fix: exempt consensus recovery reads from the 30s MDBX read-txn timeout#29
fix: exempt consensus recovery reads from the 30s MDBX read-txn timeout#29procdump wants to merge 2 commits into
Conversation
|
Claude finished @procdump's task in 2m 29s —— View job Code Review
Overall: This is a clean, well-reasoned fix that correctly addresses the root cause. The design is minimal and targeted — it exempts exactly the two recovery-path callers that need it, leaves everything else enforced, and follows the established What the change does
FindingsConfirmed correct
Nit: doc comment says "potentially large" — the issue is latency, not size
/// `timeout` controls whether this (potentially large) scan is subject to the read-transaction
/// timeout; recovery-path callers pass [`ReadTimeout::Exempt`].
The incident showed a ~600-round scan hitting the 30 s cap during an I/O stall — clearly not a "large" scan. The framing "potentially large" might mislead future readers into thinking only big scans need exemption. A tighter description would be: "a bounded, one-shot recovery scan that may outlast the timeout under I/O pressure". The per-variant doc on Minor:
|
b0319ca to
7efdb2d
Compare
|
Claude finished @procdump's task in 2m 12s —— View job PR Review: fix: exempt consensus recovery reads from the 30s MDBX read-txn timeout
SummaryThis is a clean, minimal, well-motivated fix for a real production incident. The root cause analysis is thorough and the solution is correctly scoped. The design choices (an explicit enum over a bool, using the existing No blocking issues. A few observations below. Correctness
Design observations
Test coverageThe test at There's no test exercising the Nits
VerdictApprove. The fix is correct, minimal, and well-documented. The PR description's incident timeline is an excellent addition to the project history — it makes the "why" obvious to anyone who reads this code later. |
|
Claude finished @procdump's task in 2m 11s —— View job PR Review: fix: exempt consensus recovery reads from the 30s MDBX read-txn timeout
SummaryThis PR is correct and production-ready. The two commits are well-structured: the first applies the fix (adds What changed since the prior reviewsSecond commit ( This is a clean improvement.
This resolves the "defined in Confirmed correct
Outstanding nits from prior reviews (still not fixed)Nit: trait doc says "potentially large" — the issue is I/O latency, not size
/// `timeout` controls whether this (potentially large) scan is subject to the read-transaction
/// timeout; recovery-path callers pass [`ReadTimeout::Exempt`].
The incident was a ~600-round scan timing out under I/O pressure — clearly not large. "Potentially large" risks future maintainers assuming only big scans need exemption. The per-variant doc on Optional:
|
`after_round` runs a bounded one-shot scan on the recovery path (DAG rebuild
in `construct_dag_from_cert_store`, and `prime_consensus`). Both open a read
txn subject to the 30s `max_read_transaction_duration` cap. During an I/O
stall the scan outlives the cap, MDBX force-resets the txn, and the next
access errors — which `state.rs` turned into a `.expect` panic that killed
the node mid-write and left the DB corrupted on the next open (crash loop).
Add a `ReadTimeout { Enforced, Exempt }` param to `after_round` and pass
`Exempt` from both recovery callers via `disable_long_read_safety()`, so a
transient stall can no longer guillotine recovery. The 30s cap still guards
every other reader against free-list bloat and writer starvation.
`prime_consensus` also benefits: it now returns the correct `cert_store_round`
instead of degrading to a stale value on timeout.
ReadTimeout describes a read-transaction property, not a certificate concept, so it belongs beside Database/DbTx in the types crate rather than in certificate_store. Re-exported from the storage crate so existing rayls_infrastructure_storage::ReadTimeout imports are unchanged. No behavior change.
f24cd9b to
dc6b6a3
Compare
|
Claude finished @procdump's task in 1m 48s —— View job PR Review: fix: exempt consensus recovery reads from the 30s MDBX read-txn timeout
Overall: correct, minimal, and well-motivated. The root-cause analysis in the PR description is thorough, and the fix is precisely scoped to the two callsites that actually need exemption. The subsequent refactor commit ( What the change does
FindingsConfirmed correct
Minor:
|
Problem
A validator crash-looped into an unrecoverable "database is corrupted" state.
Root cause was a transient storage stall, not a logic bug.
Timeline from the incident logs:
~15:10:38— the node's storage stalls; execution freezes (block stuck at4776429 for ~78s while consensus rounds keep advancing).
15:11:08 / 15:11:24 / 15:11:56— three separate read transactions each hitthe 30s
max_read_transaction_durationcap ("Long-lived read transaction hasbeen timed out"). Multiple unrelated readers timing out at once is an I/O
stall signature, not a slow query.
15:11:56— the DAG-rebuild read inconstruct_dag_from_cert_store(
after_round) is one of them. Its.expect("database available")turns thetimeout into a panic, killing the process mid-write.
15:11:57onward — on restart MDBXopen()returnsMDBX_CORRUPTED(writemap +
SafeNoSync, killed mid-writeback during the stall). Crash loop withexponential backoff, never recovers.
The scan that panicked was tiny (~600 rounds; a healthy rebuild does 500 rounds
in ~76ms), which rules out "large scan" and confirms the stall.
Fix
The read-txn timeout exists to stop leaked/hung readers from pinning the MVCC
free-list and starving the writer — worth keeping. But it should not guillotine
a bounded, one-shot recovery scan that legitimately runs long under I/O
pressure. The repo already has
disable_long_read_safety()for exactly this(used by the epoch-manager tally walk);
after_roundjust never opted in.ReadTimeout { Enforced, Exempt }param toafter_round.Exempt:construct_dag_from_cert_store(the crash path) — keeps its.expect,which now only fires on a real DB fault (panic-at-boot is correct there).
prime_consensus— now returns the correctcert_store_roundinstead ofdegrading to a stale value on timeout.