pub struct Store { /* private fields */ }Expand description
The store instance — the public facade above the tier engines (ADR-011 §1’s vocabulary: one running instantiation of the store facade — one engine set per the constructor mode, plus dispatch and GC state; the GC arbitration domain for single-instance topologies).
Engine-blind by construction: the engines are held in crate-internal
enums dispatched statically (see the module’s engine-representation
design point); consumers name modes only. The GC state home
(GcHome, crate-internal) rides as a field — the store instance owns
all GC state (ADR-010 §2), which the gc-protocol task fills.
Implementations§
Source§impl Store
impl Store
Sourcepub fn mem_only() -> Store
pub fn mem_only() -> Store
mem-only mode (ADR-011 §4): the mem engine alone, no dispatch threshold in effect — a testing/embedder-ephemeral posture, never production. Every length routes to the (sole) mem engine; there is no large tier and nothing to reject.
Sourcepub fn kv_only_mem(threshold: u64) -> Store
pub fn kv_only_mem(threshold: u64) -> Store
kv-only mode (ADR-011 §4; ADR-012 §6.2) with the mem kv
engine: no large tier; threshold is the put-time rejection
point — known-length puts over it reject immediately,
unknown-length puts at mid-stream threshold overflow (the put
seam’s behavior; zero residue on either rejection). Routing in
this mode is kv for every length.
Sourcepub async fn dual_tier_mem_local(
root: impl Into<PathBuf>,
threshold: u64,
) -> Result<Store, Error>
pub async fn dual_tier_mem_local( root: impl Into<PathBuf>, threshold: u64, ) -> Result<Store, Error>
dual-tier mode (the default, ADR-011 §4) in the wave-2
constructible shape — kv = mem + large = local, the
dispatch-coverage test shape ADR-012 §6.3 names: both tiers
load-bearing, routed by size-threshold dispatch at threshold
(constructor-tunable; crate::dispatch::DEFAULT_DISPATCH_THRESHOLD
is the shipped default).
Constructing the large engine creates the root directory — fallible I/O, hence a fallible constructor. The mem kv engine cannot fail.
Not fleet-anything: mem is never fleet-valid (ADR-011 §4).
Sourcepub async fn kv_only_sqlite(
database: impl Into<PathBuf>,
threshold: u64,
window_delay: Duration,
) -> Result<Store, Error>
pub async fn kv_only_sqlite( database: impl Into<PathBuf>, threshold: u64, window_delay: Duration, ) -> Result<Store, Error>
kv-only mode (ADR-011 §4; ADR-012 §6.2) with the sqlite kv
engine — the durable single-node kv-only shape: no large tier;
threshold is the put-time rejection point (known-length puts
over it reject immediately, unknown-length puts at mid-stream
threshold overflow — the put seam’s behavior, identical to the
mem kv-only wiring; zero residue on either rejection). Routing
in this mode is kv for every length.
Opening (or creating) the database file is fallible I/O; the
constructor also runs the GC schema’s engine-neutral call
(ensure_gc_schema — sqlite’s body creates gc_window only,
ADR-012 §5), so a construction failure is Err(Error::Io) per
the fallible-constructor pattern dual_tier_mem_local set.
window_delay (ADR-008 §2) is the durable window’s cadence
knob — DEFAULT_DELETE_WINDOW_DELAY is the shipped default,
Duration::ZERO the legal degenerate tuning. Not fleet-
anything: a sqlite fleet is a recorded non-option (ADR-010 §3).
Sourcepub async fn dual_tier_sqlite_local(
database: impl Into<PathBuf>,
root: impl Into<PathBuf>,
threshold: u64,
window_delay: Duration,
) -> Result<Store, Error>
pub async fn dual_tier_sqlite_local( database: impl Into<PathBuf>, root: impl Into<PathBuf>, threshold: u64, window_delay: Duration, ) -> Result<Store, Error>
dual-tier mode (the default, ADR-011 §4) in the default-build
production shape — kv = sqlite + large = local (feature
sqlite is default-on, ADR-013 §4): size-threshold dispatch at
threshold (constructor-tunable;
crate::dispatch::DEFAULT_DISPATCH_THRESHOLD is the shipped
default), at-or-below on the kv tier, over-threshold staged into
the large tier.
Fallible on two accounts (file I/O): opening/creating the pool
database — which also runs the GC schema’s engine-neutral call
(ensure_gc_schema; sqlite’s body creates gc_window only,
ADR-012 §5’s one-window-host rule: the durable window is the kv
engine’s when the kv engine is SQL-backed) — and creating the
local root. A construction failure is Err(Error::Io) per the
fallible-constructor pattern dual_tier_mem_local set.
window_delay (ADR-008 §2) is the durable window’s cadence
knob — DEFAULT_DELETE_WINDOW_DELAY is the shipped default,
Duration::ZERO the legal degenerate tuning. Not fleet-
anything: a sqlite fleet is a recorded non-option (ADR-010 §3).
Source§impl Store
The write path (store-api.md §Write path): put_as — the two
internal paths behind one signature, the Batch batching seam, and
the kv-only rejection point (ADR-012 §6.2), on the pin-before-publish
ordering (ADR-005). The named design points (stream shape, len
mismatch, kv-only carrier, key-at-commit amendment) are recorded in
the module doc; their behaviors are pinned by the tests below.
impl Store
The write path (store-api.md §Write path): put_as — the two
internal paths behind one signature, the Batch batching seam, and
the kv-only rejection point (ADR-012 §6.2), on the pin-before-publish
ordering (ADR-005). The named design points (stream shape, len
mismatch, kv-only carrier, key-at-commit amendment) are recorded in
the module doc; their behaviors are pinned by the tests below.
Sourcepub async fn put_as(
&self,
algorithm: Algorithm,
len: Option<u64>,
stream: impl AsyncRead + Unpin + Send,
) -> Result<(Key, Pin), Error>
pub async fn put_as( &self, algorithm: Algorithm, len: Option<u64>, stream: impl AsyncRead + Unpin + Send, ) -> Result<(Key, Pin), Error>
put_as(algorithm, len, stream) → (Key, Pin) — the one
derivation-explicit put seam (store-api.md §Write path;
ADR-020 §Decision 5): the caller names the derivation. The
same bytes have multiple equally valid addresses, one per
registry member, so the crate never picks a derivation for
its caller — the caller that cannot name an algorithm cannot
form an address (the ADR-020 §Decision 1 invariant). Every
registered algorithm writes through this one signature: no
matches! restriction, no canonical default, no family-named
sibling method. Two arms behind one signature:
Known-length (Some(len)) — the encouraged path (covers
git objects, stat-derived files, Content-Length-bearing
uploads, network offers): one pass, no staging needed for
hashing — the preamble ("blob <len>\0") initializes the
derivation before content flows. Preamble-carrying members
(the blob family) hash preamble-then-content; identity-family
members (Algorithm::GitObjectSha1/Algorithm::GitObjectSha256,
ADR-018) take the preimage verbatim — git’s loose-object form,
the caller provides "<type> <len>\0" + content — with len
as the stored-length check; the derivation is the plain hash
over the preimage, so the resulting key’s digest is the
object’s git oid, byte-identical, every kind included.
Verification-on-get re-derives with the key’s algorithm
byte-identically. ≤ the threshold → the kv tier’s whole-value
put; > the threshold → the large tier’s staged put (content
streams into the staging cursor while hashing; the derivation
completes at stream end; commit-rename publishes). A stream
that yields fewer or more bytes than the declared len
rejects with Error::Verification — zero residue either side
(the module doc’s named design point).
Unknown-length (None) — retained on the facade (ADR-020
§Decision 3: “stream in, get the key back” is a legitimate
in-process posture for edge producers, and the hard part is
the crash-safe staging hygiene every outer-layer workaround
re-implements): content buffers in memory up to the dispatch
threshold, ADR-003’s option (d) semantics pinned in the three
numbered rules:
- Mid-stream threshold overflow (non-restartable sources are normal): the buffered prefix flushes into a fresh stage file and the stream continues appending into it — there is no “restart the stream” requirement anywhere.
- Verification runs over the eventual stored artifact as a
whole: the memory buffer for the sub-threshold kv commit;
the complete staged bytes for the large commit — the
derivation pass restarts over preamble + staged bytes for
preamble-carrying members (the preamble is inside the
hashed input, ADR-002); identity-family members restart over
the staged bytes — the permitted simple implementation,
single-pass capable if ever measured worth it. Reading the
staged artifact back goes through the staging cursor’s
ReadCursorseam. - Stream error before commit: no entry lands and no stage file remains — buffer discard on the failure path, exactly like stage discard (the stage-hygiene invariant extended to the buffered case).
Error mapping is uniform across both arms and all algorithms:
length mismatch Error::Verification (zero residue), media
failure Error::Io.
Pin-before-publish (ADR-005): the pin refcount is minted
under the arbitration lock before the entry becomes visible —
an entry cannot appear in a sweep’s list() without already
carrying its pin. A dedup re-put acquires no new pool entry; the
returned Pin refcounts the existing one under the same lock.
kv-only mode (ADR-012 §6.2): a known-length put over the threshold rejects immediately; an unknown-length put rejects at mid-stream threshold overflow — lengths are not known at construction, so put time is the only possible rejection point. Zero residue on either rejection.
Fleet puts (ADR-012 §3/§4; ADR-018 §Decision 7): under a
fleet: true declaration the same signature routes the fleet
forms — the kv tier through the joint entry+pin tx (the
joint-tx driver’s named design points, the termination design
included, are recorded on this module’s fleet impl block), the
pg-lo tier through the one-tx consolidation form, the local tier
pin-first/publish-second; the named derivation threads through
them (derivation-parameterized sites, not family-branching
copies), and the returned guard carries the pin row’s TTL/3
renewal duty. Under fleet: false (every pre-existing
constructor) this doc applies verbatim and nothing of the fleet
machinery runs.
stat semantic (ADR-018 §Decision 3): for a family entry,
stat’s EntryMeta.len is the length of the stored value —
the loose-object form, git’s header included — not the content
length. Deliberate and per-family; see store-api.md §Read path.
Sourcepub fn batch(&self) -> Batch
pub fn batch(&self) -> Batch
The batch-scoped pin group (store-api.md §Write path; ADR-005
§Marks 2). Multi-put writes — manifest writes are exactly this —
group under one batch-scoped pin: Store::put_in_batch runs
each member’s lone-put flow and contributes its pin to the
group; the group’s pins drop together on Batch::commit or
drop (no early single-pin release; member liveness never
outlives the batch — batch drop is the failure path).
Sourcepub async fn put_in_batch(
&self,
batch: &mut Batch,
algorithm: Algorithm,
len: Option<u64>,
stream: impl AsyncRead + Unpin + Send,
) -> Result<Key, Error>
pub async fn put_in_batch( &self, batch: &mut Batch, algorithm: Algorithm, len: Option<u64>, stream: impl AsyncRead + Unpin + Send, ) -> Result<Key, Error>
A member put inside a Batch — the batching put seam
(store-api.md §Write path). Member puts publish pin-before-
publish exactly as lone puts do: the two paths behind
Store::put_as’s one signature run, and the minted guard is
contributed to the group instead of returned; the algorithm is
named like every other write (ADR-020 §Decision 5). The batch’s
pins drop together on Batch::commit or batch drop.
Foreign-Batch refusal (review-001 N-5): the batch must have
been minted by this store — the seam opens with a
debug_assert!(Arc::ptr_eq) on the batch’s registry Arc against
the store’s, before any pin is minted. The debug-assert point is
the recorded choice over a named error: the mis-use is
developer-error territory, and the error family is ADR-pinned
(ADR-012 §6.5 — new variants are new ADRs). Release builds run
on: a foreign batch’s members would carry their pins in the
other store’s in-process registry, where the group release is a
silently no-op — the leak the assert’s window exists to catch.
Source§impl Store
The read path (store-api.md §Read path): get, stat,
read_range, has, list — every key-addressed op falling through
kv first, then large (ADR-012 §6.1’s general rule), typed key
conversion at the facade boundary, and the module-doc design points
(the Blob stream shape, verification-on-get, the Vec<Key> list
shape) carried in their implementations.
impl Store
The read path (store-api.md §Read path): get, stat,
read_range, has, list — every key-addressed op falling through
kv first, then large (ADR-012 §6.1’s general rule), typed key
conversion at the facade boundary, and the module-doc design points
(the Blob stream shape, verification-on-get, the Vec<Key> list
shape) carried in their implementations.
Sourcepub async fn get(&self, key: &[u8]) -> Result<Option<(u64, Blob)>, Error>
pub async fn get(&self, key: &[u8]) -> Result<Option<(u64, Blob)>, Error>
get(key) → Option<(len, stream)>. Falls through kv → large;
a both-tier miss is Ok(None) — never Error::Missing
(ADR-012 §6.5 ruling 5). On a kv hit the materialized read is
whole-blob-verified against the key at get time (mismatch:
Err(Error::Verification), never a further fall-through) and
the returned length is the verified materialized byte length;
the large tier’s hit opens streaming (metadata + handle, no
canonical re-derivation — see the module doc), its length the
cursor’s metadata-only probe. Both hits return the same
(len, stream) shape: the stream is Blob over the tier’s
cursor body.
Sourcepub async fn stat(&self, key: &[u8]) -> Result<Option<EntryMeta>, Error>
pub async fn stat(&self, key: &[u8]) -> Result<Option<EntryMeta>, Error>
stat(key) → Option<EntryMeta> — the cheap probe riding the
trait’s size method (ADR-008): length plus the key’s hash
algorithm, never a content load, falling through kv → large.
A both-tier miss is Ok(None).
Sourcepub async fn read_range(
&self,
key: &[u8],
offset: u64,
len: u64,
) -> Result<(Vec<u8>, [u8; 32]), Error>
pub async fn read_range( &self, key: &[u8], offset: u64, len: u64, ) -> Result<(Vec<u8>, [u8; 32]), Error>
read_range(key, offset, len) → (slice, slice_digest) — the
slice plus the out-of-band SHA-256 slice digest (ADR-006, fixed
by convention), composed through the cursor’s read_range seam.
Falls through kv → large exactly as Store::get does; no
canonical verification rides a range (the slice digest is the
whole answer, ADR-006 decision 2). A both-tier miss is
Err(Error::Missing) — the pinned shape difference vs get’s
None (ADR-012 §6.5 ruling 5).
Sourcepub async fn has(&self, key: &[u8]) -> Result<bool, Error>
pub async fn has(&self, key: &[u8]) -> Result<bool, Error>
has(key) → bool — the pure existence probe (ADR-008’s has
discipline: an existence probe per tier, local’s body being
the literal size-probe), falling through kv → large. Absent on
both tiers: false. Virgin-store reads are no-ops.
Sourcepub async fn list(&self) -> Result<Vec<Key>, Error>
pub async fn list(&self) -> Result<Vec<Key>, Error>
list() → Vec<Key> — whole-pool enumeration of every tier,
complete by contract (ADR-003), returned materialized and
sorted in the keys’ total order (the recorded Vec-vs-stream
design point; see the module doc). Malformed pool bytes are
fatal: Err(Error::KeyInvalid), never a silent skip.
Degenerate duplicates are not deduped on the public form: a key resident in both tiers (reachable only through the staged shapes that write a second home directly) lists once per home — the sweep’s mark side dedups its candidate set, the public enumeration simply reflects each engine’s contents.
Source§impl Store
The GC protocol’s facade surface (ADR-005; gc-and-namespaces.md;
store-api.md §Lifecycle, §Invariants 4/5): the liveness
registration verbs, the direct delete with its arbitration-then-
delete refusal, and the sweep() delete-window protocol. This is
the architecture’s most intricate state machine in its single-node
form — the fleet extension (DB-backed pins, sweep lease, SQL
arbitration) extends it without changing its invariants.
impl Store
The GC protocol’s facade surface (ADR-005; gc-and-namespaces.md;
store-api.md §Lifecycle, §Invariants 4/5): the liveness
registration verbs, the direct delete with its arbitration-then-
delete refusal, and the sweep() delete-window protocol. This is
the architecture’s most intricate state machine in its single-node
form — the fleet extension (DB-backed pins, sweep lease, SQL
arbitration) extends it without changing its invariants.
The state machine, one instance at a time. All shared GC state
rides the crate-internal GcHome behind the crate-internal
Store::gc accessor: one arbitration lock
over per-key pin refcounts, in-flight delete markers, and the
registered source lists (crate::gc’s module doc carries the
named design points — the marker mechanism that closes the
sweep-vs-put race without engine I/O under the lock, and the
single-node sweep lease).
Abort-as-data (ADR-012 §1): every sweep returns
Ok(SweepReport); aborts are GcAbortCause values in the report,
never errors. Error::GcRefuse is the direct-delete refusal alone,
decided here at the facade — it never fires below the facade (the
error module’s altitude discipline). The full protection set
(ADR-012 §6.4): pinned, re-observed live via a registered source,
or named by a protect callback.
Sourcepub fn register_liveness_source(
&self,
source: LivenessRegistration,
) -> Result<Registration, Error>
pub fn register_liveness_source( &self, source: LivenessRegistration, ) -> Result<Registration, Error>
Register a LivenessRegistration — the live-shared GC seam
(ADR-005 §Marks 1; register_liveness_source verbatim; POC #1
finding 7’s finding is the verb’s pinning reason). ADR-012
§6.6’s fleet-shaped argument: the same verb carries both
forms —
LivenessRegistration::InProcess— the original form (anArc<dyn LivenessSource>); consulted live at mark time and at delete-time arbitration, never a snapshot at registration. Legal under every topology; underfleet: trueits protection is instance-local only (the allow-and- document posture: the in-process callback view is not a fleet liveness form — ADR-008 §2 — so this instance’s sweeps and direct deletes see it, other instances’ SQL arbitration does not; the embedder’s fleet-wide protection materializes into its liveness table or pin rows before the fleet’s other instances sweep).LivenessRegistration::FleetTable— the fleet form: a named (schema-qualified) table reference registered into the shared engine, read by the delete-time SQL arbitration and never owned by the store (the schema-ownership rule — the engine never creates the table). Malformed references error here; re-registering the same reference is idempotent. Only legal on a fleet store — the verb errors underfleet: false(the fleet machinery is dormant there by declaration, ADR-012 §3).
The verb returns Result<Registration, Error>: the fleet form
validates the reference and forwards to the shared engine (both
fallible); the in-process form never errors in practice — the
pre-fleet signature’s infallibility was the in-process form’s
accident, retired by the unified surface (the recorded
verb-shape design point). The returned Registration token
deregisters via Store::unregister_liveness_source (ADR-012
§6.4’s correction flow). Zero registered liveness
registrations — in-process sources and fleet tables counted
together — make every sweep abort with
GcAbortCause::NoLivenessSources — the safe default.
The protect callback’s fleet posture (recorded here where the
liveness verbs live): ProtectCallback is single-node-only
(ADR-008 §2) — a fleet sweeper cannot consult another node’s
in-process callback. A registered callback remains consultable
by this instance’s sweeps (its protection is instance-local),
and the embedder materializes its fleet-wide protect set into
its liveness table (or pin rows) before invoking sweep; no
new machinery, the posture only.
Sourcepub fn unregister_liveness_source(&self, registration: Registration) -> bool
pub fn unregister_liveness_source(&self, registration: Registration) -> bool
Deregister a liveness registration previously registered on
this store — the in-process sources and the fleet tables’
correction flow (ADR-012 §6.4): a fleet-table token removes the
store-side record and the engine-side reference (the SQL
arbitration stops consulting it). false when the token was
already consumed, was never a liveness token (a protect token),
or — fleet form — the reference was not registered.
Sourcepub fn register_protect_callback(
&self,
callback: Arc<dyn ProtectCallback>,
) -> Registration
pub fn register_protect_callback( &self, callback: Arc<dyn ProtectCallback>, ) -> Registration
Register a ProtectCallback — ADR-005 §Marks 3’s protection
source. Consulted pre-sweep (it may add externally-known hashes
or fail the sweep into GcAbortCause::ProtectFailed report
data) and at direct-delete arbitration, where a
protect-callback-named key is refused (Error::GcRefuse, ADR-012
§6.4’s full protection set). Registration replaces nothing and
composes: each callback’s set unions into the protection.
Sourcepub fn unregister_protect_callback(&self, registration: Registration) -> bool
pub fn unregister_protect_callback(&self, registration: Registration) -> bool
Deregister a protect callback previously registered on this
store. false when the token was already consumed or never
came from Store::register_protect_callback.
Sourcepub async fn delete(&self, key: &[u8]) -> Result<(), Error>
pub async fn delete(&self, key: &[u8]) -> Result<(), Error>
Direct delete(key) — fall-through consult (kv first, then
large; ADR-012 §6.4’s “arbitration-then-delete where the entry
lives”), then the full protection set arbitration at delete
time: a key that is pinned, re-observed live via a registered
source, or named by a protect callback is refused with
Error::GcRefuse — nothing deleted. Unprotected keys delete
(across both homes when a degenerate pool holds the key twice);
absent keys carry Error::Missing; malformed key bytes
Error::KeyInvalid. A failed protect callback is refused
conservatively (GcRefuse, nothing deleted) — the direct-path
reading of abort-as-data’s safe side, recorded in the facade
module doc.
Outside-of-sweep deletion is unusual by posture — most deletion flows through sweeps (ADR-005); an embedder’s correction flow deregisters/re-registers around the delete (ADR-012 §6.4).
Sourcepub async fn sweep(&self) -> Result<SweepReport, Error>
pub async fn sweep(&self) -> Result<SweepReport, Error>
sweep() — the explicit mark-and-sweep with the delete
window (ADR-005 §Sweep), in two forms under one signature: the
durable form runs on the delete window’s SQL-backed host
(ADR-012 §5’s one-window-host rule — the SQL-backed kv engine
when it is SQL-backed: sqlite and postgres; the pg-lo large
host otherwise: kv = mem + large = pg-lo; the branch keys off
the engine representation, never a flag), and the
inline form (ADR-012 §5: local/mem stage candidates
in-process and execute the window protocol inline in the same
sweep) runs verbatim elsewhere. Sweeps are never errors:
every abort is report data in SweepReport.aborted
(Ok(SweepReport)), and a completed sweep’s counts are exact
relative to its form’s schedule — the inline form reports
candidates_staged == deleted + cancelled_by_arbitration over its
same-sweep staged set; the durable form reports
candidates_staged as the candidates staged this sweep and
deleted/cancelled as the overdue set executed this sweep
(staged and executed are different sets there — the delay
separates them, ADR-012 §6.7; deleted/cancelled are both 0 on
any abort).
The protocol, as run here:
- Single-flight: the in-process sweep lease (ADR-013 §2’s
single-node analog); a contended second sweeper aborts with
GcAbortCause::SweeperLock, nothing deleted, nothing staged or executed. Underfleet: truethe SQL lease composes — both held for the whole sweep (sweep_under_leases, ADR-013 §2’s named exclusive semantics at the fleet host: atomic test-and-set, one sweeper per pool across instances, holdership ends at release, owner death, or engine-session loss; contention — at either domain — is theSweeperLockreport abort with nothing deleted). Both releases ride their Drop guards (review-001 N-6): a panic or cancellation inside the sweep body releases the lease(s), never a stuck single-flight state. - Zero registered liveness sources aborts with
GcAbortCause::NoLivenessSourcesbefore anything is marked — the safe default (nothing deleted). - Protect: each registered
ProtectCallbackis consulted pre-sweep; its keys union into the live set; a callback that fails to answer aborts withGcAbortCause::ProtectFailed— a flaky protection source skips the sweep rather than risk deletion (iroh’sProtectOutcome::Abortconclusion). On the durable form this consult’s coverage extends to the executor phase, so an abort here precedes any overdue execution too. - Durable form (SQL-backed kv engine), per sweep under the
lease: execute overdue first (the bounded
WINDOW_TAKE_BATCHtake looped until drained —window_takere-arbitrates each overdue candidate at execution time — pin-before-publish during the window delay cancels the candidate — and deletes), then mark (unchanged), then stage the fresh candidate-dead set atvisible_after = mark_end + window(the constructor-tunable delay, ADR-008 §2). The executor of the staged rows is the next sweep (ADR-008 §2; ADR-012 §6.7). Inline form: the mark phase enumerates the whole pool (both tiers’ completelist(), ADR-003 — a malformed pool list is fatalErr(KeyInvalid)); the live set computed from all three sources (registered sources, pins, protect callbacks); the candidate-dead set staged (sorted, in-process). - Delete window: per candidate, arbitration at delete
time (not once upfront) under the arbitration lock — pinned
or re-observed-live cancels the candidate (counted in
cancelled_by_arbitration); otherwise the candidate deletes (counted indeleted) — on the durable form over the bounded take’s drained batches. Underfleet: truethe SQL arbitration composes behind the in-process domain (arbitrated_delete’s recorded two-domain composition: in-process first — cheap, local; SQL second — fleet-authoritative), on the direct-delete path and at execution time alike; a foreign instance’s un-expired pin row or a liveness-table row cancels the candidate.
The fleet sweep body (under fleet: true —
sweep_window_fleet, the store-fleet-sweep shape,
ADR-008 §2’s mechanism as ADR-010 §2/ADR-012 shaped it): the
protect consult (instance-local, ProtectFailed abort precedes
everything in the body) → the maintenance step
(pin_reap: the expired pin rows are reaped — ADR-008 §2’s
GC-of-GC cleanup, before the overdue execution; the ordering
recorded there) → execute overdue (window_take on the kv
host — every fleet-valid configuration’s window hosts there,
the one-window-host rule — each candidate re-arbitrated at
execution time through the two-domain composition and deleted
where it lives, the fall-through across both tiers) → mark
(the pool enumerated; the live set from the in-process sources
— the SQL liveness tables are consulted at arbitration time,
never enumerated into the mark set) → stage at
visible_after = mark_end + window → release the lease. Under
fleet: false every form above is byte-unchanged.
The test-asserted property (ADR-005): a visible pool entry is
never deleted while liveness protects it, and an in-flight put
is never deleted by a sweep started before it committed — the
in-flight delete markers plus the blocked pin mint are the
mechanism (crate::gc’s module doc), and the concurrency
tests below assert it (the durable form’s race holds across the
window delay — a put landing during the delay pins in-process
and the execution-time re-arbitration cancels the candidate).
The single-flight, two-domain (ADR-013 §2; the fleet
extension): the in-process flag composes with the SQL lease
under fleet: true — both held for the whole sweep, the
instance-local guard staying cheap and uniform beside the
fleet-authoritative lease. The contended second sweeper aborts
with GcAbortCause::SweeperLock at either domain, nothing
deleted, nothing staged or executed.
The lease’s hold across the sweep body (named design point):
the SQL lease holds one pooled checkout for the sweep’s
duration (the session-scoped advisory lock’s same-session
acquire/release discipline), and the sweep body’s other engine
clauses — the maintenance step, window_take, the mark’s list
enumeration, window_stage — ride further checkouts under
the held lease. The pool therefore must size ≥ 2 for a fleet
sweep not to acquire-wait against itself: the fleet constructors
enforce max_size >= 2 at entry, and the shipped default (8)
provides headroom (a sequential sweep body holds exactly two
concurrent checkouts — the lease’s and the current clause’s).