Skip to main content

Module liveness

Module liveness 

Source
Expand description

The GC liveness sources over alkgit’s own durable rows (waves 7 and 9): the ref-table closure — every ref row’s full reachable graph walk over the substrate object front — and, under the lfs feature, the media rows source (LfsMedia, substrate.md §GC sources 1 and 2; ADR-019 §6) — both registered as in-process alkblobs::LivenessSources on the composition’s store at the composition entry (crate::compose).

§The walk (the owning discipline)

The live set is the union over all repos’ oid rows of the full graph closure of the row’s tip oid: commits (parents included — the owning walk; gix-fsck’s Connectivity::check_commit does not walk parents, so it is not the walk here), trees (sub-trees and blobs), blobs, and tags (peeled to their target and walked on). Gitlink tree entries (submodule modes) neither mark nor walk: the pinned commit belongs to its other repo, whose own refs cover it. Symref rows (the seeded unborn HEAD) carry no oid — unborn contributes nothing; when the symref’s target is a real row, that row’s own closure covers the object. Keys are assembled map-free (crate::odb::oid_key): [0x03] + oid bytes, the git-object family’s physical encoding.

The walk is best-effort by the seam’s shape (alkblobs::LivenessSource::live_keys returns keys, not errors): a missing or unreadable object stops that branch (the key is absent from the pool or unread — both safe) and the walk continues; the next rebuild retries. A corrupt entry surfaces the front’s verification error at the walk read — the same StorageError::Invalid the serving reads carry.

§The LFS media source (the second alkgit-owned liveness source,

the lfs feature)

LfsMedia (wave 9, ADR-022 §4; substrate.md §GC source 2) serves the LFS media family’s liveness from the pointer-lifecycle rows (crate::lfs::LfsRows). The mark consult is a row read, not a graph walk — media is unstructured content, there is nothing reachable through it — and the keys form map-free: the row’s oid is the digest, so the key is [0x04] + oid bytes (lfs_media_key, the odb front’s oid_key analog for the 32-byte sha256 form; the oid is the raw sha256 of the content, byte-exact the GitObjectSha256 derivation — lfs.md §2, ADR-022 §1). One key with N rows is one live key (rows dedup through the key set — the cross-reference refcount’s mark-side reading; any row re-protects the key, the refcount column’s contract realized).

The invalidation posture is the pinned simple-honest shape: the mark consult rebuilds per mark (the row read is cheap; the refs closure’s fingerprint machinery buy the walk’s cost — nothing to skip here, so nothing to key a memo on). What the arbitration discipline does need is the refs closure’s write-counter half: RefClosure‘s contains consult cannot answer exactly without an epoch signal, and the LFS rows’ dangerous window is a row landing between a sweep’s mark and a staged candidate’s arbitration (the stale index says unreferenced; deleting it eats referenced media). crate::lfs::VersionedLfsRows bumps the shared counter on every assert — the coverage-increasing event, and the only one whose staleness is dangerous: a removal’s stale answers are the safe side (a stale “live” holds a candidate; a stale “dead” is exact for a candidate whose rows left). The same-window answer pattern as the refs source: index hit exactly, cold memo live (the safe default), miss after a moved counter live until the next mark’s rebuild resolves it.

The mark consult bridges with the front’s block_in_place posture (the sqlite/pg rows twins are async — spawn_blocking / native I/O inside); the arbitration consult never bridges. Same floor: the multi-thread runtime (test-pinned). The failed mark consult answers empty with the memo untouched — the same residual class the refs closure documents (the stale memo answers at arbitration; the next mark corrects).

The cross-process posture is the refs closure’s, stated honestly: the in-process source is instance-local (alkblobs ADR-012 §6.6’s allow-and-document reading); the fleet-wide answer is the materialized liveness table (alkblobs::LivenessRegistration::FleetTable — wave-10 fleet assembly’s wiring, not this module’s). The in-flight LFS uploads are protected fleet-wide by the DB-backed pins (alkblobs ADR-008) — the put side is pins’, not this source’s.

§The sync verb / async store bridge

alkblobs::LivenessSource’s verbs are sync (the sweep consults live_keys from its async body, contains under the arbitration lock). The walk bridges with crate::odb::ObjectFront::block_in_place — the odb-front task’s async-context posture (the POC-O panic shape; the blocking half is the pack pipelines’). Consequence: the multi-thread runtime is the floor — a current-thread runtime panics (test-pinned). contains never bridges: it answers from the memo’s in-memory index only (RefClosure::keys_builds proves no consult derives). The walk consumes the front’s async read legs directly (the same verified reads the sync Find legs bridge to) — the walk runs inside the bridge’s future, where the sync legs’ own block_on is illegal (the POC-O panic shape).

§Memoization and the invalidation decision (the honest posture)

