Skip to content

db/snapshotsync: share segment open/close helpers with caplin snapshots#22172

Merged
AskAlexSharov merged 3 commits into
mainfrom
yperbasis/caplin-snapshots-reuse
Jul 8, 2026
Merged

db/snapshotsync: share segment open/close helpers with caplin snapshots#22172
AskAlexSharov merged 3 commits into
mainfrom
yperbasis/caplin-snapshots-reuse

Conversation

@yperbasis

@yperbasis yperbasis commented Jul 2, 2026

Copy link
Copy Markdown
Member

The two caplin snapshot containers (freezeblocks.CaplinSnapshots, snapshotsync.CaplinStateSnapshots) re-implemented RoSnapshots' OpenList machinery — the find-open-segment walk, the optimistic-open error dance (duplicated twice more inline across CaplinSnapshots.OpenList's two identical switch arms), and closeWhatNotInList.

The copies had drifted in unsafe ways, which is the real payoff here:

  • CaplinSnapshots' close path called the embedded Decompressor.Close() instead of DirtySegment.close(), so recsplit index handles leaked on every file-list change;
  • stale unopened stubs (Decompressor == nil) were skipped rather than removed; to make that safe, CaplinStateSnapshots now derives the protect-list name from its filePath field instead of the promoted Decompressor.FilePath(), which would nil-panic on such a stub (pinned by TestCaplinStateCloseWhatNotInListDropsUnopenedStub);
  • both caplin close paths now inherit the generic refcount guard (don't close a segment a live View/RoTx still references; contract pinned by TestCloseAndDropNotProtected). Caveat: the guard is inert for the caplin containers today — they mark every segment frozen, and VisibleSegments.BeginRo doesn't refcount frozen segments — so this is future-proofing rather than an active fix, and the pre-existing close-under-live-view hazard for frozen segments is unchanged by this PR.

This extracts FindOpenSegment / ClassifyOpenErr / CloseSegmentsNotInList in snapshotsync, converts RoSnapshots and both caplin containers onto them, collapses the two identical switch arms into one loop, and deletes the vestigial processed flag with its stray "Only bob sidecars count" comment (semantics preserved: segmentsMax still tracks BeaconBlocks only in CaplinSnapshots; CaplinStateSnapshots tracked it for every file and still does).

Deliberately out of scope: making the caplin containers embed RoSnapshots wholesale; un-freezing caplin segments so the refcount guard actually engages for their views (a refcount-contention trade-off deserving its own PR).

Behavior changes (all safety-directional, called out above): full index+seg close, stub removal (incl. the nil-panic fix), refcount guard on caplin close paths (inert today). Everything else is behavior-preserving; snapshotsync/freezeblocks suites green, scoped golangci-lint clean (×2), make erigon builds. Net −120 lines of non-test code (−50 including the two new tests).

Part of a dedup series; siblings #22165#22171.

The two caplin snapshot containers re-implemented RoSnapshots' OpenList
machinery: the find-open-segment walk, the optimistic-open error
classification (twice inline in freezeblocks' CaplinSnapshots, once per
its two segment types), and closeWhatNotInList. The copies had drifted
unsafely: neither honored the refcount guard that keeps a segment alive
while a View still reads it, and freezeblocks' close path used the
embedded Decompressor's Close instead of DirtySegment.close, leaking
index handles.

Extract FindOpenSegment/ClassifyOpenErr/CloseSegmentsNotInList in
snapshotsync, use them from RoSnapshots and both caplin containers, and
drop the vestigial 'processed' flag and its stray comment. Caplin close
paths now inherit the refcount guard (pinned by
TestCloseAndDropNotProtected), full index+seg closing, and removal of
stale unopened stubs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR deduplicates and centralizes snapshot segment open/close logic in db/snapshotsync, then switches both Caplin snapshot containers and RoSnapshots to use the shared helpers to prevent drift and fix safety issues (notably refcount-guarded closes and full segment/index close).

Changes:

  • Extracts shared helpers in snapshotsync for finding an already-open segment, classifying open errors, and closing/dropping segments not present in a file list (with refcount guard).
  • Refactors freezeblocks.CaplinSnapshots and snapshotsync.CaplinStateSnapshots to use the shared helpers, collapsing duplicated open-list switch arms and reusing the close/drop logic.
  • Adds a regression test ensuring close/drop honors both the “protected list” and “segment still referenced by a live reader” refcount guard.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
db/snapshotsync/snapshots.go Adds reusable helpers (FindOpenSegment, ClassifyOpenErr, CloseSegmentsNotInList) and refactors RoSnapshots to use them.
db/snapshotsync/snapshots_test.go Adds TestCloseAndDropNotProtected to pin refcount-guard behavior.
db/snapshotsync/freezeblocks/caplin_snapshots.go Replaces duplicated open/close machinery with calls into snapshotsync helpers.
db/snapshotsync/caplin_state_snapshots.go Reuses shared open-error classification and close/drop helper logic for caplin state snapshots.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread db/snapshotsync/caplin_state_snapshots.go
pull Bot pushed a commit to Dustin4444/erigon that referenced this pull request Jul 3, 2026
`bytes48.go` and `bytes96.go` were byte-identical whole files modulo the
type name; `bytes4.go`, `bytes64.go`, `hash.go`, and `address.go`
repeated most of the same method bodies. Since Go can't parameterize
`[N]byte` by length and these types must stay comparable arrays, the
bodies move to unexported helpers over `[]byte` (`fixedFormat`,
`fixedSetBytes`, `fixedTerminalString`, `fixedGenerate`); each method
becomes a one-liner passing `b[:]`. Net −169 production lines; no
exported API change.

Kept bespoke on purpose: `Address`'s EIP-55 checksummed
`Hex`/`String`/`Format`; `Bytes4.TerminalString` (`%x` of the whole
array — head…tail would overlap at N=4); `Hash.Generate`
(`math/rand/v2`, incompatible with the v1 quick-check helper); all
Hash/Address-only methods.

Safety net: `TestFixedBytesGolden` (+377 lines) pins per type — every
Format verb incl. bad-verb labels, String/Hex/MarshalText/Value,
TerminalString, SetBytes placement, UnmarshalText/JSON error strings
(incl. the `BLSSignature` label), seeded `Generate`, and the EIP-55
canonical example for Address. Goldens were captured from the
pre-refactor implementation and stay green through the refactor.

First commit is a cherry-pick of the `SetBytes` fix from erigontech#22165 (this
consolidation builds on it); it will drop out on rebase once that PR
merges.

