Skip to main content

Module shared_ring

Module shared_ring 

Source
Expand description

SharedRing<P> - cross-thread / cross-process lock-free MPMC ring backed by a memory-mapped file.

One mechanism gives you THREE deployment modes:

  1. Cross-thread: multiple threads in one process map the same file; lock-free CAS handles concurrency.
  2. Cross-process: multiple processes open the same file via SharedRing::open; the OS page-cache aliases them onto the same physical pages.
  3. Disk-persistent: the MMF is backed by a real file; the kernel writes dirty pages to disk on its own schedule, plus SharedRing::flush forces a sync when the caller wants durability.

The same byte layout serves all three.

§Layout

+-----------------------------+
| RingHeader  (64B aligned)   |  producer_seq, consumer_seq,
|                             |  capacity, slot_size, magic
+-----------------------------+
| Slot[0] (64B cache line)    |  state + sequence + payload
| Slot[1]                     |
| ...                         |
| Slot[capacity - 1]          |
+-----------------------------+

Each slot is exactly one cache line (64 bytes). The state field advances through EMPTY -> CLAIMED_BY_PRODUCER -> PUBLISHED -> CLAIMED_BY_CONSUMER -> EMPTY in a closed loop.

§Concurrency protocol

Producers:

  1. Read producer_seq (atomic).
  2. Compute slot_idx = producer_seq % capacity.
  3. Read slot’s sequence number; if it doesn’t equal producer_seq, the ring is full (slot still holds an unconsumed value). Retry or fail.
  4. CAS producer_seq from S to S+1. On success, the slot is ours to write; copy payload, then store slot.sequence = S+1 (release).

Consumers:

  1. Read consumer_seq.
  2. slot_idx = consumer_seq % capacity.
  3. Acquire-load slot.sequence; must equal consumer_seq + 1 (means producer published). Otherwise empty.
  4. CAS consumer_seq from S to S+1. On success, read payload, then store slot.sequence = S + capacity (releases the slot for the next producer that will use it at producer_seq = S + capacity).

This is the classic Vyukov MPMC bounded-queue protocol.

Structs§

Consumer
Sole-consumer handle on a SharedRingSpsc pair. Same Send + !Sync + !Clone shape as Producer, mirroring the SPSC contract on the read side.
LazySharedRing
Defer the file-backed MMF setup until first use.
Producer
Sole-producer handle on a SharedRingSpsc pair. Send so it can be moved to a producer thread; !Sync so it cannot be shared across threads (which would violate the SPSC contract). Not Clone: a second producer is statically impossible.
RingHeader
Header layout: three cache lines so the two hot counters never false-share. Line 0 is read-mostly metadata (plus the rarely-written epoch); producer_seq and consumer_seq each get their own line. Every producer CASes producer_seq and every consumer CASes consumer_seq; co-locating them on one line made each side’s CAS invalidate the other side’s copy, serializing the producer and consumer coherence traffic under contention. The SPSC ring separates head/tail for exactly this reason.
SharedRing
SharedRingSpsc
Compile-time-enforced single-producer / single-consumer ring, backed by the Lamport 1983 SPSC core in crate::spsc_ring::SpscRingCore.
Slot

Enums§

RingError

Constants§

PAYLOAD_BYTES
Payload bytes per slot = SLOT_SIZE - sizeof(sequence: u64).
RING_MAGIC
Magic number to detect a valid ring header. ASCII ‘APMF’ + version.
SLOT_SIZE
Each slot is exactly one cache line.

Functions§

ring_file_size
Compute the total MMF size for a ring of capacity slots.