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
FastWriter — one 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
appendreturns 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
impl WriterArm
Sourcepub fn durability(self) -> &'static str
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.
Sourcepub fn journal(self, blobs: &Path) -> Option<PathBuf>
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.
Trait Implementations§
impl Copy for WriterArm
impl Eq for WriterArm
impl StructuralPartialEq for WriterArm
Auto Trait Implementations§
impl Freeze for WriterArm
impl RefUnwindSafe for WriterArm
impl Send for WriterArm
impl Sync for WriterArm
impl Unpin for WriterArm
impl UnsafeUnpin for WriterArm
impl UnwindSafe for WriterArm
Blanket Implementations§
impl<T> Allocation for T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.