Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a19bd5e
fix(vmsync): log context cancellation as INFO instead of ERROR during…
powerslider Nov 17, 2025
746ab4b
fix(vmsync): improve on remarks
powerslider Nov 17, 2025
eaef2ad
fix: remove checking if error is wrapped
powerslider Nov 17, 2025
cd986bc
style: improvements
powerslider Nov 17, 2025
e5cbefc
fix: add context cancellation check to block syncer loop
powerslider Nov 18, 2025
51f258a
fix(statesync): add context cancellation checks to prevent shutdown hang
powerslider Nov 18, 2025
7190c80
fix(statesync): remove pre-select context err check in code_queue.go
powerslider Nov 19, 2025
d3f9869
feat(statesync): introduce Finalizer interface for syncer cleanup
powerslider Nov 21, 2025
07546b5
fix: necessary cleanup in registry_test.go
powerslider Nov 26, 2025
29e2310
Merge branch 'master' into powerslider/1089-finalize-syncers
powerslider Nov 26, 2025
ce4f9e7
style: gci format
powerslider Nov 26, 2025
5796e07
style: gci format
powerslider Nov 26, 2025
cbd0706
style: gci format
powerslider Nov 27, 2025
0bde2b9
fix: add an early return for the success case in Finalize() in the st…
powerslider Dec 1, 2025
841ef64
fix: remove unnecessary defensive db nil check in the atomic syncer F…
powerslider Dec 1, 2025
f077d73
Merge branch 'master' into powerslider/1089-finalize-syncers
powerslider Dec 2, 2025
58adc19
fix(statesync): add a syncCompleted flag to unambiguously determine s…
powerslider Dec 3, 2025
d9c9720
Merge branch 'master' into powerslider/1089-finalize-syncers
powerslider Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion graft/coreth/plugin/evm/atomic/sync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
var (
_ sync.Syncer = (*Syncer)(nil)
_ syncclient.LeafSyncTask = (*syncerLeafTask)(nil)
_ sync.Finalizer = (*Syncer)(nil)

errTargetHeightRequired = errors.New("target height must be > 0")
)
Expand Down Expand Up @@ -125,7 +126,6 @@ func NewSyncer(client syncclient.LeafClient, db *versiondb.Database, atomicTrie
syncer.syncer = syncclient.NewCallbackLeafSyncer(client, tasks, &syncclient.LeafSyncerConfig{
RequestSize: cfg.requestSize,
NumWorkers: cfg.numWorkers,
OnFailure: func() {}, // No-op since we flush progress to disk at the regular commit interval.
})

return syncer, nil
Expand All @@ -146,6 +146,13 @@ func (s *Syncer) Sync(ctx context.Context) error {
return s.syncer.Sync(ctx)
}

// Finalize commits any pending database changes to disk.
// This ensures that even if the sync is cancelled or fails, we preserve
// the progress up to the last fully synced height.
func (s *Syncer) Finalize() error {
return s.db.Commit()
}

// addZeroes returns the big-endian representation of `height`, prefixed with [common.HashLength] zeroes.
func addZeroes(height uint64) []byte {
// Key format is [height(8 bytes)][blockchainID(32 bytes)]. Start should be the
Expand Down
16 changes: 16 additions & 0 deletions graft/coreth/plugin/evm/vmsync/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ func (r *SyncerRegistry) Register(syncer syncpkg.Syncer) error {

// RunSyncerTasks executes all registered syncers synchronously.
func (r *SyncerRegistry) RunSyncerTasks(ctx context.Context, summary message.Syncable) error {
// Ensure finalization runs regardless of how this function exits.
// This guarantees cleanup even on early returns or panics.
defer r.FinalizeAll(summary)

// Early return if context is already canceled (e.g., during shutdown).
if err := ctx.Err(); err != nil {
return err
Expand Down Expand Up @@ -102,3 +106,15 @@ func (r *SyncerRegistry) StartAsync(ctx context.Context, summary message.Syncabl

return g
}

// FinalizeAll iterates over all registered syncers and calls Finalize on those that implement the Finalizer interface.
// Errors are logged but not returned to ensure best-effort cleanup of all syncers.
func (r *SyncerRegistry) FinalizeAll(summary message.Syncable) {
for _, task := range r.syncers {
if f, ok := task.syncer.(syncpkg.Finalizer); ok {
if err := f.Finalize(); err != nil {
log.Error("failed to finalize syncer", "syncer", task.name, "err", err, "summary", summary.GetBlockHash().Hex(), "height", summary.Height())
}
}
}
}
9 changes: 4 additions & 5 deletions graft/coreth/sync/client/leaf_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ type LeafSyncTask interface {
type LeafSyncerConfig struct {
RequestSize uint16 // Number of leafs to request from a peer at a time
NumWorkers int // Number of workers to process leaf sync tasks
OnFailure func() // Callback for handling errors during sync
}

type CallbackLeafSyncer struct {
Expand Down Expand Up @@ -159,9 +158,9 @@ func (c *CallbackLeafSyncer) Sync(ctx context.Context) error {
})
}

err := eg.Wait()
if err != nil {
c.config.OnFailure()
if err := eg.Wait(); err != nil {
return err
}
return err

return nil
Comment on lines +161 to +165
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err := eg.Wait(); err != nil {
return err
}
return err
return nil
return eg.Wait()

}
4 changes: 4 additions & 0 deletions graft/coreth/sync/statesync/code_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ import (
"github.com/ava-labs/libevm/libevm/options"

"github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customrawdb"

syncpkg "github.com/ava-labs/avalanchego/graft/coreth/sync"
)

const defaultQueueCapacity = 5000

var (
_ syncpkg.Finalizer = (*CodeQueue)(nil)

errFailedToAddCodeHashesToQueue = errors.New("failed to add code hashes to queue")
errFailedToFinalizeCodeQueue = errors.New("failed to finalize code queue")
)
Expand Down
27 changes: 19 additions & 8 deletions graft/coreth/sync/statesync/state_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"sync"
"sync/atomic"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/ethdb"
Expand Down Expand Up @@ -59,6 +60,10 @@ type stateSync struct {
storageTriesDone chan struct{}
triesInProgressSem chan struct{}
stats *trieSyncStats

// syncCompleted is set to true when the sync completes successfully.
// This provides an explicit success signal for Finalize().
syncCompleted atomic.Bool
}

// SyncerOption configures the state syncer via functional options.
Expand Down Expand Up @@ -106,7 +111,6 @@ func NewSyncer(client syncclient.Client, db ethdb.Database, root common.Hash, co
ss.syncer = syncclient.NewCallbackLeafSyncer(client, ss.segments, &syncclient.LeafSyncerConfig{
RequestSize: leafsRequestSize,
NumWorkers: defaultNumWorkers,
OnFailure: ss.onSyncFailure,
})

if codeQueue == nil {
Expand Down Expand Up @@ -214,7 +218,11 @@ func (t *stateSync) onMainTrieFinished() error {
// [mainTrie]'s batch last to avoid persisting the state
// root before all storage tries are done syncing.
func (t *stateSync) onSyncComplete() error {
return t.mainTrie.batch.Write()
if err := t.mainTrie.batch.Write(); err != nil {
return err
}
t.syncCompleted.Store(true)
return nil
}

// storageTrieProducer waits for the main trie to finish
Expand Down Expand Up @@ -301,19 +309,22 @@ func (t *stateSync) removeTrieInProgress(root common.Hash) (int, error) {
return len(t.triesInProgress), nil
}

// onSyncFailure is called if the sync fails, this writes all
// batches of in-progress trie segments to disk to have maximum
// progress to restore.
func (t *stateSync) onSyncFailure() {
// Finalize flushes in-progress trie batches to disk to preserve progress on failure.
func (t *stateSync) Finalize() error {
if t.syncCompleted.Load() {
return nil
}

t.lock.RLock()
defer t.lock.RUnlock()

for _, trie := range t.triesInProgress {
for _, segment := range trie.segments {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(could be an over-cautious comment)
this looks like should've been cleared on success, but if not then we might end-up in a weird spot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check my previous reply #4623 (comment)

if err := segment.batch.Write(); err != nil {
log.Error("failed to write segment batch on sync failure", "err", err)
return
log.Error("failed to write segment batch on finalize", "err", err)
return err
}
}
}
return nil
}
7 changes: 7 additions & 0 deletions graft/coreth/sync/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ type Syncer interface {
ID() string
}

// Finalizer provides a mechanism to perform cleanup operations after a sync operation.
// This is useful for handling inflight requests, flushing to disk, or other cleanup tasks.
type Finalizer interface {
// Finalize performs any necessary cleanup operations.
Finalize() error
}

// SummaryProvider is an interface for providing state summaries.
type SummaryProvider interface {
StateSummaryAtBlock(ethBlock *types.Block) (block.StateSummary, error)
Expand Down