POC-O’s shape was the closure memoized by the refs table’s version counter. The wave-6 refs rows carry no version counter, so this task pins the mechanism; the three candidates resolved as two composable half-decisions:

  • The memo’s rebuild key is a durable refs fingerprint (the durable-marker option, realized read-side — no schema change). RefRows::snapshot returns the rows plus a fingerprint over their canonical form; a sweep’s mark consult reads it (one cheap refs-rows read, ordered by repo/name) and rebuilds the walk only when it moved. Because the fingerprint is computed from the durable rows, cross-process ref writes are seen at mark time by construction — the fleet-shape invalidation is the snapshot read, not process-local state.
  • The arbitration window is covered by an in-process write counter (the POC-O shape carried). A push landing between this sweep’s mark and the next sweep’s execution-time arbitration cannot move the fingerprint consult (arbitration runs under the store’s short-held lock — no bridging, no reads). The composition’s refs writes therefore bump a shared in-process counter (crate::refs::VersionedRefTable on the sqlite assembly), and alkblobs::LivenessSource::contains answers conservatively — “live” (unknown) — whenever the counter moved past the memo’s build: the affected candidates cancel and resolve on the next sweep’s mark, which rebuilds against the new rows. Over-invalidation costs one delayed sweep; the alternative deletes a pushed object. A cold (never-built) memo answers “live” too — the safe default until the first mark; direct deletes under it refuse (alkblobs::Error::GcRefuse), the ADR-012 §6.4 deregister-delete-re-register correction flow is the escape hatch, and an index hit always answers exactly.
  • The cross-process posture, stated honestly: the in-process source is instance-local (alkblobs ADR-012 §6.6’s allow-and- document reading — the SQL protocol sees tables, not callbacks). Single-node shapes (the default sqlite assembly) are exact. Fleet deployments carry one residual window: a completed push from another instance between mark and execution-time arbitration (this instance’s counter does not move and the mark has not come yet). The in-flight push itself is protected fleet-wide by the DB-backed pins (alkblobs ADR-008), and the fleet-wide answer is the materialized liveness table (alkblobs::LivenessRegistration::FleetTable — the embedder’s own table the store core reads at SQL arbitration); that materialization is the fleet assembly’s wiring — the wave-8 composition entry is the unifier and the wave-10 pg assembly exercises it (the pg GitRefs twin landed with the wave-7 pg refs task), not this module’s.

§Registration

RefClosure::register registers the source on the store (Store::register_liveness_source, the live-shared seam — consulted live every sweep, never a snapshot at registration). The composition’s construction wires RefClosure::new over the object front, the refs rows seam, and the assembly’s write counter, then registers: zero registered liveness sources make every sweep abort with alkblobs::GcAbortCause::NoLivenessSources — this source is what the wave-7 maintenance chain’s sweep dispatch requires. The wave-8 composition entry unifies the construction; until then the pieces + this wiring are the proof surface (the tests run the full chain, sqlite co-tenancy included).

Structs§

LfsMedia
The LFS-media alkblobs::LivenessSource — the second alkgit-owned liveness source (substrate.md §GC source 2; ADR-022 §4): the pointer-lifecycle rows as the media family’s live set, a mark consult that reads the rows (no graph walk) and an arbitration consult that answers from the mark’s index. See the module docs’ LFS section for the walk-not-walk posture, the invalidation decision, and the cross-process residual.
RefClosure
The ref-table-closure alkblobs::LivenessSource — the first alkgit-owned liveness source (substrate.md §GC; ADR-019 §6). See the module docs for the walk, the bridge, and the invalidation decision.
RefRow
One refs row as the liveness seam reads it: the resolved repo id, the validated ref name, and the row’s state — an oid tip or the symref (unborn) shape. The rows cross RefRows::snapshot as they are, not as alkgit::RefLine listings: liveness walks tips, it does not advertise.
RefRowsSnapshot
A refs-rows snapshot: every row of every repo, in the canonical order (repo id, then name — sorted), plus the fingerprint the memo keys on.

Enums§

RefRowState
A refs row’s state as the CAS tables store it (the refs module’s row shapes, surfaced for the liveness seam).

Traits§

RefRows
The refs rows’ read seam — the durable state the closure walks and fingerprints. Implemented by the refs tables (crate::refs::InMemoryRefTable, the sqlite twin) and forwarded by the composition’s write-counting wrapper (crate::refs::VersionedRefTable). Engine twins carry their own: snapshotting is a plain ordered read of the rows the CAS writes.

Functions§

lfs_media_key
The media family’s key formation from a row’s oid — the map-free half the walk’s oid_key is, sha256-swapped (lfs.md §2; ADR-022 §1: the oid is the digest, byte-exact the GitObjectSha256 derivation): the key is [Algorithm::GitObjectSha256] + oid bytes, decoded through Key::from_bytes over the physical encoding — family-explicit, rejects-never-guesses. A non-64-lowercase-hex oid is StorageError::Invalid (the rows store validates the same form on write; the read-side check is the front’s OQ-05 posture).