diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go index 655ffc3544..7650bb2e7d 100644 --- a/core/state/trie_prefetcher_test.go +++ b/core/state/trie_prefetcher_test.go @@ -19,6 +19,7 @@ package state import ( "math/big" "sync" + "sync/atomic" "testing" "github.com/ethereum/go-ethereum/common" @@ -71,8 +72,8 @@ func TestSchdeulerTerminationRaceCondition(t *testing.T) { // The lock-based implementation of [subfetcher] had a race condition // whereby schedule() could obtain the lock after the <-sf.stop branch of // loop() had already checked for an empty queue. Although probabilistic, - // this test reliably triggered at a rate of ~4 in 10,000 on an Apple M3 Max - // chip. + // this test reliably triggered at a rates of (~4 in 10k) and (~100 in 50k) + // on an Apple M3 Max chip. t.Parallel() db := filledStateDB() @@ -80,9 +81,13 @@ func TestSchdeulerTerminationRaceCondition(t *testing.T) { // Maximise concurrency by synchronising all scheduling and termination. start := make(chan struct{}) - var wg sync.WaitGroup + var ( + wg sync.WaitGroup + raceInduced atomic.Uint64 + ) - for i := 0; i < 50_000; i++ { + const numTrials = 50_000 + for i := 0; i < numTrials; i++ { wg.Add(2) fetcher := newSubfetcher(db.db, db.originalRoot, common.Hash{}, db.originalRoot, common.Address{}) @@ -102,13 +107,16 @@ func TestSchdeulerTerminationRaceCondition(t *testing.T) { <-doneScheduling if gotScheduleErr == nil && len(fetcher.tasks) > 0 { - t.Errorf("%T.schedule() returned nil error but %d task(s) remain in queue after %T.terminate([blocking]) returned", fetcher, len(fetcher.tasks), fetcher) + raceInduced.Add(1) } }() } close(start) wg.Wait() + if got := raceInduced.Load(); got > 0 { + t.Errorf("In %d/%d concurrent trials %T.schedule() returned nil error but >0 tasks remain in queue after %[3]T.terminate([blocking]) returned", got, numTrials, &subfetcher{}) + } } func TestVerklePrefetcher(t *testing.T) {