Skip to content

common, db, cl, execution, rpc, node: consolidate pool and retry utilities#22170

Queued
yperbasis wants to merge 11 commits into
mainfrom
yperbasis/shared-pools
Queued

common, db, cl, execution, rpc, node: consolidate pool and retry utilities#22170
yperbasis wants to merge 11 commits into
mainfrom
yperbasis/shared-pools

Conversation

@yperbasis

@yperbasis yperbasis commented Jul 2, 2026

Copy link
Copy Markdown
Member

Four independent consolidations of copy-pasted utility code, plus review follow-ups:

  1. bufio pools ×4 → db/bufiopool. getBufioWriter/putBufioWriter (+reader twins in two of them) were byte-identical — including comments — in db/etl, db/recsplit, db/seg, and db/datastruct/btindex. One package now owns them; 512KB size and Reset(nil)-before-Put semantics preserved verbatim.
  2. keccak pools ×2 → one. common/hasher.go and common/crypto/crypto.go each pooled keccak.NewFastKeccak(). The single pool of raw KeccakState lives in common (import direction forces it); crypto.NewKeccakState/ReturnToPool delegate. Reset-on-Get semantics identical on both old paths; no exported signature changed. The no-longer-pooled Hasher wrapper is free in practice: NewHasher now inlines (the old pooled version exceeded the inlining budget) and &Hasher{} does not escape at any call site, so no per-call heap allocation appeared.
  3. bytes.Buffer pools ×8 → common/pool. Eight private sync.Pool{New: …&bytes.Buffer{}} copies across cl/persistence, cl/antiquary, db/snapshotsync, execution/types, and node/rpcstack (gzip responses) replaced by GetBuffer/PutBuffer; Put drops buffers over 1MB so one oversized use can't pin memory (the rpcstack copy already used exactly this cap; the others had none). Call sites that routinely exceed 1MB on mainnet no longer use a pool at all: the CL read paths (historical-states dumps/diffs, beacon-block decompression) only wrapped an in-memory slice to read it back and now feed bytes.NewReader straight to the zstd reader — zero-copy, cheaper than the old uncapped pools, which copied the whole blob per call — and the antiquary's multi-MB diff writer reuses the collector-owned buffer like its sibling helpers. The pool's remaining users are typically-sub-1MB write targets. Reset-on-get and drop-oversized semantics are unit-tested; a dead plainBytesBufferPool in base_encoding removed along the way.
  4. rpc/requests: retryConnects → cenkalti/backoff/v4 (already a dependency). Per-attempt 500ms and overall 20s budgets and dial-error propagation preserved, pinned by characterization tests written and run green against the old implementation first. Two error-swallowing bugs fixed on top, pinned by red-first tests: overall-deadline expiry after only per-attempt timeouts now returns context.DeadlineExceeded instead of nil (callers used to see success with an unpopulated result); and on overall-deadline expiry the last dial error is substituted only for the context's own deadline error, never for a permanent error that merely wraps DeadlineExceeded. Because backoff.Retry surfaces the overall context's error verbatim when it gives up, the substitution keys off err == context.DeadlineExceeded (identity) rather than errors.Is, so a backoff.Permanent error that wraps DeadlineExceeded is left intact.

Net −146 production lines plus +180 of tests. Re-verified after merging latest main: go test on every touched tree, make lint clean, go build ./..., make erigon integration.

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

yperbasis added 4 commits July 2, 2026 21:22
Four byte-identical copies of the pooled 512KB bufio reader/writer
helpers lived in db/etl, db/recsplit, db/seg and db/datastruct/btindex.
Consolidate them into a new db/bufiopool package, keeping the buffer
size and the Reset(nil)-before-Put semantics unchanged.
common/hasher.go and common/crypto/crypto.go each kept their own
sync.Pool of fastkeccak states. Keep a single pool of raw KeccakState
in the common package (common/crypto imports common, so the pool must
live there) and delegate crypto's NewKeccakState/ReturnToPool to it.

Get/Reset/Put semantics are unchanged: both implementations reset on
Get and put back without reset. The pool now holds raw KeccakStates
instead of Hasher wrappers, so common.NewHasher allocates a 16-byte
wrapper per call; the hotter crypto path (Keccak256, Keccak256Hash,
RlpHash) stays allocation-free.
Seven packages each declared their own sync.Pool of bytes.Buffer with
the same get/reset/put pattern. Consolidate them into common/pool
(GetBuffer resets on get, matching every existing call site) and add a
1MB capacity cap on put so one oversized use cannot pin memory now that
unrelated subsystems share the pool.

