Guard reserved signature side effects - #8246
Merged
Amaury Chamayou (achamayou) merged 2 commits intoSep 4, 2026
Merged
Conversation
Amaury Chamayou (achamayou)
force-pushed
the
achamayou-guard-reserved-signature
branch
from
August 31, 2026 07:51
4c25903 to
2cabd3c
Compare
This was referenced Aug 31, 2026
Amaury Chamayou (achamayou)
force-pushed
the
achamayou-guard-reserved-signature
branch
from
August 31, 2026 12:29
2cabd3c to
e84bf32
Compare
This was referenced Aug 31, 2026
cjen1-msft
reviewed
Sep 3, 2026
cjen1-msft
approved these changes
Sep 3, 2026
Amaury Chamayou (achamayou)
force-pushed
the
achamayou-guard-reserved-signature
branch
from
September 4, 2026 12:44
e84bf32 to
4f3683c
Compare
Copilot started reviewing on behalf of
Amaury Chamayou (achamayou)
September 4, 2026 12:45
View session
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The new code introduces an incorrect/deprecated mutex guard type usage that is likely to fail compilation and should be fixed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a rollback race in reserved signature handling by making the “should end ledger chunk” decision and the chunker side-effect atomic with respect to rollback, preventing stale chunk-end markers from being recorded for signatures that never reach the ledger.
Changes:
- Add
AbstractStore::prepare_reserved_tx()to validate term/rollback epoch underversion_lockand (if still valid) record reserved-signature chunk side effects atomically. - Update
CommittableTx::commit_reserved()to useprepare_reserved_tx()and returnFAIL_NO_REPLICATEwhen the reserved signature is no longer valid post-rollback. - Add a single-threaded regression test in
kv_testcovering truncating rollback, view-only rollback, and the success case; document the fix inCHANGELOG.md.
Custom instructions used:
- None (no repository instruction files from
.github/copilot-instructions.mdor.github/instructions/were loaded during this review).
File summaries
| File | Description |
|---|---|
| src/kv/test/kv_test.cpp | Adds regression test ensuring reserved signature chunk side effects are skipped after rollback. |
| src/kv/store.h | Implements prepare_reserved_tx() under version_lock to guard chunk side effects against rollback/view changes. |
| src/kv/kv_types.h | Extends AbstractStore with prepare_reserved_tx() API. |
| src/kv/committable_tx.h | Uses prepare_reserved_tx() in commit_reserved() to avoid stale/torn chunk decisions across rollback. |
| CHANGELOG.md | Adds a “Fixed” entry describing the guarded reserved signature side effects. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Amaury Chamayou (achamayou)
force-pushed
the
achamayou-guard-reserved-signature
branch
from
September 4, 2026 16:39
cdcbeb0 to
eabef16
Compare
A signature transaction decided whether to end a ledger chunk, and recorded that decision on the chunker, in two steps. The comment claimed version_lock was held by Store::commit, but Store::commit releases it before calling pending_tx->call(), so the should_create_ledger_chunk_unsafe() read ran with no lock at all. A rollback landing in that window discarded the signature but left the chunk marker behind, skewing every subsequent chunk boundary. The unlocked read is also a data race: Snapshotter::record_committable calls back into Store::flag_enabled_unsafe and unset_flag_unsafe, both of which require version_lock. Validate the reserved epoch, decide, and record the marker in one atomic step under version_lock, failing replication otherwise. Store::commit already handles FAIL_NO_REPLICATE from a pending tx invalidated by rollback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 75d99c5d-6efa-4048-8032-8c78b97208d9
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Amaury Chamayou (achamayou)
force-pushed
the
achamayou-guard-reserved-signature
branch
from
September 4, 2026 19:32
eabef16 to
687f101
Compare
Amaury Chamayou (achamayou)
deleted the
achamayou-guard-reserved-signature
branch
September 4, 2026 22:16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Last of a stack of small fixes for a family of key-value store races found by an interleaving-exploration harness. Stacked on #8245.
The problem
CommittableTx::commit_reserved()decided whether a signature should end a ledger chunk, and recorded that decision on the chunker, in two steps:That comment is wrong, and has been for some time.
Store::commit()releasesversion_lockbefore it callspending_tx->call(), which is what reaches this code - so the_unsaferead runs with no lock at all, and a rollback can land between the read andproduced_chunk_at.Two consequences:
chunk_endskeeps an entry at its version.LedgerChunker::get_unchunked_sizemeasures from the most recent chunk end, so every later chunk boundary is measured from an entry that is not in the ledger.Store::commit()already anticipates this shape of failure - it handlesFAIL_NO_REPLICATEfrompending_tx->call()with the comment "A pending tx may fail here if rollback invalidated a reserved signature tx after it was dequeued from pending_txs" - butcommit_reserved()only produced it for a map-level conflict, never for its own side effects.The fix
Store::prepare_reserved_txtakesversion_lockonce and does the whole thing atomically: validate the signature's view and rollback epoch, decide, and record. If the epoch no longer holds it returnsnulloptandcommit_reserved()returnsFAIL_NO_REPLICATE- the pathStore::commit()was already written to expect.The stale comment is replaced with one that says what actually holds.
Why this is minimal
version_lockis not held here, so taking it is safe - and the existing ordercommit_lock->version_lock->chunker_lockis preserved, withLedgerChunkerstill a leaf.Test
Reserved signature side effects are not applied after a rollbackinkv_testcallsStore::prepare_reserved_txdirectly - single-threaded, no harness - covering all three cases:Mutation-verified: disabling the guard leaves exactly the stale
chunk_endsmarker described above.Labelled
run-long-test.Review stack
These five PRs come from one investigation and are stacked; review and merge in order.
All were found by an interleaving-exploration harness built over the real KV, consensus and history stack (draft #8238). The harness itself is deliberately not included here; these PRs carry only the fixes and the single-threaded regression tests that pin them.