Skip to content

Rings and stacks

Three lock-free MPMC primitives backed by a memory-mapped file. Each is sized at create time; capacity is fixed and pow2.

SharedRing

The headline MPMC bounded ring. Single byte layout serves cross-thread, cross-process, and disk-persistent. Lock-free publish via atomic CAS on the producer cursor; the consumer’s Acquire load on the slot’s Vyukov sequence number is the gate that makes the payload bytes visible - no torn reads by construction.

The trade against crossbeam_queue::ArrayQueue<T>: cross-process visibility plus disk persistence, at the cost of fixed slot-payload size (PAYLOAD_BYTES) and a slightly heavier per-op cost from the cross-process-safe protocol. For single-process work where neither extra is needed, the in-memory queue wins.

Constructor signature:

pub fn create(path: impl AsRef<Path>, capacity: usize) -> Result<Self, RingError>;
pub fn open(path: impl AsRef<Path>, expected_capacity: usize) -> Result<Self, RingError>;

Op kinds: OP_PUSH = 1, OP_POP = 2. The canonical source-tree doc with bench numbers and protocol detail is crates/subetha-cxc/docs/pointers/SHARED_RING.md .

SharedBroadcastRing

One producer, N consumers. Each consumer registers via register_consumer() and gets a private cursor; the producer writes once and every registered consumer sees the message at its own pace. The producer’s try_push does not block on slow consumers - it returns Err(BroadcastError::Full) when the slowest active consumer’s cursor is a full ring of capacity behind.

Constructor:

pub fn create(path: impl AsRef<Path>, capacity: usize) -> Result<Self, BroadcastError>;
pub fn open(path: impl AsRef<Path>, expected_capacity: usize) -> Result<Self, BroadcastError>;

MAX_CONSUMERS caps the number of consumer cursors. Op kinds: OP_PUSH = 1, OP_RECV = 2, OP_REGISTER = 3, OP_UNREGISTER = 4.

Canonical doc: crates/subetha-cxc/docs/pointers/SHARED_BROADCAST_RING.md .

SharedTreiberStack

Lock-free LIFO stack backed by a Treiber-style head pointer. Cross-process variant of the classic Treiber stack with OffsetPtr links between slots (so the two-process case works with both mappings pointing into the same physical pages).

Constructor:

pub fn create(path: impl AsRef<Path>, capacity: usize) -> Result<Self, StackError>;
pub fn open(path: impl AsRef<Path>, expected_capacity: usize) -> Result<Self, StackError>;

The classic ABA problem on the head pointer is avoided by versioning the head with a counter in the same atomic word ((version: u32, slot_idx: u32) packed into a u64). Op kinds follow the ordered module: OP_INSERT = 1, OP_GET = 2, OP_REMOVE = 3, OP_ITER = 4, OP_POP = 5.

Canonical doc: crates/subetha-cxc/docs/pointers/SHARED_TREIBER_STACK.md .

Picking between them

NeedPrimitive
FIFO, one producer, one consumer (or few of each)SharedRing
Pub/sub fan-out, slow consumers must not block producerSharedBroadcastRing
LIFO stack semantics with cross-process visibilitySharedTreiberStack
Work-stealing producer + consumersSharedDequeKhl, SharedDequeKhpd (see role-pair selection )

See also