mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-03-16 08:00:39 +00:00
core/state/snapshot: properly stop snapshot generation
This commit is contained in:
parent
a8a4804895
commit
1e675e3f85
2 changed files with 52 additions and 6 deletions
|
|
@ -49,6 +49,10 @@ type diskLayer struct {
|
|||
// Reset() in order to not leak memory.
|
||||
// OBS: It does not invoke Close on the diskdb
|
||||
func (dl *diskLayer) Release() error {
|
||||
// Stop any ongoing snapshot generation to prevent it from accessing
|
||||
// the database after it's closed during shutdown
|
||||
dl.stopGeneration()
|
||||
|
||||
if dl.cache != nil {
|
||||
dl.cache.Reset()
|
||||
}
|
||||
|
|
@ -186,12 +190,9 @@ func (dl *diskLayer) Update(blockHash common.Hash, accounts map[common.Hash][]by
|
|||
|
||||
// stopGeneration aborts the state snapshot generation if it is currently running.
|
||||
func (dl *diskLayer) stopGeneration() {
|
||||
dl.lock.RLock()
|
||||
generating := dl.genMarker != nil
|
||||
dl.lock.RUnlock()
|
||||
if !generating {
|
||||
return
|
||||
}
|
||||
// Check if generation goroutine is running by checking if genAbort channel exists.
|
||||
// Note: genMarker can be nil even when the generator is still running (waiting
|
||||
// for abort signal after completing generation), so we check genAbort instead.
|
||||
if dl.genAbort != nil {
|
||||
abort := make(chan *generatorStats)
|
||||
dl.genAbort <- abort
|
||||
|
|
|
|||
|
|
@ -986,3 +986,48 @@ func testGenerateBrokenSnapshotWithDanglingStorage(t *testing.T, scheme string)
|
|||
snap.genAbort <- stop
|
||||
<-stop
|
||||
}
|
||||
|
||||
// TestReleaseStopsGeneration verifies that Release() properly stops ongoing
|
||||
// snapshot generation without hanging. This prevents a race condition during
|
||||
// shutdown where the generator could access the database after it's closed.
|
||||
//
|
||||
// The generator goroutine waits for an abort signal even after completing
|
||||
// generation successfully. Without calling stopGeneration(), Release() would
|
||||
// leave the generator hanging forever, which could prevent clean shutdown.
|
||||
func TestReleaseStopsGeneration(t *testing.T) {
|
||||
testReleaseStopsGeneration(t, rawdb.HashScheme)
|
||||
testReleaseStopsGeneration(t, rawdb.PathScheme)
|
||||
}
|
||||
|
||||
func testReleaseStopsGeneration(t *testing.T, scheme string) {
|
||||
var helper = newHelper(scheme)
|
||||
stRoot := helper.makeStorageTrie("", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, false)
|
||||
|
||||
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
|
||||
helper.makeStorageTrie("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.makeStorageTrie("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
|
||||
_, snap := helper.CommitAndGenerate()
|
||||
|
||||
select {
|
||||
case <-snap.genPending:
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("Snapshot generation failed")
|
||||
}
|
||||
|
||||
// Call Release() - this should stop generation gracefully without hanging
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
snap.Release()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("Release() hung - stopGeneration() was likely not called")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue