Compare commits

...

3 Commits

Author SHA1 Message Date
Arran Schlosberg e15cb3e553
chore: report aggregate race conditions in test 2024-11-28 13:09:17 +00:00
Arran Schlosberg 5b4ac11371
test: block on `terminate(false)` instead of `wait()`
This is effectively the same thing as both wait on `<-sf.term` but it makes for a clearer test that demonstrates the bug because `terminate(false)` is documented as blocking until "all disk loads finish".
2024-11-28 12:53:49 +00:00
Arran Schlosberg 2e75e74435
test: demonstrate fix of race condition in locking impl 2024-11-28 12:47:31 +00:00
1 changed files with 53 additions and 0 deletions

View File

@ -18,6 +18,8 @@ package state
import (
"math/big"
"sync"
"sync/atomic"
"testing"
"github.com/ethereum/go-ethereum/common"
@ -66,6 +68,57 @@ func TestUseAfterTerminate(t *testing.T) {
}
}
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 rates of (~4 in 10k) and (~100 in 50k)
// on an Apple M3 Max chip.
t.Parallel()
db := filledStateDB()
skey := common.HexToHash("aaa")
// Maximise concurrency by synchronising all scheduling and termination.
start := make(chan struct{})
var (
wg sync.WaitGroup
raceInduced atomic.Uint64
)
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{})
var gotScheduleErr error
doneScheduling := make(chan struct{})
go func() {
defer wg.Done()
<-start
gotScheduleErr = fetcher.schedule(nil, []common.Hash{skey}, false)
close(doneScheduling)
}()
go func() {
defer wg.Done()
<-start
fetcher.terminate(false /*async*/)
<-doneScheduling
if gotScheduleErr == nil && len(fetcher.tasks) > 0 {
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) {
disk := rawdb.NewMemoryDatabase()
db := triedb.NewDatabase(disk, triedb.VerkleDefaults)