Serialize ephemeral_savepoint() against the first table-open#1297
Merged
Conversation
ephemeral_savepoint() checks the dirty flag and then registers the savepoint, while TableNamespace::set_dirty() sets the dirty flag and then, if no savepoint exists, disables allocation tracking. Because WriteTransaction is Sync, the two can run on different threads, and their opposite-order accesses to the dirty flag and the savepoint set can interleave: the savepoint call reads dirty == false, set_dirty() then stores dirty, observes no savepoint, and sets allocation tracking to Ignore, and only then does the savepoint call register the savepoint. That leaves a live savepoint with allocation tracking disabled. A later restore_savepoint() relies on that tracking to free the pages allocated since the savepoint; with it Ignored, those pages are leaked and are reclaimed only by a full repair (quick-repair does not recover them). set_dirty() always runs under the tables lock, so take that same lock across the dirty check and savepoint registration in ephemeral_savepoint(). This orders the two operations: either the savepoint is registered before set_dirty() checks for it (tracking stays enabled), or set_dirty() has already set the dirty flag and the savepoint call returns InvalidSavepoint. Assisted-by: Claude Code https://claude.ai/code/session_019rKhoFerkDeoUn1DqpJn3t
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1297 +/- ##
=======================================
Coverage 90.20% 90.21%
=======================================
Files 36 36
Lines 16328 16330 +2
=======================================
+ Hits 14729 14732 +3
+ Misses 1599 1598 -1
🚀 New features to boost your workflow:
|
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.
Summary
WriteTransactionisSync, soephemeral_savepoint()and the first table-open of the sametransaction can run on different threads.
ephemeral_savepoint()checks the dirty flag and thenregisters the savepoint, while
TableNamespace::set_dirty()sets the dirty flag and then, if nosavepoint exists, disables allocation tracking. Their opposite-order accesses to the dirty flag
and the savepoint set can interleave:
ephemeral_savepoint()readsdirty == falseset_dirty()storesdirty = true, observes no savepoint, and sets allocation tracking toIgnoreephemeral_savepoint()registers the savepointThat leaves a live savepoint with allocation tracking disabled.
Impact
A later
restore_savepoint()relies on that tracking to free the pages allocated since thesavepoint; with it
Ignored, those pages are leaked and reclaimed only by a full repair(quick-repair does not recover them). A leak, not corruption. This is the concurrent sibling of
the drop-vs-commit race fixed in #1296.
Fix
set_dirty()always runs under the tables lock, so take that same lock across the dirty check andsavepoint registration in
ephemeral_savepoint(). This orders the two operations: either thesavepoint is registered before
set_dirty()checks for it (tracking stays enabled), orset_dirty()has already set the dirty flag and the savepoint call returnsInvalidSavepoint.Testing
ephemeral_savepoint_racing_first_open_no_leakraces severalsavepoint-creating threads against one table-opening thread across many iterations, then
restores a savepoint;
check_integrity()'s allocator rebuild reports any leaked (allocated butunreachable) page. It reliably fails without the fix and passes with it.
cargo test --all-features,cargo fmt --check, andcargo clippy --all-targets --all-featuresare all clean.🤖 Generated with Claude Code
https://claude.ai/code/session_019rKhoFerkDeoUn1DqpJn3t
Generated by Claude Code