pub struct SnapshotManager { /* private fields */ }Expand description
Central allocator and liveness tracker.
Uses an atomic counter for xid allocation and a parking_lot-guarded HashSet for in-progress/aborted bookkeeping. The sets stay small — only unfinished transactions plus a finite rollback history — so a plain HashSet outperforms more complex data structures here.
§Autocommit xid pool
Single-row autocommit writes (MutationEngine::append_one) need an
xid that’s “born committed” — they call begin() then commit()
back-to-back before the row is even durable. The pre-commit pool
(autocommit_pool_*) batches the reservation: one
next_xid.fetch_add(BATCH) reserves a contiguous range of xids,
each handed out via a single atomic without touching the
RwLock<ManagerState>. Pool xids are never inserted into active
or aborted so they look like already-committed transactions to
every snapshot — identical visibility semantics to the legacy
begin()/commit() pair (which also leaves the xid in neither set).
Implementations§
Source§impl SnapshotManager
impl SnapshotManager
pub fn new() -> Self
Sourcepub fn begin(&self) -> Xid
pub fn begin(&self) -> Xid
Allocate a new xid and mark it active. Returns the xid for
stamping onto UnifiedEntity::xmin/xmax.
Allocation and the active insert happen together under the state
write lock. This is load-bearing for first-committer-wins: a
concurrent snapshot (state read lock) must never observe a
lower-numbered still-running xid as absent from active. Were the
fetch_add done outside the lock, a writer that grabbed the smaller
xid could still be mid-begin() while a later transaction snapshots
a larger xid — the smaller xid would be missing from in_progress,
yet xmin <= snapshot.xid, so the snapshot would treat that
concurrent (later possibly-committed) writer as visible. That both
leaks an uncommitted row into a peer’s read and lets two writers on
the same row both commit. Serialising allocation under the lock
guarantees any xid smaller than a taken snapshot’s xid is already in
active (or already finished), so in_progress is never short a
concurrent writer.
Sourcepub fn fresh_read_snapshot(&self) -> Snapshot
pub fn fresh_read_snapshot(&self) -> Snapshot
Capture a fresh read-committed snapshot at this instant: sees every
transaction that has committed so far and hides every still-active
one. Unlike [snapshot], the caller supplies no xid — the snapshot
id is the current allocator high-water mark, read under the same
state lock that [begin] holds while it bumps the counter and
inserts into active. That mutual exclusion is what makes the read
atomic: a concurrent begin() is either wholly visible (counter
bumped AND present in active, hence hidden as in-progress) or
wholly invisible (neither) — never the torn state where its xid sits
below the high-water mark yet is missing from active, which would
leak an uncommitted writer as visible.
Used for RMW pre-image reads that must always observe the latest committed row version rather than a pinned statement snapshot.
Sourcepub fn fresh_retained_snapshot(
self: &Arc<Self>,
) -> (Snapshot, SnapshotRetentionGuard)
pub fn fresh_retained_snapshot( self: &Arc<Self>, ) -> (Snapshot, SnapshotRetentionGuard)
Capture and register under the same lock used by GC cutoff selection. Registering only when the snapshot is installed leaves a capture-to-read interval in which a committed writer’s old versions can be reclaimed.
Sourcepub fn retain_snapshot(
self: &Arc<Self>,
snapshot: Snapshot,
) -> (Snapshot, SnapshotRetentionGuard)
pub fn retain_snapshot( self: &Arc<Self>, snapshot: Snapshot, ) -> (Snapshot, SnapshotRetentionGuard)
Retain a snapshot already protected by its parent transaction, outer statement frame, or historical reference while transferring ownership.
Sourcepub fn snapshot(&self, xid: Xid) -> Snapshot
pub fn snapshot(&self, xid: Xid) -> Snapshot
Capture a point-in-time snapshot. Must be called after begin()
when using SnapshotIsolation/Serializable. ReadCommitted refreshes
this per statement via the same call.
Sourcepub fn commit(&self, xid: Xid)
pub fn commit(&self, xid: Xid)
Mark a transaction as committed. Its writes become visible to future snapshots; earlier snapshots keep their own view.
Sourcepub fn allocate_committed_xid(&self) -> Xid
pub fn allocate_committed_xid(&self) -> Xid
Allocate an xid that is born committed — for autocommit
callers (MutationEngine::append_one) that previously paid two
state.write() lock acquisitions per row to insert-then-remove
from the active set.
The returned xid is never inserted into active and never into
aborted, which matches the steady state of the legacy
begin()/commit() pair when called back-to-back: the xid leaves
the manager’s tracking sets unobservably. Concurrent readers
therefore see it as an already-committed transaction once
xmin <= snapshot.xid, which is exactly the semantics the
autocommit path needs.
Implementation: a small reservation pool (AUTOCOMMIT_POOL_BATCH
xids) is reserved with one fetch_add. Each caller hands itself
the next xid via a single atomic. When the pool drains, the
next caller serialises briefly through autocommit_pool_refill
to bump the window, then falls back into the lock-free hot path.
Durability note: this method does NOT make the row durable —
it only allocates the identifier. The caller must complete the
usual WAL-append + fsync cycle before acknowledging the write.
Pre-allocating the xid is safe because the xid carries no
promise that any row exists; it’s just a number for xmin.
Sourcepub fn rollback(&self, xid: Xid)
pub fn rollback(&self, xid: Xid)
Mark a transaction as rolled back. Its writes MUST stay hidden
from every future read — is_visible consults the aborted set
before honouring a row’s xmin.
pub fn begin_serializable(&self, xid: Xid)
pub fn record_serializable_read( &self, reader: Xid, collection: &str, logical_id: EntityId, )
pub fn record_serializable_vault_read( &self, reader: Xid, collection: &str, tenant: Option<&str>, key: Option<&str>, )
pub fn serializable_vault_commit_would_be_dangerous( &self, writer: Xid, write_set: &HashSet<(String, Option<String>, String)>, ) -> bool
pub fn serializable_commit_would_be_dangerous( &self, writer: Xid, write_set: &HashSet<LogicalRowKey>, ) -> bool
Sourcepub fn is_aborted(&self, xid: Xid) -> bool
pub fn is_aborted(&self, xid: Xid) -> bool
Is this xid known to have rolled back? Called by the read path to skip tuples whose creator never committed.
Sourcepub fn checkpoint_excluded_state(&self) -> (u64, HashSet<Xid>)
pub fn checkpoint_excluded_state(&self) -> (u64, HashSet<Xid>)
Unfinished writers must be rolled back in a crash-recoverable checkpoint, without changing their live transaction state or their readers.
pub fn checkpoint_projection_state(&self) -> (u64, HashSet<Xid>, u64)
Sourcepub fn row_reserves_unique_key(
&self,
xmin: Xid,
xmax: Xid,
own_xids: &HashSet<Xid>,
) -> bool
pub fn row_reserves_unique_key( &self, xmin: Xid, xmax: Xid, own_xids: &HashSet<Xid>, ) -> bool
Unique-key admission uses current transaction outcomes, not the reader’s snapshot. An invisible active/newly committed writer still reserves its key; an aborted creator or completed replacement does not.
Sourcepub fn oldest_active_xid(&self) -> Option<Xid>
pub fn oldest_active_xid(&self) -> Option<Xid>
Snapshot of every still-active xid (for VACUUM oldest-active-xid
calculation — any row with xmax < min(active) is reclaimable).
Sourcepub fn oldest_required_xid(&self) -> Option<Xid>
pub fn oldest_required_xid(&self) -> Option<Xid>
GC must preserve writers excluded by an active reader’s snapshot, even when those writers have since committed and left the active set.
Sourcepub fn oldest_pinned_xid(&self) -> Option<Xid>
pub fn oldest_pinned_xid(&self) -> Option<Xid>
Oldest externally pinned xid. Pinned snapshots behave like active snapshots for VACUUM: any tuple visible to that xid must survive even when no SQL transaction is currently active.
Sourcepub fn peek_next_xid(&self) -> Xid
pub fn peek_next_xid(&self) -> Xid
Return the next xid that would be allocated. Useful for diagnostics and for VACUUM to know the upper bound of aborted-xid retention.
Sourcepub fn observe_committed_xid(&self, xid: Xid)
pub fn observe_committed_xid(&self, xid: Xid)
Advance the allocator so future snapshots consider an xid recovered from storage/WAL to be in the committed past.
Sourcepub fn prune_aborted(&self, below: Xid)
pub fn prune_aborted(&self, below: Xid)
Prune outcomes only after the caller has removed or normalized every physical version that references them across all collections and models. A snapshot cutoff alone is insufficient: an aborted insert with xmin=x becomes visible again if x is forgotten while that version still exists. Pinned xids remain retained for higher-level references.
Sourcepub fn pin(&self, xid: Xid)
pub fn pin(&self, xid: Xid)
Pin an xid so its row versions stay reclaim-safe across VACUUM.
Reference-counted — call unpin once per pin to release.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for SnapshotManager
impl !RefUnwindSafe for SnapshotManager
impl Send for SnapshotManager
impl Sync for SnapshotManager
impl Unpin for SnapshotManager
impl UnsafeUnpin for SnapshotManager
impl UnwindSafe for SnapshotManager
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more