The sibling pools in cl/persistence/base_encoding (plain uint64/byte/
pattern slices) and the zstd encoder/decoder pools are left as-is.
Replace the hand-rolled recursive retry helper in retryConnects with
cenkalti/backoff/v4 (constant 1s backoff, context-bound), preserving
the existing semantics exactly: 500ms per-attempt timeout within a 20s
overall budget, retry on dial errors and deadline expiries, immediate
return on any other error, a per-attempt timeout following a dial
error reports that dial error, and overall-deadline expiry reports the
last dial error (nil when attempts only ever timed out).

The new retry_test.go pins those semantics; it was written against the
old implementation and passes unchanged against the new one.

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 consolidates several previously duplicated pooling and retry utilities into shared packages, reducing copy-paste drift risk while preserving existing behavior (including a characterized retry “nil-on-timeout” quirk).

Changes:

  • Consolidates repeated bufio.Reader/Writer pooling into db/bufiopool and updates DB utilities to use it.
  • Consolidates repeated *bytes.Buffer pooling into common/pool (with a 1MB cap on pooled buffers) and updates CL/DB/execution call sites.
  • Unifies KeccakState pooling into common and updates common/crypto to delegate; replaces rpc/requests retry logic with cenkalti/backoff/v4 and adds characterization tests.

Reviewed changes

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

Show a summary per file
File Description
rpc/requests/retry_test.go Adds characterization tests for retryConnects semantics.
rpc/requests/request_generator.go Replaces custom retry loop with cenkalti/backoff/v4 while preserving semantics.
execution/types/receipt.go Switches typed receipt encoding buffer reuse to common/pool.
execution/types/hashing.go Removes the now-duplicated local buffer pool.
db/snapshotsync/freezeblocks/caplin_snapshots.go Switches decompression staging buffer reuse to common/pool.
db/snapshotsync/freezeblocks/beacon_block_reader.go Removes local buffer pool and uses common/pool.
db/seg/parallel_compress.go Switches bufio pooling to db/bufiopool.
db/seg/compress.go Removes local bufio pool and uses db/bufiopool.
db/recsplit/recsplit.go Removes local bufio pool and uses db/bufiopool.
db/etl/dataprovider.go Switches writer pooling to db/bufiopool.
db/datastruct/btindex/btree_index.go Switches writer pooling to db/bufiopool.
db/datastruct/btindex/btree_index_test.go Updates test helper to use db/bufiopool.
db/bufiopool/bufiopool.go Introduces shared 512KB bufio reader/writer pooling.
common/pool/pool.go Introduces shared *bytes.Buffer pooling with a max-cap drop policy.
common/hasher.go Unifies pooled KeccakState ownership in common.
common/crypto/crypto.go Delegates keccak pooling to common.
cl/persistence/state/historical_states_reader/historical_states_reader.go Switches buffer pooling to common/pool.
cl/persistence/format/snapshot_format/blocks.go Switches buffer pooling to common/pool.
cl/persistence/beacon_indicies/indicies.go Switches buffer pooling to common/pool.
cl/persistence/base_encoding/uint64_diff.go Switches buffer pooling to common/pool.
cl/antiquary/state_antiquary.go Removes local buffer pool (now shared).
cl/antiquary/beacon_states_collector.go Switches diff buffer pooling to common/pool.

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

Comment thread rpc/requests/retry_test.go
@yperbasis

Copy link
Copy Markdown
Member Author

Filed the follow-up for the retry quirk preserved by the characterization tests here: #22182 (retryConnects reports success when every connect attempt timed out).

yperbasis added 3 commits July 3, 2026 10:43
… buffer copies

The CL read paths (historical-states dumps/diffs, beacon-block
decompression) only wrapped an in-memory slice in a pooled buffer to
read it back; feed bytes.NewReader to the zstd reader instead, dropping
both the copy and the pool round-trip. These blobs routinely exceed
common/pool's 1MB Put cap on mainnet (a full balances dump is ~16MB
raw), so pooling bought nothing there anyway.

The antiquary's multi-MB diff writer now reuses the collector-owned
buffer like the sibling helpers in the same file.

Also remove the dead plainBytesBufferPool in base_encoding.
Two error-swallowing fixes, each pinned by a test written red-first:

