Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
// TransactionInProgressError
// ClosedError (I25: db.close() raced a live txn/sp)
// AlreadyFinishedError (I22/I24: double-drive a finished txn/sp)
// TagMismatchError (delete_tagged supplied a tag that does
// not match the handle's stored tag)
// FatalError (drop-and-reopen recovery only)
// IoError (ALSO subclasses builtin OSError — see register)
// ChecksumMismatchError
Expand Down
4 changes: 4 additions & 0 deletions src/membership_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,10 @@ impl MembershipIndex {
inner_root = inner.create_root(cache, alloc)?;
}
let new_inner_root = inner.insert(cache, inner_root, handle, 1, alloc, freed)?;
// Pack the POST-insert `inner.depth`, not the stale `inner_depth` from
// unpack_inner: this insert may have grown the inner tree, bumping its
// depth. Persisting the old depth would make later readers descend the
// wrong number of levels (invisible at depth 0; see inner_grow_roundtrip).
let packed = pack_inner(new_inner_root, inner.depth);
let new_outer_root = outer.insert(cache, root, tag as u64, packed, alloc, freed)?;
self.outer_depth = outer.depth;
Expand Down
5 changes: 5 additions & 0 deletions src/overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ impl Overflow {
});
}
let max_pages = total_length.div_ceil(OVERFLOW_PAYLOAD);
// Ordering matters: the wrong-type and zero-length guards above run
// BEFORE this allocation, so an untrusted `total_length` (e.g. a
// stale handle pointing at a non-overflow page whose bytes 16..24
// read as u64::MAX) can never drive a speculative giant allocation
// here. The per-page loop guard alone would be too late.
let mut result = Vec::with_capacity(total_length);

let mut current_page = first_page;
Expand Down
10 changes: 10 additions & 0 deletions src/page_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ pub struct PageCache {
/// allocation in a write-heavy transaction (where all pages are
/// dirty and no victim exists), trivially making page-allocation
/// O(n) per call. With this counter the early-out is O(1).
///
/// INVARIANT: this counts ONLY dirty entries currently resident in
/// `entries`, never pages that have been spilled to the spillway.
/// That is exactly why the `dirty_count == entries.len()`
/// short-circuit is sound — both sides measure the in-cache set. A
/// Phase-B spill decrements this as it removes the victim from
/// `entries` (and re-increments on a failed spill that restores it),
/// so `dirty_count <= entries.len()` always holds; `forget_above`
/// (truncate's spillway prune) deliberately does NOT touch it,
/// because spilled pages were never counted here.
dirty_count: usize,
/// I52 (ISSUES.md, 2026-05-22): reusable scratch buffer for
/// `flush()` and `discard_all_dirty()`. Both functions iterate
Expand Down
4 changes: 3 additions & 1 deletion src/transaction/fault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
//! - `fail_next_handle_table_op`: companion for the FORWARD step — the next
//! `allocate_inner` handle-table insert returns a non-fatal `CacheFull`, exercising
//! the prepare-abort/unwind path of the step carrying the eager depth bump
//! (HandleTable::grow).
//! (HandleTable::grow). Consumed inside `handle_table_insert_candidate`, which both
//! `allocate_inner` and `update_inner` route through, so either path's forward step
//! can be the one that trips it (the test that arms it controls which fires first).
//! - `fail_next_update_value_write`: for `update_inner` — the next update returns a
//! non-fatal `CacheFull` at the NEW-value-write step (the first fallible step with
//! the fix; pre-fix it landed AFTER the old location was freed), proving the old
Expand Down
Loading