Skip to main content

Module shared_ring

Module shared_ring 

Source
Expand description

A breadcrumb ring in shared memory, keyed to the artifact instead of the process.

§Why a second ring exists

The in-process flight recorder (crate::breadcrumbs) can only ever show what this process did. But the corruption that motivated this crate is caused by one process’s writes and detected by a different process’s open — so the detecting process’s trail is, structurally, the wrong trail. It shows the reopen that noticed the damage and nothing about the writes that did it.

A SharedRing is a fixed-size ring buffer in a memory-mapped file that lives beside the artifact (the store), so every process that touches that store appends to the same trail. A corruption report can then carry the last writes from whichever process made them.

§Crash safety

Every writer that touches this file is, by assumption, a process that may be killed mid-write — that is the entire scenario. So the ring uses no locks that could be left held:

  • A writer claims a slot with one atomic fetch_add on a shared ticket counter. No lock, so nothing to leak.
  • Each slot is a seqlock: the sequence number is set odd before the body is written and even after. A reader that sees an odd sequence — or a sequence that changed while it was reading — skips that slot.
  • A process dying mid-slot therefore costs exactly one breadcrumb. It cannot wedge, poison, or corrupt the ring for anyone else.

Slots are fixed-size and messages are truncated to fit, so recording never allocates and never blocks.

§Trust boundary

The ring file is shared mutable state. Only processes already trusted with the artifact should be able to write it, so it is created owner-only and a deployment that genuinely shares a store between accounts must widen it deliberately. A hostile writer can garble the trail (it cannot escape the mapping: every read is bounds-checked and every string is validated UTF-8), but a hostile writer with access to the store has better things to corrupt.

§Why every byte is an atomic

Other processes write this mapping while we read it. Forming a &[u8] or a &mut [u8] over memory that can change underneath the reference is undefined behaviour in Rust — a data race — no matter how carefully the seqlock sequences the logical reads and writes. The seqlock decides which crumbs are trustworthy; it cannot make a racing non-atomic access defined.

So the mapping is never viewed as a slice. Payload bytes are read and written one relaxed AtomicU8 at a time, and a reader copies the slot into local memory before parsing it, so all the shape-checking happens on bytes nothing else can touch. Relaxed suffices because the sequence number’s release/acquire pair supplies the ordering.

Structs§

SharedRing
A bounded breadcrumb trail shared by every process that opens the same file.

Constants§

MAX_CAPACITY
The largest ring this crate will create: 1M slots, a 256 MiB mapping. SharedRing::open clamps to it.