mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-19 14:29:27 +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.
|
// Reset() in order to not leak memory.
|
||||||
// OBS: It does not invoke Close on the diskdb
|
// OBS: It does not invoke Close on the diskdb
|
||||||
func (dl *diskLayer) Release() error {
|
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 {
|
if dl.cache != nil {
|
||||||
dl.cache.Reset()
|
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.
|
// stopGeneration aborts the state snapshot generation if it is currently running.
|
||||||
func (dl *diskLayer) stopGeneration() {
|
func (dl *diskLayer) stopGeneration() {
|
||||||
dl.lock.RLock()
|
// Check if generation goroutine is running by checking if genAbort channel exists.
|
||||||
generating := dl.genMarker != nil
|
// Note: genMarker can be nil even when the generator is still running (waiting
|
||||||
dl.lock.RUnlock()
|
// for abort signal after completing generation), so we check genAbort instead.
|
||||||
if !generating {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if dl.genAbort != nil {
|
if dl.genAbort != nil {
|
||||||
abort := make(chan *generatorStats)
|
abort := make(chan *generatorStats)
|
||||||
dl.genAbort <- abort
|
dl.genAbort <- abort
|
||||||
|
|
|
||||||
|
|
@ -986,3 +986,48 @@ func testGenerateBrokenSnapshotWithDanglingStorage(t *testing.T, scheme string)
|
||||||
snap.genAbort <- stop
|
snap.genAbort <- stop
|
||||||
<-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