Skip to main content

RefLog

Struct RefLog 

Source
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

Source

pub fn new(path: impl Into<PathBuf>) -> Self

Source

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.

Source

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.

Source

pub fn push(&self, updates: &[RefUpdate]) -> Result<u64>

Append one push atomically, durable before it returns. Returns the push_seq it was given.

Source

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.

Source

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.

Source

pub fn scan(&self) -> Result<PushLogScan>

Source

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.

Source

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.

Source

pub fn maybe_compact( &self, policy: CompactionPolicy, ) -> Result<Option<CompactionReport>>

Compact if the log has grown past policy.

Source

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.

Source

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.

Source

pub fn seal_section(&self) -> Result<ReservedSection>

The reserved section to seal into the archive.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Allocation for T
where T: RefUnwindSafe + Send + Sync,

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more