pub struct RefLog { /* private fields */ }Expand description
The ref log of one repository.
§One pass per push, not two per push and both O(the log)
Until 2026-08-30 a single ref update read the whole log twice: once
through current to compare the caller’s expectation
against the namespace, and once more inside push to re-derive
push_seq. Both are O(pushes), so the cost of landing a ref grew with
every ref that had ever landed — the busier the repository, the slower its
next push, which is the wrong direction for the one number a client waits on.
The log is still the authority and the frame is still the transaction. What
changed is that the fold and the counter are carried forward in memory by
the writer that already holds the gate, instead of being recomputed from
disk by every reader and every writer. One scan happens, lazily, the first
time this handle is asked anything; after that a push is one build, one
append, one fsync and one in-memory apply.
The crash contract is unchanged, because the cache is not a durability
mechanism: it is only ever advanced after PushLog::append has returned,
i.e. after the frame is on the platter, so a crash loses exactly the frames
it lost before and a restarted process rebuilds the cache with the same
single scan recovery has always done. A failed append leaves the cache where
it was.
It assumes one writer per store, which is what gunnar already guarantees
and enforces one level up: GitStore serialises ref work behind a process-
local ref_gate, and two processes over one store were already unsound
before this (that mutex could never have seen the other one). A caller that
mutates the file behind this handle’s back — the recovery tests do — must
re-open the log or call invalidate, exactly as a
restarted process would.
Implementations§
Source§impl RefLog
impl RefLog
pub fn new(path: impl Into<PathBuf>) -> Self
Sourcepub fn invalidate(&self)
pub fn invalidate(&self)
Forget the folded namespace and the counter, so the next question is answered by a fresh scan of the file.
For a caller that changed the log by some route other than this handle.
Nothing on a serving path calls it; the recovery tests do, and so does
append_batch, which hands the sequence out of the
writer’s hands.
Sourcepub fn next_push_seq(&self) -> Result<u64>
pub fn next_push_seq(&self) -> Result<u64>
The next push_seq this log should use: one past the highest already
recorded. Derived from the log the first time it is asked, so a crashed
writer that lost its counter cannot reuse a sequence number and silently
reorder history; carried forward from there.
Sourcepub fn push(&self, updates: &[RefUpdate]) -> Result<u64>
pub fn push(&self, updates: &[RefUpdate]) -> Result<u64>
Append one push atomically, durable before it returns. Returns the
push_seq it was given.
Sourcepub fn push_rebuildable(&self, updates: &[RefUpdate]) -> Result<u64>
pub fn push_rebuildable(&self, updates: &[RefUpdate]) -> Result<u64>
Append one push without its own fsync.
For a frame that carries rebuildable state rather than a durable fact —
gunnar’s HEAD, which is projected from the catalog row on every open of
the store and re-projected if it is missing. The frame is still whole or
absent (the framing gives that, not the barrier), and the very next
durable append to this log fsyncs the same file and takes it with it.
Never for a ref a client was told landed. push is that.
Sourcepub fn append_batch(&self, batch: &RecordBatch) -> Result<u64>
pub fn append_batch(&self, batch: &RecordBatch) -> Result<u64>
Append an already-built push batch, whose push_seq the caller assigned.
The counter is out of this handle’s hands here, so the cache is dropped
rather than advanced — the next reader pays one scan. push
is the path a server takes; this one is for a bench that drives the
append directly.
pub fn scan(&self) -> Result<PushLogScan>
Sourcepub fn scans(&self) -> u64
pub fn scans(&self) -> u64
Full passes over the log file this handle has paid. One, for a handle
that has only ever pushed — see PushLog::scans.
Sourcepub fn compact(&self) -> Result<CompactionReport>
pub fn compact(&self) -> Result<CompactionReport>
Fold every frame into one. See PushLog::compact — rows and their
order are preserved, so fold answers identically before and after.
Sourcepub fn maybe_compact(
&self,
policy: CompactionPolicy,
) -> Result<Option<CompactionReport>>
pub fn maybe_compact( &self, policy: CompactionPolicy, ) -> Result<Option<CompactionReport>>
Compact if the log has grown past policy.
Sourcepub fn current(&self) -> Result<BTreeMap<String, RefState>>
pub fn current(&self) -> Result<BTreeMap<String, RefState>>
The current ref namespace. The whole-snapshot accessor, for the
batch callers — refs, put_refs_cas, the live set — that read every
ref at once and must see them at one instant. head and a single-ref
CAS want one row, not a copy of the namespace; they take get.
Sourcepub fn get(&self, name: &str) -> Result<Option<RefState>>
pub fn get(&self, name: &str) -> Result<Option<RefState>>
One ref by name, without folding a copy of the whole namespace.
O(log r) against current’s O(r): head and a
single-ref compare-and-swap each need exactly one row, and cloning the
entire BTreeMap<String, RefState> to read one key is the cost this
removes — a mirror with thousands of refs paid it on every head, which
a client asks on every connection. The one RefState it returns is
cloned out so no lock is held past the lookup, exactly as current does
for the whole map.
Sourcepub fn seal_section(&self) -> Result<ReservedSection>
pub fn seal_section(&self) -> Result<ReservedSection>
The reserved section to seal into the archive.