Skip to main content

WriterArm

Enum WriterArm 

Source
pub enum WriterArm {
    Fast,
    Safe,
    Uring,
}
Expand description

Which ArchiveWrite the push path appends through.

The arm changes what append promises and nothing else. All three write the pushed pack’s bytes verbatim, at the same offset, into the same file; a store built on any of them holds byte-identical payload. What differs is how much of the write is durable when append returns, and that is the whole axis.

Variants§

§

Fast

FastWriterone pwrite, no fsync, no journal.

append returns as soon as the bytes are in the kernel’s page cache. Stated plainly, because an operator selecting this is choosing it:

  • A machine crash (power loss, panic, a hard reset) after append returns loses the bytes. Not “may lose” — nothing has told the device about them.
  • There is no journal, so a store on this arm has no durable record that a pack was ever acked. It therefore has no crash recovery: a reopen cannot re-queue an interrupted pack because there is nothing to diff the index against, and pack ordinals restart at 0 (indexer::packs_already_acked).
  • A process crash alone keeps the bytes: page cache survives exit. That is the only crash it survives.

It is here because it is the ceiling the other two are measured against — at 8 KiB it acks in 3.9 µs against SafeWriter’s 132 µs (examples/push_path_bench.rs, oden 2026-08-07) — and because there are real workloads whose durability contract is not git’s: a rebuildable mirror, a bulk import that is re-run on failure, a benchmark. Selecting it is not refused and not warned about. It is the operator’s call and the contract above is what they are choosing.

§

Safe

SafeWriter — blob fsync, then the journal row, then the journal’s fsync. The default, and git’s contract: a push that was acked survives a crash. Four syscalls per append.

§

Uring

UringWriter — the same ordering as Safe, enforced by the kernel through an IOSQE_IO_LINK chain instead of by the caller blocking between four syscalls. One io_uring_enter carrying four linked ops.

MEASURED 2026-08-07 (PLAN §8.3): it did not win — within noise of SafeWriter at every pack size, because the cost is the two device flushes and not the syscall count. Kept as a selectable arm precisely so that finding can be re-run rather than remembered. Linux only.

What this arm costs that the other two do not: pinned memory, and a ceiling on how many stores a process can hold. Its ring registers a staging buffer, and IORING_REGISTER_BUFFERS pins pages against RLIMIT_MEMLOCK — a limit the kernel counts on the user_struct, so it is shared by every process the uid is running. One writer per store means the number of repositories this arm can serve is RLIMIT_MEMLOCK / page, and Fast and Safe have no such ceiling because they register nothing. It was ~107 stores on a stock 8 MiB limit until 2026-08-14, when the registration was cut from 64 KiB to the one page a journal row actually needs; see uring_write’s module docs for the arithmetic, the measurement and the failure it produced in gunnar’s sweep.

It used to differ from Safe in one more way, and that is now closed: UringWriter::create opened its journal with File::create, so a reopen truncated the journal where SafeWriter appends to it — the durability of Safe within a process and the crash recovery of Fast across one. Both durable arms now open the same log the same way, which is what “one journal format, two transports” (LAW 5) always claimed of them.

Implementations§

Source§

impl WriterArm

Source

pub const ALL: [WriterArm; 3]

Every arm, for a bench that sweeps them.

Source

pub fn as_str(self) -> &'static str

The spelling an operator types.

Source

pub fn durability(self) -> &'static str

One line on what this arm’s append promises. Same string the trait’s own ArchiveWrite::durability returns, so a bench row and a config dump cannot disagree.

Source

pub fn journal(self, blobs: &Path) -> Option<PathBuf>

Whether this arm keeps the durable extent log that GitStore::open_with_arms derives §13.12’s indexed bit from.

None is Fast and it is load-bearing rather than cosmetic: with no journal there is no durable record of an ack, so the crash-recovery diff has nothing to run against and a reopen re-queues nothing. Returning the path of a journal this arm does not write would make a reopened store diff against a stale log and re-absorb packs that a different arm acked.

Source

pub fn create(self, blobs: &Path) -> Result<Box<dyn ArchiveWrite>>

Build the writer. blobs is the file the pushed packs are appended to.

Source

pub fn parse(s: &str) -> Result<Self>

Parse the spelling an operator types. Errors name what is accepted.

Trait Implementations§

Source§

impl Clone for WriterArm

Source§

fn clone(&self) -> WriterArm

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for WriterArm

Source§

impl Debug for WriterArm

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for WriterArm

Source§

fn default() -> WriterArm

Returns the “default value” for a type. Read more
Source§

impl Eq for WriterArm

Source§

impl PartialEq for WriterArm

Source§

fn eq(&self, other: &WriterArm) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for WriterArm

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

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

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.