pub struct SharedRing { /* private fields */ }Implementations§
Sourcepub fn create(
path: impl AsRef<Path>,
capacity: usize,
) -> Result<Self, RingError>
pub fn create( path: impl AsRef<Path>, capacity: usize, ) -> Result<Self, RingError>
Create or initialise a new ring backed by path. capacity
must be a power of two. The file is truncated to the exact
size needed. Use SharedRing::open to attach to an
existing ring without re-initialising.
Sourcepub fn create_anon(capacity: usize) -> Result<Self, RingError>
pub fn create_anon(capacity: usize) -> Result<Self, RingError>
Create an anonymous in-memory ring with no backing file. Same
byte layout + concurrency protocol as SharedRing::create,
but the mapping is private to this process so cross-process
visibility is not available.
Use when: one-shot scripts, in-process pipelines, tests
that do not need cross-process or disk-persistent semantics.
Skips the file create + ftruncate + first-page-fault cost
create pays (~600 us on Zen+ R7 2700 / Windows 11), so
short-lived sessions amortise much faster.
Do NOT use when: another process needs to attach to the
same ring (use SharedRing::create + SharedRing::open
for that path), or when durability across restart matters.
Sourcepub fn create_from_shm(shm: ShmFile, capacity: usize) -> Result<Self, RingError>
pub fn create_from_shm(shm: ShmFile, capacity: usize) -> Result<Self, RingError>
Build a fresh ring on top of a named RAM-resident
shared-memory backing. Cross-process visible via the
logical_name of the underlying ShmFile; never touches the
page cache. The ShmFile must be sized to at least
ring_file_size(capacity) bytes.
Sourcepub fn open_from_shm(
shm: ShmFile,
expected_capacity: usize,
) -> Result<Self, RingError>
pub fn open_from_shm( shm: ShmFile, expected_capacity: usize, ) -> Result<Self, RingError>
Open an existing named ShmFs-backed ring. Validates magic + capacity. Does NOT re-initialize.
Sourcepub fn create_in_region<R: RegionOwner>(
region: R,
capacity: usize,
) -> Result<Self, RingError>
pub fn create_in_region<R: RegionOwner>( region: R, capacity: usize, ) -> Result<Self, RingError>
Build a fresh Vyukov MPMC ring laid out in caller-owned memory
(huge / large pages, or any
RegionOwner). The region must
hold at least ring_file_size(capacity) bytes; the ring owns it
for its lifetime so the pages stay mapped. This is the global-
FIFO MPMC primitive on large pages; the sharded grid
(SharedRingMpmc::create_grid_in_region) is the per-producer-FIFO
counterpart.
Sourcepub fn open_in_region<R: RegionOwner>(
region: R,
expected_capacity: usize,
) -> Result<Self, RingError>
pub fn open_in_region<R: RegionOwner>( region: R, expected_capacity: usize, ) -> Result<Self, RingError>
Attach to an existing Vyukov ring already laid out in region
(e.g. a named LargePageSection another process created).
Validates the header; does NOT re-initialise.
Sourcepub fn into_lazy(path: impl Into<PathBuf>, capacity: usize) -> LazySharedRing
pub fn into_lazy(path: impl Into<PathBuf>, capacity: usize) -> LazySharedRing
Wrap this ring in a LazySharedRing so subsequent attaches
at the same path can be deferred until first use. The eagerly-
constructed ring stays valid; this helper just hands you the
type’s lazy constructor for symmetry.
Sourcepub fn open(
path: impl AsRef<Path>,
expected_capacity: usize,
) -> Result<Self, RingError>
pub fn open( path: impl AsRef<Path>, expected_capacity: usize, ) -> Result<Self, RingError>
Open an existing ring at path. Validates magic + capacity.
Returns RingError::LayoutMismatch when the file’s size
does not match a ring of expected_capacity slots, OR when
the on-disk header reports different magic / capacity.
pub fn capacity(&self) -> usize
pub fn header(&self) -> &RingHeader
Sourcepub fn try_push(&self, payload: &[u8]) -> Result<(), RingError>
pub fn try_push(&self, payload: &[u8]) -> Result<(), RingError>
Try to push payload into the ring. Returns Err(Full) when
the ring is full.
Sourcepub fn try_push_spsc(&self, payload: &[u8]) -> Result<(), RingError>
pub fn try_push_spsc(&self, payload: &[u8]) -> Result<(), RingError>
Single-producer fast path: skip the CAS on producer_seq.
Caller contract: the caller guarantees only one thread / one
process is calling try_push_spsc on
this ring at a time. Concurrent producers will corrupt the
counter; use try_push for MPMC.
Saves the compare_exchange_weak on producer_seq that the
MPMC path needs to defend against racing producers. Two atomics
per push (1 Acquire load on the slot’s sequence + 1 Release
store on the slot’s sequence) plus one Release store on
producer_seq, vs the MPMC path’s 1 load + 1 CAS + 1 load + 1
store. Net: ~25% less atomic traffic per push.
Also skips the per-op Observation push to the sidecar ring.
Use try_push when you want sidecar
observability on the hot path.
Sourcepub fn try_pop_spsc(&self, out: &mut [u8]) -> Result<usize, RingError>
pub fn try_pop_spsc(&self, out: &mut [u8]) -> Result<usize, RingError>
Single-consumer fast path: skip the CAS on consumer_seq.
Caller contract: the caller guarantees only one thread / one
process is calling try_pop_spsc on this
ring at a time. Concurrent consumers will corrupt the counter;
use try_pop for MPMC.
Same mirror-image savings as
try_push_spsc: two atomics + one
Release store per pop, no CAS, no sidecar observation push.
Sourcepub fn next_pop_signal(&self) -> &AtomicU64
pub fn next_pop_signal(&self) -> &AtomicU64
The publish signal for the consumer’s NEXT pop: the sequence atom of the slot at the current consumer position. A producer publishing that slot Release-stores this exact atom, so a monitor-wait armed on it wakes on the publish. Recompute after every successful pop - the position (and therefore the slot) advances.
Sourcepub fn try_pop(&self, out: &mut [u8]) -> Result<usize, RingError>
pub fn try_pop(&self, out: &mut [u8]) -> Result<usize, RingError>
Try to pop one payload into out. On success, returns the
number of bytes written. On Err(Empty), the ring is empty.
Sourcepub fn flush(&self) -> Result<(), RingError>
pub fn flush(&self) -> Result<(), RingError>
Force the underlying file’s dirty pages to disk. Only meaningful for file-backed rings; no-op for anonymous and ShmFs-backed rings (which never touch disk).
Sourcepub fn flush_async(&self) -> Result<(), RingError>
pub fn flush_async(&self) -> Result<(), RingError>
Non-blocking flush; lets the OS schedule the writeback. Only meaningful for file-backed rings; no-op otherwise.
Sourcepub fn producer_seq(&self) -> u64
pub fn producer_seq(&self) -> u64
Current producer sequence number (monotonic; wraps via modulo-capacity on slot index).
Sourcepub fn consumer_seq(&self) -> u64
pub fn consumer_seq(&self) -> u64
Current consumer sequence number.
Sourcepub fn approx_len(&self) -> usize
pub fn approx_len(&self) -> usize
Approximate items waiting to be drained.
Sourcepub fn next_stuck_slot(&self, from: u64) -> Option<u64>
pub fn next_stuck_slot(&self, from: u64) -> Option<u64>
Find the first slot in the claimed-but-undrained window
[consumer_seq, producer_seq) whose sequence number is stuck
at pos instead of having advanced to pos + 1 (published).
Returns Some(pos) for the first stuck position, None if
every claimed slot has been published.
Use for: sidecar-driven recovery from a producer that
crashed between claiming a slot (CAS on producer_seq) and
publishing it (Release-store on slot.sequence). The window
where a crash leaves a permanent hole is narrow but real for
any Vyukov MPMC; this is the scan that finds those holes.
Hot-path cost: zero. This method is only called by the
sidecar when its Empty-observation analysis decides a ring is
stuck. try_push and try_pop never touch it.
Scan cost: O(producer_seq - consumer_seq) in the worst case (typically small; if the window is large the ring is already saturated and the scan dominates nothing).
Sourcepub fn heal_stuck_slot(&self, pos: u64) -> Result<bool, RingError>
pub fn heal_stuck_slot(&self, pos: u64) -> Result<bool, RingError>
Heal a slot stuck in the claimed-but-never-published state by
advancing its sequence number from pos to pos + 1. The
next consumer at this position drains the slot in normal
try_pop order; its payload bytes are whatever the dying
producer happened to write before crashing (or initial zeros
if the producer crashed before any payload write).
Caller contract: the caller must independently confirm
that the producer which claimed this slot will never publish
it (process dead, lease expired, application-level timeout
elapsed). SharedRing does not record per-slot producer
identity, so this method cannot make that determination on
its own. Calling without dead-producer confirmation will
data-race a live producer that is about to publish; the
race is benign for the CAS itself (the producer’s Release
publishes the same value pos + 1 we are trying to publish,
so the CAS just returns Ok(false)) but the consumer drains
a slot the producer never finished writing.
Where the dead-producer signal comes from: the canonical
signal is HeartbeatTable +
FailoverWatchdog. Register each
producer with a heartbeat; the watchdog declares a process
dead when its heartbeat goes stale beyond the grace period,
then walks the rings that producer touched and calls
heal_stuck_slot(pos) for each stuck position
next_stuck_slot returns.
Returns: Ok(true) if the slot was stuck and is now
healed (CAS succeeded; consumer can drain it).
Ok(false) if the slot was not stuck (sequence already at
pos + 1 or beyond, or pos outside the
[consumer_seq, producer_seq) window). Returns Err only
on PayloadTooLarge style protocol misuse.
Hot-path cost: zero. Only invoked from sidecar recovery. The heal itself is one atomic CAS on the slot’s sequence number; no payload write, no other state touched.