- Overall-deadline expiry after only per-attempt timeouts returned nil,
  so callers saw success with an unpopulated result. Return
  context.DeadlineExceeded instead.
- The final deadline-to-dial-error remap matched any error wrapping
  DeadlineExceeded, including a permanent one (e.g. a non-dial
  net.OpError joined with the deadline sentinel), replacing it with the
  stale dial error or nil. Require the overall context to have actually
  expired.
node/rpcstack's gzBufPool duplicated common/pool exactly, down to the
1MB drop cap. Its cap-threshold test moves to common/pool as unit tests
of the shared pool's reset-on-get and drop-oversized semantics.
gzDstPool stays: it pools []byte for libdeflate, not bytes.Buffer.
@yperbasis yperbasis changed the title common, db, cl, execution, rpc: consolidate pool and retry utilities common, db, cl, execution, rpc, node: consolidate pool and retry utilities Jul 3, 2026

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 26 out of 26 changed files in this pull request and generated 1 comment.

Comment thread node/rpcstack_gzip_handler_test.go
pull Bot pushed a commit to Dustin4444/erigon that referenced this pull request Jul 3, 2026
…erigontech#22171)

`cmd/downloader`, `txnprovider/txpool`, `node/privateapi`, and
`p2p/sentry` each re-inlined the interceptor/keepalive option block that
`grpcutil.NewServer` already builds — two of them complete with its dead
commented-out metrics hooks — and all four repeated the same listen +
health-server + serve-goroutine tail.

`NewServer(rateLimit, creds)` now delegates to a new
`NewServerWithOpts(creds, extraOpts...)` (nil-safe creds, no forced
`MaxConcurrentStreams`), and `StartServer`/`StartServerOnListener` own
the listen/health/serve tail. The four sites convert onto these.

Option preservation was the review focus, per site: downloader and
txpool still run **without** `MaxConcurrentStreams` (as before); txpool
keeps its explicit `ReadBufferSize(0)`/`WriteBufferSize(0)`;
privateapi/sentry keep health registration gated on their `healthCheck`
flag while downloader/txpool keep it unconditional; existing `NewServer`
callers see byte-identical behavior.

Two deliberate exceptions to strict preservation:
- Both converted `StartGrpc`s now take
`credentials.TransportCredentials` by value (as `privateapi.StartGrpc`
already does) instead of pointer-to-interface, dropping the identical
unwrap shim at each site; their sole callers pass literal `nil` and are
unchanged.
- The txpool serve-failure log now reads `txpool gRPC server fail`
instead of the `private RPC server fail` string copy-pasted from
privateapi — in the standalone txpool daemon, `--private.api.addr` names
the remote node it connects to, so the old label pointed triage at the
wrong process. Nothing greps or asserts these strings.

Otherwise no behavior change — existing tests are the safety net
(txpool, privateapi, sentry suites green). Full `make lint` clean, `make
erigon integration` build. Net −117 lines.

Part of a dedup series; siblings erigontech#22165erigontech#22170.
yperbasis added 3 commits July 7, 2026 20:45
This unit test exercises gzipResponseWriter.Flush(); the buffer's origin
is irrelevant to it, so borrowing from the shared common/pool (without
returning it) added coupling for no benefit. Use a plain bytes.Buffer.
…wrapped error

On overall-deadline expiry retryConnects reports the last dial error that
backoff.Retry discarded. The guard used errors.Is(err, DeadlineExceeded),
which also matches a permanent (non-connection) error that merely wraps
DeadlineExceeded — so once a dial error had been seen and the overall
deadline had expired, such a permanent error was swallowed and the older
dial error returned instead.

backoff.Retry surfaces the context's error verbatim (the bare sentinel)
only when it gives up on the overall deadline; a permanent error comes back
as its own wrapped value. Compare by identity to tell the two apart, which
also makes the now-redundant ctx.Err() conjunct unnecessary. Pinned by a
red-first test.
@yperbasis yperbasis marked this pull request as ready for review July 7, 2026 19:26
@yperbasis yperbasis requested a review from Copilot July 7, 2026 19:26

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 26 out of 26 changed files in this pull request and generated no new comments.

Comment thread common/pool/pool.go
)

// Buffers that grew beyond maxBufferCap are dropped on Put so that one oversized use cannot pin memory in the pool.
const maxBufferCap = 1 << 20

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

1gb?

@AskAlexSharov AskAlexSharov added this pull request to the merge queue Jul 8, 2026
Any commits made after this event will not be merged.
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