Verified: `go test ./common/...`, `go build ./...`, scoped
`golangci-lint` clean (×2), `make erigon`.

Part of a dedup series; siblings erigontech#22165erigontech#22172.

---------

Co-authored-by: awskii <artem.tsskiy@gmail.com>
yperbasis added 2 commits July 7, 2026 20:51
…en ClassifyOpenErr

The closeWhatNotInList nameOf closure called the promoted
Decompressor.FilePath(), which nil-panics on an unopened stub; use the
filePath field, which is set for every tree member. Pinned by
TestCaplinStateCloseWhatNotInListDropsUnopenedStub. Also drop
ClassifyOpenErr's unused skip return value.
@yperbasis yperbasis requested a review from Copilot July 7, 2026 19:11
@yperbasis yperbasis marked this pull request as ready for review July 7, 2026 19:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@AskAlexSharov AskAlexSharov added this pull request to the merge queue Jul 8, 2026
Merged via the queue into main with commit 76e5d1d Jul 8, 2026
98 checks passed
@AskAlexSharov AskAlexSharov deleted the yperbasis/caplin-snapshots-reuse branch July 8, 2026 04:40
AskAlexSharov added a commit that referenced this pull request Jul 8, 2026
db/snapshotsync conflict resolution: main's #22172 extracted the per-segment
`DirtySegment.refcount` close model into shared helpers and wired caplin to
them. This branch removed per-segment refcount in favour of the generation
model (snapshotVisible.refcnt), so the RoSnapshots close path keeps
detachNotInList + generation reclamation, while the shared
CloseSegmentsNotInList/closeAndDropNotProtected helpers are retained (they
dedup caplin's own close code) minus the refcount check, matching caplin's
existing no-per-reader-protection behaviour. Kept main's generic
FindOpenSegment/ClassifyOpenErr helpers and the OpenList dedup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants