Skip to main content

orbit_core/ring/
shm.rs

1//! `ShmRing` — POSIX SHM-backed ring buffer (V1 substrate).
2//!
3//! The runtime substrate that finally makes Orbit's "fleet sees each
4//! other" promise real. Cross-process visibility is a memory-coherent
5//! `mmap` with shared atomics; no kernel round-trips per access.
6//!
7//! ## Layout (single SHM segment per `OrbitTyped` kind)
8//!
9//! ```text
10//! ┌──────────────────────────┬───────────────────────────┬──────────────┐
11//! │ ShmRingHeader (64B)      │ LaneHeader[0..L]          │ Lane slots   │
12//! │ kind/spec/topology       │ one head per writer lane  │ L × N slots  │
13//! └──────────────────────────┴───────────────────────────┴──────────────┘
14//! ```
15//!
16//! Shared topology retains the lock-free multi-writer claim-before-commit
17//! protocol. Shared-ordered topology serializes writers with a
18//! process-recoverable kernel advisory lock, commits the slot, and only then
19//! advances the head.
20//! Per-node topology assigns disjoint slots to every node and requires one
21//! active process per node id. Concurrent tasks inside that process are
22//! serialized locally, and the lane head is published only after the slot
23//! reaches its committed sequence. A process dying during a per-node write
24//! therefore leaves no visible hole for other lanes.
25//!
26//! ## Slot publication protocol
27//!
28//! 1. choose/claim the lane counter
29//! 2. `slot = slots[counter % capacity]`
30//! 3. `slot.seq = 2*counter + 1` (odd → writing)
31//! 4. fill `id`, `kind`, `ver`, `payload_len`, `payload[..len]`
32//! 5. `slot.seq = 2*counter + 2` (even → committed)
33//! 6. for per-node lanes, release-store `head = counter + 1`
34//!
35//! ## Read protocol
36//!
37//! 1. read `seq_pre = slot.seq` (Acquire)
38//! 2. if odd → writer in progress, return None / retry
39//! 3. read all fields into locals
40//! 4. read `seq_post = slot.seq` (Acquire)
41//! 5. if `seq_pre != seq_post` → torn write, retry
42//! 6. validate counter encoded in seq matches the one we want
43//!
44//! Tearing is detected, never silently merged. A reader that loses
45//! the race against the writer simply retries (or returns None for
46//! head-read).
47//!
48//! ## Per-kind payload size
49//!
50//! Every `OrbitTyped::KIND` declares its own `RingSpec`. The payload
51//! capacity determines that SHM segment's slot stride; unrelated lanes
52//! no longer pay for one global maximum. Capacity and payload capacity
53//! are persisted in the header and verified by every attaching process.
54
55#![cfg(unix)]
56
57use std::ptr;
58use std::sync::Mutex;
59use std::sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering, fence};
60
61use bytes::Bytes;
62
63use crate::NodeId;
64use crate::id::NetId64;
65use crate::ring::{Frame, RingSpec, RingTopology};
66use crate::shm::{self, ShmRegion};
67
68// ─────────────────────────────────────────────────────────────────────
69// On-disk (well, on-SHM) layout
70// ─────────────────────────────────────────────────────────────────────
71
72const MAGIC: u32 = 0x4F524254; // "ORBT" big-endian when displayed
73const VERSION: u32 = 3;
74const SLOT_ALIGNMENT: usize = 64;
75
76/// Cache-line aligned to keep the header on its own line.
77#[repr(C, align(64))]
78struct ShmRingHeader {
79    version_counter: AtomicU64,
80    capacity: u64,
81    magic: u32,
82    version: u32,
83    payload_capacity: u32,
84    slot_stride: u32,
85    kind: u8,
86    topology: u8,
87    lane_count: u16,
88    /// Shared futex/umtx word used by notification-enabled ring users.
89    /// It lives in the existing reserved header space, so the V3
90    /// layout remains compatible with already-created segments.
91    notification_generation: AtomicU32,
92    /// Readers parked on `notification_generation` through
93    /// [`ParkedRingEventFd`](super::ParkedRingEventFd), so a publisher using
94    /// the parked variant wakes only when one is waiting. Taken from the
95    /// reserved space like the generation: segments created before it read
96    /// zero, and publishers that always wake never look at it.
97    notification_waiters: AtomicU32,
98    /// Explicitly fills one cache line; field order avoids implicit
99    /// alignment padding that would otherwise make this header 128B.
100    _reserved: [u8; 20]
101}
102
103#[repr(C, align(64))]
104struct ShmLaneHeader {
105    write_pos: AtomicU64,
106    _reserved: [u8; 56]
107}
108
109/// Fixed prefix of one dynamically-strided SHM slot.
110#[repr(C)]
111struct ShmSlotHeader {
112    /// LMAX-style sequence: odd while a writer is mid-flight, even
113    /// once committed. Reader checks seq before/after content read
114    /// to detect torn writes.
115    seq: AtomicU64,
116    /// `NetId64::raw()` of the frame that occupies this slot.
117    id: AtomicU64,
118    /// `Frame::ver` — caller-supplied version / tick.
119    ver: AtomicU64,
120    /// Length of the meaningful prefix of `payload`.
121    payload_len: AtomicU32,
122    /// `Frame::kind` — the message-class byte (state/event/cmd/…).
123    kind: AtomicU8,
124    _reserved: [u8; 3]
125}
126
127// The content fields are atomic for the same reason `seq` is: a reader may be
128// copying this slot while a writer overwrites it. The seqlock *detects* that
129// afterwards, which is not the same as not having a race — under the memory
130// model a torn read through a plain `u64` is undefined however the hardware
131// behaves, and Miri or TSan would say so. `Relaxed` is what they need: all the
132// ordering is already carried by `seq` and the two fences around it, so these
133// compile to the same plain loads and stores they were, with the race removed
134// rather than papered over.
135
136const SLOT_HEADER_SIZE: usize = std::mem::size_of::<ShmSlotHeader>();
137const HEADER_SIZE: usize = std::mem::size_of::<ShmRingHeader>();
138const LANE_HEADER_SIZE: usize = std::mem::size_of::<ShmLaneHeader>();
139
140const _: () = assert!(HEADER_SIZE == 64);
141const _: () = assert!(LANE_HEADER_SIZE == 64);
142const _: () = assert!(SLOT_HEADER_SIZE == 32);
143
144fn invalid_input(message: impl Into<String>) -> std::io::Error {
145    std::io::Error::new(std::io::ErrorKind::InvalidInput, message.into())
146}
147
148fn lane_count_for(
149    spec: RingSpec,
150    fleet_capacity: u16
151) -> std::io::Result<usize> {
152    if fleet_capacity == 0 {
153        return Err(invalid_input("ShmRing fleet capacity must be > 0"));
154    }
155    Ok(match spec.topology {
156        RingTopology::Shared | RingTopology::SharedOrdered => 1,
157        RingTopology::PerNode => usize::from(fleet_capacity)
158    })
159}
160
161fn checked_layout(
162    spec: RingSpec,
163    fleet_capacity: u16
164) -> std::io::Result<(usize, usize, usize)> {
165    if spec.capacity == 0 {
166        return Err(invalid_input("ShmRing capacity must be > 0"));
167    }
168    if !spec.capacity.is_power_of_two() {
169        return Err(invalid_input("ShmRing capacity must be a power of two"));
170    }
171    if spec.payload_capacity > u32::MAX as usize {
172        return Err(invalid_input("ShmRing payload capacity must fit in u32"));
173    }
174
175    let unaligned = SLOT_HEADER_SIZE
176        .checked_add(spec.payload_capacity)
177        .ok_or_else(|| invalid_input("ShmRing slot size overflow"))?;
178    let slot_stride = unaligned
179        .checked_add(SLOT_ALIGNMENT - 1)
180        .map(|value| value & !(SLOT_ALIGNMENT - 1))
181        .ok_or_else(|| invalid_input("ShmRing slot stride overflow"))?;
182    if slot_stride > u32::MAX as usize {
183        return Err(invalid_input("ShmRing slot stride must fit in u32"));
184    }
185    let lane_count = lane_count_for(spec, fleet_capacity)?;
186    let lane_headers_size = lane_count
187        .checked_mul(LANE_HEADER_SIZE)
188        .ok_or_else(|| invalid_input("ShmRing lane header size overflow"))?;
189    let slots_offset = HEADER_SIZE
190        .checked_add(lane_headers_size)
191        .ok_or_else(|| invalid_input("ShmRing slots offset overflow"))?;
192    let slots_per_lane = spec
193        .capacity
194        .checked_mul(slot_stride)
195        .ok_or_else(|| invalid_input("ShmRing lane size overflow"))?;
196    let slots_size = lane_count
197        .checked_mul(slots_per_lane)
198        .ok_or_else(|| invalid_input("ShmRing slots size overflow"))?;
199    let segment_size = slots_offset
200        .checked_add(slots_size)
201        .ok_or_else(|| invalid_input("ShmRing segment size overflow"))?;
202    Ok((slot_stride, slots_offset, segment_size))
203}
204
205/// Compute the SHM segment size required by a ring spec.
206pub fn segment_size_for_spec(spec: RingSpec) -> std::io::Result<usize> {
207    segment_size_for_spec_and_fleet(spec, 1)
208}
209
210/// Compute the SHM segment size required for a fleet-aware ring spec.
211pub fn segment_size_for_spec_and_fleet(
212    spec: RingSpec,
213    fleet_capacity: u16
214) -> std::io::Result<usize> {
215    checked_layout(spec, fleet_capacity).map(|(_, _, segment_size)| segment_size)
216}
217
218// ─────────────────────────────────────────────────────────────────────
219// ShmRing
220// ─────────────────────────────────────────────────────────────────────
221
222/// Cross-process ring buffer backed by a POSIX SHM segment.
223///
224/// Use [`ShmRing::open_or_create`] to attach to (or create) the
225/// segment by name. Multiple processes calling this with the same name and
226/// matching [`RingSpec`] share the underlying memory; the first process to
227/// call it does the one-time header initialization.
228pub struct ShmRing {
229    region: ShmRegion,
230    kind: u8,
231    capacity: usize,
232    payload_capacity: usize,
233    topology: RingTopology,
234    lane_count: usize,
235    slot_stride: usize,
236    slots_offset: usize,
237    lane_stride: usize,
238    write_locks: Vec<Mutex<()>>
239}
240
241impl ShmRing {
242    /// Open or create a SHM-backed ring under `fleet_name` for type
243    /// kind `kind` with `spec`. The first process to call
244    /// this initializes the header; later attachers reuse it.
245    pub fn open_or_create(
246        fleet_name: &str,
247        kind: u8,
248        spec: RingSpec
249    ) -> std::io::Result<Self> {
250        Self::open_or_create_for_fleet(fleet_name, kind, spec, 1)
251    }
252
253    /// Open or create a SHM-backed ring using `fleet_capacity` physical
254    /// writer lanes when `spec` is [`RingTopology::PerNode`].
255    pub fn open_or_create_for_fleet(
256        fleet_name: &str,
257        kind: u8,
258        spec: RingSpec,
259        fleet_capacity: u16
260    ) -> std::io::Result<Self> {
261        let lane_count = lane_count_for(spec, fleet_capacity)?;
262        let (slot_stride, slots_offset, size) = checked_layout(spec, fleet_capacity)?;
263        let lane_stride = spec
264            .capacity
265            .checked_mul(slot_stride)
266            .ok_or_else(|| invalid_input("ShmRing lane stride overflow"))?;
267        let name = shm::ring_segment_name(fleet_name, kind);
268
269        // Locked whatever the topology. Creation is two steps that a peer can
270        // arrive between — `shm_open(O_CREAT|O_EXCL)` publishes the name, and
271        // the header below is written after it — and the attach path reads that
272        // header and rejects a segment whose magic is still zero, with no
273        // retry. Only `SharedOrdered` took this lock, so every other ring was
274        // one scheduling accident away from a peer failing to boot with
275        // "wrong magic 0x00000000"; N workers starting together is exactly the
276        // case that produces it. `SharedOrdered` still keeps the lock for its
277        // writes — this is the same lock, held for a different reason.
278        let (region, _initialization_lock) = ShmRegion::open_or_create_locked(&name, size)?;
279
280        // Initialize header on first creation; subsequent attachers
281        // skip and rely on whatever the creator wrote.
282        if region.created() {
283            // SAFETY: region is mapped, header lives at offset 0,
284            // size is right because we just created with this size.
285            unsafe {
286                let header_ptr = region.as_ptr() as *mut ShmRingHeader;
287                ptr::write(
288                    header_ptr,
289                    ShmRingHeader {
290                        version_counter: AtomicU64::new(0),
291                        capacity: spec.capacity as u64,
292                        magic: MAGIC,
293                        version: VERSION,
294                        payload_capacity: spec.payload_capacity as u32,
295                        slot_stride: slot_stride as u32,
296                        kind,
297                        topology: spec.topology as u8,
298                        lane_count: lane_count as u16,
299                        notification_generation: AtomicU32::new(0),
300                        notification_waiters: AtomicU32::new(0),
301                        _reserved: [0; 20]
302                    }
303                );
304
305                for lane in 0..lane_count {
306                    let lane_ptr = region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
307                        as *mut ShmLaneHeader;
308                    ptr::write(
309                        lane_ptr,
310                        ShmLaneHeader { write_pos: AtomicU64::new(0), _reserved: [0; 56] }
311                    );
312                }
313
314                // Zero out the slot region so all `seq` values start
315                // at 0 (== "never written, even, slot empty"). 0 is
316                // valid as both a u64 atomic and as bytes for our
317                // POD slot shape.
318                let slots_ptr = region.as_ptr().add(slots_offset);
319                ptr::write_bytes(slots_ptr, 0, lane_count * lane_stride);
320            }
321        } else {
322            // Attaching to an existing segment — sanity-check the header.
323            // SAFETY: region is mapped; header lives at offset 0.
324            let header = unsafe { &*(region.as_ptr() as *const ShmRingHeader) };
325            if header.magic != MAGIC {
326                return Err(std::io::Error::new(
327                    std::io::ErrorKind::InvalidData,
328                    format!(
329                        "SHM segment {} has wrong magic 0x{:08X} (expected 0x{:08X})",
330                        name, header.magic, MAGIC
331                    )
332                ));
333            }
334            if header.version != VERSION {
335                return Err(std::io::Error::new(
336                    std::io::ErrorKind::InvalidData,
337                    format!("SHM segment {} version {} != local {}", name, header.version, VERSION)
338                ));
339            }
340            if header.kind != kind {
341                return Err(std::io::Error::new(
342                    std::io::ErrorKind::InvalidData,
343                    format!("SHM segment {} kind {} != requested {}", name, header.kind, kind)
344                ));
345            }
346            if header.topology != spec.topology as u8 {
347                return Err(std::io::Error::new(
348                    std::io::ErrorKind::InvalidData,
349                    format!(
350                        "SHM segment {} topology {} != requested {}",
351                        name, header.topology, spec.topology as u8
352                    )
353                ));
354            }
355            if header.lane_count as usize != lane_count {
356                return Err(std::io::Error::new(
357                    std::io::ErrorKind::InvalidData,
358                    format!(
359                        "SHM segment {} lane count {} != requested {}",
360                        name, header.lane_count, lane_count
361                    )
362                ));
363            }
364            if header.capacity as usize != spec.capacity {
365                return Err(std::io::Error::new(
366                    std::io::ErrorKind::InvalidData,
367                    format!(
368                        "SHM segment {} capacity {} != requested {}",
369                        name, header.capacity, spec.capacity
370                    )
371                ));
372            }
373            if header.payload_capacity as usize != spec.payload_capacity {
374                return Err(std::io::Error::new(
375                    std::io::ErrorKind::InvalidData,
376                    format!(
377                        "SHM segment {} payload capacity {} != requested {}",
378                        name, header.payload_capacity, spec.payload_capacity
379                    )
380                ));
381            }
382            if header.slot_stride as usize != slot_stride {
383                return Err(std::io::Error::new(
384                    std::io::ErrorKind::InvalidData,
385                    format!(
386                        "SHM segment {} slot stride {} != requested {}",
387                        name, header.slot_stride, slot_stride
388                    )
389                ));
390            }
391        }
392
393        Ok(Self {
394            region,
395            kind,
396            capacity: spec.capacity,
397            payload_capacity: spec.payload_capacity,
398            topology: spec.topology,
399            lane_count,
400            slot_stride,
401            slots_offset,
402            lane_stride,
403            write_locks: (0..lane_count).map(|_| Mutex::new(())).collect()
404        })
405    }
406
407    /// True when this handle was the one that *created* the SHM
408    /// segment (vs attaching to an already-existing one).
409    pub fn created(&self) -> bool {
410        self.region.created()
411    }
412
413    /// Remove the SHM segment name. Existing mappings stay valid;
414    /// new opens will fail until `open_or_create` recreates it.
415    /// Call from the owner process at fleet shutdown.
416    pub fn unlink(&self) -> std::io::Result<()> {
417        self.region.unlink()
418    }
419
420    /// The KIND byte this ring carries.
421    pub fn kind(&self) -> u8 {
422        self.kind
423    }
424
425    /// Slot count per lane.
426    pub fn capacity(&self) -> usize {
427        self.capacity
428    }
429
430    /// Number of physical writer lanes in this segment.
431    pub fn lane_count(&self) -> usize {
432        self.lane_count
433    }
434
435    /// Maximum inline payload bytes for this ring lane.
436    pub fn payload_capacity(&self) -> usize {
437        self.payload_capacity
438    }
439
440    pub fn spec(&self) -> RingSpec {
441        RingSpec {
442            capacity: self.capacity,
443            payload_capacity: self.payload_capacity,
444            topology: self.topology
445        }
446    }
447
448    fn lane_header(
449        &self,
450        lane: usize
451    ) -> &ShmLaneHeader {
452        debug_assert!(lane < self.lane_count);
453        unsafe {
454            &*(self.region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
455                as *const ShmLaneHeader)
456        }
457    }
458
459    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
460    pub(crate) fn notification_generation(&self) -> &AtomicU32 {
461        // SAFETY: the mapped header was initialized before this ring handle
462        // was returned and remains mapped for the lifetime of `self`.
463        unsafe { &(*(self.region.as_ptr() as *const ShmRingHeader)).notification_generation }
464    }
465
466    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
467    pub(crate) fn notification_waiters(&self) -> &AtomicU32 {
468        // SAFETY: as for `notification_generation`.
469        unsafe { &(*(self.region.as_ptr() as *const ShmRingHeader)).notification_waiters }
470    }
471
472    fn slot_ptr(
473        &self,
474        lane: usize,
475        idx: usize
476    ) -> *mut ShmSlotHeader {
477        debug_assert!(lane < self.lane_count);
478        debug_assert!(idx < self.capacity);
479        // SAFETY: slots region begins at `slots_offset`; lane and idx
480        // are bounded by the validated mapping layout.
481        unsafe {
482            let base = self.region.as_ptr().add(self.slots_offset + lane * self.lane_stride);
483            base.add(idx * self.slot_stride).cast::<ShmSlotHeader>()
484        }
485    }
486
487    /// The payload bytes, as the atomics they have to be for the same reason
488    /// the header fields are.
489    ///
490    /// The cost is a byte at a time instead of a `memcpy`, over payloads this
491    /// workspace sizes in tens to hundreds of bytes — 18 for a metric sample,
492    /// 1024 at the largest — published at snapshot rather than request rate.
493    /// Word-at-a-time would need the payload region padded to a word multiple,
494    /// which changes the segment layout and so the compatibility check every
495    /// attaching process makes; worth doing if a payload ever grows enough to
496    /// notice, and not before.
497    unsafe fn payload_ptr(slot_ptr: *mut ShmSlotHeader) -> *mut AtomicU8 {
498        unsafe { slot_ptr.cast::<u8>().add(SLOT_HEADER_SIZE).cast::<AtomicU8>() }
499    }
500
501    /// Head of the sole shared lane, or lane zero for a per-node ring.
502    pub fn head(&self) -> u64 {
503        self.lane_header(0).write_pos.load(Ordering::Acquire)
504    }
505
506    /// Current committed head for `node_id`'s lane.
507    pub fn lane_head(
508        &self,
509        node_id: NodeId
510    ) -> u64 {
511        let lane = self.lane_index(node_id);
512        self.lane_header(lane).write_pos.load(Ordering::Acquire)
513    }
514
515    /// Allocate one non-zero semantic version shared by all writer lanes and
516    /// processes attached to this ring.
517    pub fn next_version(&self) -> u64 {
518        let header = unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
519        header
520            .version_counter
521            .fetch_add(1, Ordering::AcqRel)
522            .checked_add(1)
523            .expect("SHM ring semantic version exhausted")
524    }
525
526    /// Last semantic version allocated by any process attached to this ring.
527    pub fn current_version(&self) -> u64 {
528        let header = unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
529        header.version_counter.load(Ordering::Acquire)
530    }
531
532    /// Append a frame. Atomically reserves the next counter, mints
533    /// the [`NetId64`], writes the slot. Returns the minted id.
534    pub fn write(
535        &self,
536        node_id: NodeId,
537        frame_kind: u8,
538        ver: u64,
539        payload: Bytes
540    ) -> std::io::Result<NetId64> {
541        if payload.len() > self.payload_capacity {
542            return Err(std::io::Error::new(
543                std::io::ErrorKind::InvalidInput,
544                format!(
545                    "payload {} > ring payload capacity {}",
546                    payload.len(),
547                    self.payload_capacity
548                )
549            ));
550        }
551
552        let lane = self.lane_index(node_id);
553        match self.topology {
554            RingTopology::Shared => {
555                let counter = self.lane_header(lane).write_pos.fetch_add(1, Ordering::AcqRel);
556                Ok(self.write_slot(lane, node_id, counter, frame_kind, ver, &payload))
557            }
558            RingTopology::PerNode => {
559                let _write =
560                    self.write_locks[lane].lock().unwrap_or_else(|error| error.into_inner());
561                let lane_header = self.lane_header(lane);
562                let counter = lane_header.write_pos.load(Ordering::Relaxed);
563                let id = self.write_slot(lane, node_id, counter, frame_kind, ver, &payload);
564                lane_header.write_pos.store(counter.wrapping_add(1), Ordering::Release);
565                Ok(id)
566            }
567            RingTopology::SharedOrdered => {
568                let _write =
569                    self.write_locks[lane].lock().unwrap_or_else(|error| error.into_inner());
570                let _cross_process = self.region.lock_exclusive()?;
571                let lane_header = self.lane_header(lane);
572                let counter = lane_header.write_pos.load(Ordering::Relaxed);
573                let id = self.write_slot(lane, node_id, counter, frame_kind, ver, &payload);
574                lane_header.write_pos.store(counter.wrapping_add(1), Ordering::Release);
575                Ok(id)
576            }
577        }
578    }
579
580    /// Append one contiguous batch to a lane and return consecutive ids.
581    ///
582    /// Per-node and shared-ordered lane heads advance only after the complete
583    /// batch has committed. The batch may not exceed the ring capacity.
584    pub fn write_batch(
585        &self,
586        node_id: NodeId,
587        frame_kind: u8,
588        ver: u64,
589        payloads: Vec<Bytes>
590    ) -> std::io::Result<Vec<NetId64>> {
591        if payloads.len() > self.capacity {
592            return Err(std::io::Error::new(
593                std::io::ErrorKind::InvalidInput,
594                format!("batch {} > ring capacity {}", payloads.len(), self.capacity)
595            ));
596        }
597        if let Some(payload) = payloads.iter().find(|payload| payload.len() > self.payload_capacity)
598        {
599            return Err(std::io::Error::new(
600                std::io::ErrorKind::InvalidInput,
601                format!(
602                    "payload {} > ring payload capacity {}",
603                    payload.len(),
604                    self.payload_capacity
605                )
606            ));
607        }
608        if payloads.is_empty() {
609            return Ok(Vec::new());
610        }
611
612        let lane = self.lane_index(node_id);
613        let write_slots = |start: u64, payloads: Vec<Bytes>| {
614            payloads
615                .into_iter()
616                .enumerate()
617                .map(|(offset, payload)| {
618                    self.write_slot(
619                        lane,
620                        node_id,
621                        start.wrapping_add(offset as u64),
622                        frame_kind,
623                        ver,
624                        &payload
625                    )
626                })
627                .collect::<Vec<_>>()
628        };
629
630        match self.topology {
631            RingTopology::Shared => {
632                let start = self
633                    .lane_header(lane)
634                    .write_pos
635                    .fetch_add(payloads.len() as u64, Ordering::AcqRel);
636                Ok(write_slots(start, payloads))
637            }
638            RingTopology::PerNode => {
639                let _write =
640                    self.write_locks[lane].lock().unwrap_or_else(|error| error.into_inner());
641                let lane_header = self.lane_header(lane);
642                let start = lane_header.write_pos.load(Ordering::Relaxed);
643                let ids = write_slots(start, payloads);
644                lane_header
645                    .write_pos
646                    .store(start.wrapping_add(ids.len() as u64), Ordering::Release);
647                Ok(ids)
648            }
649            RingTopology::SharedOrdered => {
650                let _write =
651                    self.write_locks[lane].lock().unwrap_or_else(|error| error.into_inner());
652                let _cross_process = self.region.lock_exclusive()?;
653                let lane_header = self.lane_header(lane);
654                let start = lane_header.write_pos.load(Ordering::Relaxed);
655                let ids = write_slots(start, payloads);
656                lane_header
657                    .write_pos
658                    .store(start.wrapping_add(ids.len() as u64), Ordering::Release);
659                Ok(ids)
660            }
661        }
662    }
663
664    /// Read the slot whose counter matches `id.counter()`. Returns
665    /// `None` if the slot has been overwritten, was never written,
666    /// or a torn read could not be reconciled across two retries.
667    pub fn read(
668        &self,
669        id: NetId64
670    ) -> Option<Frame> {
671        if id.kind() != self.kind {
672            return None;
673        }
674        let lane = self.lane_index_for_frame(id)?;
675        let counter = id.counter();
676        let slot_idx = (counter as usize) & (self.capacity - 1);
677        let slot_ptr = self.slot_ptr(lane, slot_idx);
678
679        // Two retries — torn writes happen but resolve quickly.
680        for _ in 0..3 {
681            let Some(frame) = (unsafe { read_committed_frame(slot_ptr, self.payload_capacity) })
682            else {
683                continue;
684            };
685            if frame.id.counter() == counter {
686                return Some(frame);
687            } else {
688                // Slot now holds a different (later) id — wraparound.
689                return None;
690            }
691        }
692        None
693    }
694
695    /// Read the most recent frame (head - 1). Returns `None` if no
696    /// write has happened yet, or a torn read could not resolve.
697    pub fn read_head(&self) -> Option<Frame> {
698        let head = self.head();
699        if head == 0 {
700            return None;
701        }
702        let counter = head - 1;
703        let slot_idx = (counter as usize) & (self.capacity - 1);
704        let slot_ptr = self.slot_ptr(0, slot_idx);
705
706        for _ in 0..3 {
707            if let Some(frame) = unsafe { read_committed_frame(slot_ptr, self.payload_capacity) } {
708                return Some(frame);
709            }
710        }
711        None
712    }
713
714    /// Read whatever frame currently occupies the slot at
715    /// `counter % capacity`, regardless of which counter is stored
716    /// there. Used by walking readers that need slot-by-slot access
717    /// without knowing the writer's `NetId64` ahead of time.
718    pub fn read_at(
719        &self,
720        counter: u64
721    ) -> Option<Frame> {
722        self.read_lane_index_at(0, counter)
723    }
724
725    /// Read the frame currently occupying `node_id`'s lane slot.
726    pub fn read_lane_at(
727        &self,
728        node_id: NodeId,
729        counter: u64
730    ) -> Option<Frame> {
731        let lane = self.lane_index(node_id);
732        self.read_lane_index_at(lane, counter)
733    }
734
735    fn read_lane_index_at(
736        &self,
737        lane: usize,
738        counter: u64
739    ) -> Option<Frame> {
740        let slot_idx = (counter as usize) & (self.capacity - 1);
741        let slot_ptr = self.slot_ptr(lane, slot_idx);
742        for _ in 0..3 {
743            if let Some(frame) = unsafe { read_committed_frame(slot_ptr, self.payload_capacity) } {
744                return Some(frame);
745            }
746        }
747        None
748    }
749
750    pub(crate) fn read_state_at(
751        &self,
752        counter: u64
753    ) -> crate::ring::cursor::RingRead {
754        self.read_lane_index_state_at(0, counter)
755    }
756
757    pub(crate) fn read_lane_state_at(
758        &self,
759        node_id: NodeId,
760        counter: u64
761    ) -> crate::ring::cursor::RingRead {
762        let lane = self.lane_index(node_id);
763        self.read_lane_index_state_at(lane, counter)
764    }
765
766    fn read_lane_index_state_at(
767        &self,
768        lane: usize,
769        counter: u64
770    ) -> crate::ring::cursor::RingRead {
771        use crate::ring::cursor::RingRead;
772
773        let slot_idx = (counter as usize) & (self.capacity - 1);
774        let slot_ptr = self.slot_ptr(lane, slot_idx);
775        let expected_committed =
776            counter.checked_mul(2).and_then(|value| value.checked_add(2)).expect("seq overflow");
777
778        for _ in 0..3 {
779            let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
780            if sequence < expected_committed {
781                return if self.topology != RingTopology::Shared {
782                    RingRead::Unavailable
783                } else {
784                    RingRead::Pending
785                };
786            }
787            if sequence > expected_committed {
788                return RingRead::Unavailable;
789            }
790            if let Some(frame) = unsafe { read_committed_frame(slot_ptr, self.payload_capacity) } {
791                return if frame.id.counter() == counter {
792                    RingRead::Ready(frame)
793                } else {
794                    RingRead::Unavailable
795                };
796            }
797        }
798
799        let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
800        if sequence < expected_committed {
801            if self.topology != RingTopology::Shared {
802                RingRead::Unavailable
803            } else {
804                RingRead::Pending
805            }
806        } else {
807            RingRead::Unavailable
808        }
809    }
810
811    /// Clear all slots and reset every lane head to zero.
812    ///
813    /// Intended for owner-controlled boot-time cleanup. Do not call
814    /// while other processes are publishing to this ring: it rewrites
815    /// the shared slot memory in place.
816    pub fn reset(&self) {
817        // SAFETY: the region is mapped and the slot area begins at
818        // HEADER_SIZE. The caller must ensure the ring is quiescent.
819        unsafe {
820            let slots_ptr = self.region.as_ptr().add(self.slots_offset);
821            ptr::write_bytes(slots_ptr, 0, self.lane_count * self.lane_stride);
822        }
823        for lane in 0..self.lane_count {
824            self.lane_header(lane).write_pos.store(0, Ordering::Release);
825        }
826        let header = unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
827        header.version_counter.store(0, Ordering::Release);
828    }
829
830    fn lane_index(
831        &self,
832        node_id: NodeId
833    ) -> usize {
834        let lane = match self.topology {
835            RingTopology::Shared | RingTopology::SharedOrdered => 0,
836            RingTopology::PerNode => usize::from(node_id.get())
837        };
838        assert!(
839            lane < self.lane_count,
840            "node {} is outside SHM ring lane count {}",
841            node_id.get(),
842            self.lane_count
843        );
844        lane
845    }
846
847    fn lane_index_for_frame(
848        &self,
849        id: NetId64
850    ) -> Option<usize> {
851        let lane = match self.topology {
852            RingTopology::Shared | RingTopology::SharedOrdered => 0,
853            RingTopology::PerNode => usize::from(id.node())
854        };
855        (lane < self.lane_count).then_some(lane)
856    }
857
858    fn write_slot(
859        &self,
860        lane: usize,
861        node_id: NodeId,
862        counter: u64,
863        frame_kind: u8,
864        ver: u64,
865        payload: &[u8]
866    ) -> NetId64 {
867        let id = NetId64::make(self.kind, node_id.get(), counter);
868        let slot_idx = (counter as usize) & (self.capacity - 1);
869        let slot_ptr = self.slot_ptr(lane, slot_idx);
870
871        // Disruptor-style write: seq goes odd → write content → seq goes even.
872        //
873        // The odd marker is stored `Relaxed` and followed by a `Release`
874        // *fence*, not stored `Release`. A release store orders the accesses
875        // that come *before* it and says nothing about the ones after, so the
876        // content writes below were free to become visible ahead of the marker:
877        // a reader would then see an even seq on both sides of a slot that was
878        // being torn underneath it. The fence is what forbids that, because it
879        // orders everything before it — the marker — ahead of every store after
880        // it.
881        //
882        // The closing store stays `Release`: there it is the content writes
883        // that must be visible first, which is exactly what a release store
884        // gives. It pairs with the reader's `Acquire` fence.
885        //
886        // x86's store-store ordering hides the difference; aarch64 does not.
887        unsafe {
888            let slot = &*slot_ptr;
889            let mid_seq = counter
890                .checked_mul(2)
891                .and_then(|value| value.checked_add(1))
892                .expect("seq overflow");
893            let final_seq = mid_seq.wrapping_add(1);
894
895            slot.seq.store(mid_seq, Ordering::Relaxed);
896            fence(Ordering::Release);
897            slot.id.store(id.raw(), Ordering::Relaxed);
898            slot.ver.store(ver, Ordering::Relaxed);
899            slot.payload_len.store(payload.len() as u32, Ordering::Relaxed);
900            slot.kind.store(frame_kind, Ordering::Relaxed);
901            let bytes = Self::payload_ptr(slot_ptr);
902            for (index, byte) in payload.iter().enumerate() {
903                (*bytes.add(index)).store(*byte, Ordering::Relaxed);
904            }
905            slot.seq.store(final_seq, Ordering::Release);
906        }
907
908        id
909    }
910}
911
912// ─────────────────────────────────────────────────────────────────────
913// Read-only inspection
914// ─────────────────────────────────────────────────────────────────────
915
916/// Persisted geometry of one existing Orbit SHM ring.
917///
918/// The values come from the mapped ring header. An observer does not need the
919/// producer's fleet capacity or compile-time [`RingSpec`] to inspect raw
920/// frames.
921#[derive(Clone, Debug, PartialEq, Eq)]
922pub struct ShmRingMetadata {
923    /// Exact POSIX SHM object name that was opened.
924    pub name: String,
925    /// Number of bytes reported by the existing SHM object.
926    pub segment_size: usize,
927    /// Persisted Orbit ring format version.
928    pub format_version: u32,
929    /// Stable Orbit type kind carried by this ring.
930    pub kind: u8,
931    /// Persisted per-lane capacity, payload limit, and topology.
932    pub spec: RingSpec,
933    /// Number of physical lanes present in the mapping.
934    pub lane_count: usize
935}
936
937/// Read-only attachment to one existing Orbit SHM ring.
938///
939/// Attaching never creates, initializes, resets, locks, or unlinks a segment.
940/// Dropping the view only unmaps this process's local mapping. Use
941/// [`ShmRingView::lane`] to obtain a
942/// [`RingFrameSource`](crate::ring::cursor::RingFrameSource) for one physical
943/// lane.
944pub struct ShmRingView {
945    region: ShmRegion,
946    metadata: ShmRingMetadata,
947    slot_stride: usize,
948    slots_offset: usize,
949    lane_stride: usize
950}
951
952impl std::fmt::Debug for ShmRingView {
953    fn fmt(
954        &self,
955        formatter: &mut std::fmt::Formatter<'_>
956    ) -> std::fmt::Result {
957        formatter.debug_struct("ShmRingView").field("metadata", &self.metadata).finish()
958    }
959}
960
961impl ShmRingView {
962    /// Attach to an existing ring owned by the effective user.
963    pub fn attach_existing(
964        fleet_name: &str,
965        kind: u8
966    ) -> std::io::Result<Self> {
967        // SAFETY: `geteuid` has no error path.
968        let uid = unsafe { libc::geteuid() };
969        Self::attach_existing_for_uid(fleet_name, kind, uid)
970    }
971
972    /// Attach to an existing ring under an explicit uid-scoped name.
973    ///
974    /// POSIX permissions still decide whether the calling process may open
975    /// another user's object.
976    pub fn attach_existing_for_uid(
977        fleet_name: &str,
978        kind: u8,
979        uid: u32
980    ) -> std::io::Result<Self> {
981        let name = shm::ring_segment_name_for_uid(fleet_name, kind, uid);
982        let region = ShmRegion::open_existing_read_only(&name, HEADER_SIZE)?;
983
984        // SAFETY: the mapping is at least HEADER_SIZE bytes and the header is
985        // cache-line aligned at offset zero.
986        let header = unsafe { &*(region.as_ptr() as *const ShmRingHeader) };
987        if header.magic != MAGIC {
988            return Err(std::io::Error::new(
989                std::io::ErrorKind::InvalidData,
990                format!(
991                    "SHM segment {} has wrong magic 0x{:08X} (expected 0x{:08X})",
992                    name, header.magic, MAGIC
993                )
994            ));
995        }
996        if header.version != VERSION {
997            return Err(std::io::Error::new(
998                std::io::ErrorKind::InvalidData,
999                format!("SHM segment {} version {} != local {}", name, header.version, VERSION)
1000            ));
1001        }
1002        if header.kind != kind {
1003            return Err(std::io::Error::new(
1004                std::io::ErrorKind::InvalidData,
1005                format!("SHM segment {} kind {} != requested {}", name, header.kind, kind)
1006            ));
1007        }
1008
1009        let topology = match header.topology {
1010            value if value == RingTopology::Shared as u8 => RingTopology::Shared,
1011            value if value == RingTopology::PerNode as u8 => RingTopology::PerNode,
1012            value if value == RingTopology::SharedOrdered as u8 => RingTopology::SharedOrdered,
1013            value => {
1014                return Err(std::io::Error::new(
1015                    std::io::ErrorKind::InvalidData,
1016                    format!("SHM segment {name} has unknown topology {value}")
1017                ));
1018            }
1019        };
1020        let capacity = usize::try_from(header.capacity).map_err(|_| {
1021            std::io::Error::new(
1022                std::io::ErrorKind::InvalidData,
1023                format!(
1024                    "SHM segment {name} capacity {} does not fit this platform",
1025                    header.capacity
1026                )
1027            )
1028        })?;
1029        let lane_count = usize::from(header.lane_count);
1030        if lane_count == 0 {
1031            return Err(std::io::Error::new(
1032                std::io::ErrorKind::InvalidData,
1033                format!("SHM segment {name} has zero lanes")
1034            ));
1035        }
1036        if topology != RingTopology::PerNode && lane_count != 1 {
1037            return Err(std::io::Error::new(
1038                std::io::ErrorKind::InvalidData,
1039                format!(
1040                    "SHM segment {name} topology {topology:?} requires one lane, found {lane_count}"
1041                )
1042            ));
1043        }
1044        let spec =
1045            RingSpec { capacity, payload_capacity: header.payload_capacity as usize, topology };
1046        let fleet_capacity = if topology == RingTopology::PerNode { header.lane_count } else { 1 };
1047        let (slot_stride, slots_offset, expected_size) = checked_layout(spec, fleet_capacity)
1048            .map_err(|error| {
1049                std::io::Error::new(
1050                    std::io::ErrorKind::InvalidData,
1051                    format!("SHM segment {name} has invalid geometry: {error}")
1052                )
1053            })?;
1054        if header.slot_stride as usize != slot_stride {
1055            return Err(std::io::Error::new(
1056                std::io::ErrorKind::InvalidData,
1057                format!(
1058                    "SHM segment {} slot stride {} != geometry {}",
1059                    name, header.slot_stride, slot_stride
1060                )
1061            ));
1062        }
1063        if region.len() < expected_size {
1064            return Err(std::io::Error::new(
1065                std::io::ErrorKind::InvalidData,
1066                format!(
1067                    "SHM segment {} size {} is smaller than persisted geometry {}",
1068                    name,
1069                    region.len(),
1070                    expected_size
1071                )
1072            ));
1073        }
1074        let lane_stride = capacity
1075            .checked_mul(slot_stride)
1076            .ok_or_else(|| invalid_input(format!("SHM segment {name} lane stride overflow")))?;
1077
1078        Ok(Self {
1079            metadata: ShmRingMetadata {
1080                name,
1081                segment_size: region.len(),
1082                format_version: header.version,
1083                kind,
1084                spec,
1085                lane_count
1086            },
1087            region,
1088            slot_stride,
1089            slots_offset,
1090            lane_stride
1091        })
1092    }
1093
1094    /// Attach and also verify the caller's typed ring contract.
1095    pub fn attach_typed<T: crate::OrbitTyped>(fleet_name: &str) -> std::io::Result<Self> {
1096        let view = Self::attach_existing(fleet_name, T::KIND)?;
1097        if view.metadata.spec != T::RING_SPEC {
1098            return Err(std::io::Error::new(
1099                std::io::ErrorKind::InvalidData,
1100                format!(
1101                    "OrbitTyped KIND {} declares {:?}; existing spec is {:?}",
1102                    T::KIND,
1103                    T::RING_SPEC,
1104                    view.metadata.spec
1105                )
1106            ));
1107        }
1108        Ok(view)
1109    }
1110
1111    pub fn metadata(&self) -> &ShmRingMetadata {
1112        &self.metadata
1113    }
1114
1115    /// Last semantic version allocated by the ring at observation time.
1116    pub fn current_version(&self) -> u64 {
1117        self.header().version_counter.load(Ordering::Acquire)
1118    }
1119
1120    /// Current notification generation used by native readiness bridges.
1121    pub fn notification_generation(&self) -> u32 {
1122        self.header().notification_generation.load(Ordering::Acquire)
1123    }
1124
1125    /// Select one physical lane as a generic read-only frame source.
1126    pub fn lane(
1127        &self,
1128        lane: usize
1129    ) -> std::io::Result<ShmRingLaneView<'_>> {
1130        if lane >= self.metadata.lane_count {
1131            return Err(std::io::Error::new(
1132                std::io::ErrorKind::InvalidInput,
1133                format!("lane {lane} is outside SHM ring lane count {}", self.metadata.lane_count)
1134            ));
1135        }
1136        Ok(ShmRingLaneView { ring: self, lane })
1137    }
1138
1139    fn header(&self) -> &ShmRingHeader {
1140        // SAFETY: construction validated the mapped header.
1141        unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) }
1142    }
1143
1144    fn lane_header(
1145        &self,
1146        lane: usize
1147    ) -> &ShmLaneHeader {
1148        debug_assert!(lane < self.metadata.lane_count);
1149        // SAFETY: construction validated lane count and the complete geometry.
1150        unsafe {
1151            &*(self.region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
1152                as *const ShmLaneHeader)
1153        }
1154    }
1155
1156    fn slot_ptr(
1157        &self,
1158        lane: usize,
1159        counter: u64
1160    ) -> *mut ShmSlotHeader {
1161        debug_assert!(lane < self.metadata.lane_count);
1162        let slot = (counter as usize) & (self.metadata.spec.capacity - 1);
1163        // SAFETY: construction checked the complete persisted geometry and the
1164        // lane/slot indices are bounded above.
1165        unsafe {
1166            self.region
1167                .as_ptr()
1168                .add(self.slots_offset + lane * self.lane_stride + slot * self.slot_stride)
1169                .cast::<ShmSlotHeader>()
1170        }
1171    }
1172
1173    fn read_lane_state_at(
1174        &self,
1175        lane: usize,
1176        counter: u64
1177    ) -> crate::ring::cursor::RingRead {
1178        use crate::ring::cursor::RingRead;
1179
1180        let slot_ptr = self.slot_ptr(lane, counter);
1181        let Some(expected_committed) =
1182            counter.checked_mul(2).and_then(|value| value.checked_add(2))
1183        else {
1184            return RingRead::Unavailable;
1185        };
1186
1187        for _ in 0..3 {
1188            // SAFETY: `slot_ptr` addresses a validated slot in the mapping.
1189            let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
1190            if sequence < expected_committed {
1191                return if self.metadata.spec.topology == RingTopology::Shared {
1192                    RingRead::Pending
1193                } else {
1194                    RingRead::Unavailable
1195                };
1196            }
1197            if sequence > expected_committed {
1198                return RingRead::Unavailable;
1199            }
1200            // SAFETY: `slot_ptr` addresses a validated slot in the mapping.
1201            if let Some(frame) =
1202                unsafe { read_committed_frame(slot_ptr, self.metadata.spec.payload_capacity) }
1203            {
1204                let lane_matches = self.metadata.spec.topology != RingTopology::PerNode
1205                    || usize::from(frame.id.node()) == lane;
1206                return if frame.id.kind() == self.metadata.kind
1207                    && frame.id.counter() == counter
1208                    && lane_matches
1209                {
1210                    RingRead::Ready(frame)
1211                } else {
1212                    RingRead::Unavailable
1213                };
1214            }
1215        }
1216        RingRead::Unavailable
1217    }
1218}
1219
1220/// One physical lane of a [`ShmRingView`].
1221#[derive(Clone, Copy, Debug)]
1222pub struct ShmRingLaneView<'a> {
1223    ring: &'a ShmRingView,
1224    lane: usize
1225}
1226
1227impl ShmRingLaneView<'_> {
1228    pub fn index(self) -> usize {
1229        self.lane
1230    }
1231
1232    /// Snapshot the currently visible committed/reserved head.
1233    pub fn head(self) -> u64 {
1234        self.ring.lane_header(self.lane).write_pos.load(Ordering::Acquire)
1235    }
1236
1237    /// Counter range that may still be retained at the observed head.
1238    pub fn retained_range(self) -> std::ops::Range<u64> {
1239        let head = self.head();
1240        head.saturating_sub(self.ring.metadata.spec.capacity as u64)..head
1241    }
1242
1243    /// Read the exact counter if its frame is still committed in this lane.
1244    pub fn read_at(
1245        self,
1246        counter: u64
1247    ) -> Option<Frame> {
1248        match self.ring.read_lane_state_at(self.lane, counter) {
1249            crate::ring::cursor::RingRead::Ready(frame) => Some(frame),
1250            crate::ring::cursor::RingRead::Pending | crate::ring::cursor::RingRead::Unavailable => {
1251                None
1252            }
1253        }
1254    }
1255
1256    /// Read the frame immediately below the observed head.
1257    pub fn read_head(self) -> Option<Frame> {
1258        let head = self.head();
1259        head.checked_sub(1).and_then(|counter| self.read_at(counter))
1260    }
1261}
1262
1263impl crate::ring::cursor::RingFrameSource for ShmRingLaneView<'_> {
1264    fn kind(&self) -> u8 {
1265        self.ring.metadata.kind
1266    }
1267
1268    fn head(&self) -> u64 {
1269        (*self).head()
1270    }
1271
1272    fn capacity(&self) -> usize {
1273        self.ring.metadata.spec.capacity
1274    }
1275
1276    fn read_at(
1277        &self,
1278        counter: u64
1279    ) -> Option<Frame> {
1280        (*self).read_at(counter)
1281    }
1282
1283    fn read_state_at(
1284        &self,
1285        counter: u64
1286    ) -> crate::ring::cursor::RingRead {
1287        self.ring.read_lane_state_at(self.lane, counter)
1288    }
1289}
1290
1291// ─────────────────────────────────────────────────────────────────────
1292// ShmRingRegistry — per-fleet, per-KIND map of ShmRings
1293// ─────────────────────────────────────────────────────────────────────
1294
1295/// Type-keyed registry of [`ShmRing`]s held by a fleet. One ring per
1296/// `OrbitTyped::KIND` byte; created on demand the first time a kind
1297/// is published or queried.
1298pub struct ShmRingRegistry {
1299    fleet_name: String,
1300    fleet_capacity: u16,
1301    rings: dashmap::DashMap<u8, std::sync::Arc<ShmRing>>
1302}
1303
1304impl ShmRingRegistry {
1305    pub fn new(
1306        fleet_name: impl Into<String>,
1307        fleet_capacity: u16
1308    ) -> Self {
1309        Self { fleet_name: fleet_name.into(), fleet_capacity, rings: dashmap::DashMap::new() }
1310    }
1311
1312    /// Get-or-create the SHM ring for `kind`. Failure here means the
1313    /// SHM open or attach failed (permissions, name too long, etc.)
1314    /// and is propagated as `io::Error`.
1315    pub fn get_or_create_for<T: crate::OrbitTyped>(
1316        &self
1317    ) -> std::io::Result<std::sync::Arc<ShmRing>> {
1318        if let Some(entry) = self.rings.get(&T::KIND) {
1319            if entry.spec() != T::RING_SPEC {
1320                return Err(std::io::Error::new(
1321                    std::io::ErrorKind::InvalidData,
1322                    format!(
1323                        "OrbitTyped KIND {} was reused with ring spec {:?}; existing spec is {:?}",
1324                        T::KIND,
1325                        T::RING_SPEC,
1326                        entry.spec()
1327                    )
1328                ));
1329            }
1330            return Ok(entry.clone());
1331        }
1332        let ring = std::sync::Arc::new(ShmRing::open_or_create_for_fleet(
1333            &self.fleet_name,
1334            T::KIND,
1335            T::RING_SPEC,
1336            self.fleet_capacity
1337        )?);
1338        let entry = self.rings.entry(T::KIND).or_insert_with(|| ring.clone());
1339        if entry.spec() != T::RING_SPEC {
1340            return Err(std::io::Error::new(
1341                std::io::ErrorKind::InvalidData,
1342                format!(
1343                    "OrbitTyped KIND {} raced with ring spec {:?}; installed spec is {:?}",
1344                    T::KIND,
1345                    T::RING_SPEC,
1346                    entry.spec()
1347                )
1348            ));
1349        }
1350        Ok(entry.clone())
1351    }
1352
1353    /// Look up a ring that has already been created for `kind`.
1354    /// Returns `None` if no such ring exists yet.
1355    pub fn lookup(
1356        &self,
1357        kind: u8
1358    ) -> Option<std::sync::Arc<ShmRing>> {
1359        self.rings.get(&kind).map(|e| e.clone())
1360    }
1361}
1362
1363/// Read a slot's content; returns `None` if the seq indicates an
1364/// in-flight write or if pre/post seqs disagree (torn read).
1365///
1366/// # Safety
1367///
1368/// `slot_ptr` must point at a valid `ShmSlot` mapped into our
1369/// address space and aligned per the `repr(C, align(64))` layout.
1370unsafe fn read_committed_frame(
1371    slot_ptr: *mut ShmSlotHeader,
1372    payload_capacity: usize
1373) -> Option<Frame> {
1374    let slot = unsafe { &*slot_ptr };
1375    let seq_pre = slot.seq.load(Ordering::Acquire);
1376    if seq_pre == 0 {
1377        // never written
1378        return None;
1379    }
1380    if seq_pre & 1 == 1 {
1381        // writer in progress
1382        return None;
1383    }
1384
1385    // Read content fields. `Relaxed` throughout: the seq load above and the
1386    // fence below are what order this, and a value read here is only trusted
1387    // once the two seqs agree.
1388    let id = NetId64::from_raw(slot.id.load(Ordering::Relaxed));
1389    let kind = slot.kind.load(Ordering::Relaxed);
1390    let ver = slot.ver.load(Ordering::Relaxed);
1391    let payload_len = slot.payload_len.load(Ordering::Relaxed) as usize;
1392    if payload_len > payload_capacity {
1393        // corrupt — bail
1394        return None;
1395    }
1396    // `with_capacity` + `push` rather than `vec![0; len]` + overwrite: the
1397    // zeroing would be written over by the very next loop, and a walking reader
1398    // over a 16k-slot ring pays that twice for every byte it reads. The push
1399    // cannot reallocate — the capacity is reserved above.
1400    let payload_src = unsafe { ShmRing::payload_ptr(slot_ptr) };
1401    let mut payload_buf = Vec::with_capacity(payload_len);
1402    for index in 0..payload_len {
1403        payload_buf.push(unsafe { (*payload_src.add(index)).load(Ordering::Relaxed) });
1404    }
1405
1406    // The mirror of the writer's fence. `Acquire` on a load orders the accesses
1407    // that come *after* it, so reading the closing seq with `Acquire` would
1408    // leave the content reads above free to be reordered past it — and the
1409    // comparison below would then be comparing against a slot it never actually
1410    // read. An acquire fence orders those reads ahead of the load that follows,
1411    // which is the guarantee this check is asking for; the load itself needs no
1412    // ordering of its own once the fence is there.
1413    fence(Ordering::Acquire);
1414    let seq_post = slot.seq.load(Ordering::Relaxed);
1415    if seq_pre != seq_post {
1416        // torn write — caller can retry
1417        return None;
1418    }
1419
1420    Some(Frame { id, kind, ver, payload: Bytes::from(payload_buf) })
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425    use std::sync::atomic::{AtomicU64, Ordering};
1426
1427    use nix::sys::wait::{WaitStatus, waitpid};
1428    use nix::unistd::{ForkResult, fork};
1429
1430    use super::*;
1431    use crate::ring::cursor::{RingCursor, poll_ring};
1432
1433    #[test]
1434    fn cursor_retries_a_claimed_slot_after_it_commits() {
1435        static TEST_ID: AtomicU64 = AtomicU64::new(0);
1436
1437        let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1438        let fleet_name = format!("p{:x}{test_id:x}", std::process::id());
1439        let ring = ShmRing::open_or_create(&fleet_name, 199, RingSpec::new(4, 16))
1440            .expect("create test ring");
1441        ring.reset();
1442        let slot_ptr = ring.slot_ptr(0, 0);
1443
1444        ring.lane_header(0).write_pos.store(1, Ordering::Release);
1445        unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1446
1447        let mut cursor = RingCursor::from_start();
1448        let pending = poll_ring(&ring, &mut cursor);
1449        assert!(pending.is_empty());
1450        assert_eq!(cursor.next_counter(), 0);
1451
1452        let payload = b"ready";
1453        unsafe {
1454            let slot = &*slot_ptr;
1455            slot.id.store(NetId64::make(199, NodeId::ZERO.get(), 0).raw(), Ordering::Relaxed);
1456            slot.ver.store(7, Ordering::Relaxed);
1457            slot.payload_len.store(payload.len() as u32, Ordering::Relaxed);
1458            slot.kind.store(1, Ordering::Relaxed);
1459            let bytes = ShmRing::payload_ptr(slot_ptr);
1460            for (index, byte) in payload.iter().enumerate() {
1461                (*bytes.add(index)).store(*byte, Ordering::Relaxed);
1462            }
1463        }
1464        unsafe { &*slot_ptr }.seq.store(2, Ordering::Release);
1465
1466        let committed = poll_ring(&ring, &mut cursor);
1467        assert_eq!(committed.frames.len(), 1);
1468        assert_eq!(&committed.frames[0].payload[..], payload);
1469        assert_eq!(cursor.next_counter(), 1);
1470
1471        ring.unlink().expect("unlink test ring");
1472    }
1473
1474    #[test]
1475    fn per_node_head_ignores_a_writer_that_dies_before_commit() {
1476        static TEST_ID: AtomicU64 = AtomicU64::new(0);
1477
1478        let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1479        let fleet_name = format!("d{:x}{test_id:x}", std::process::id());
1480        let spec = RingSpec::per_node(4, 16);
1481        let abandoned = ShmRing::open_or_create_for_fleet(&fleet_name, 198, spec, 2)
1482            .expect("create per-node test ring");
1483        abandoned.reset();
1484
1485        let slot_ptr = abandoned.slot_ptr(1, 0);
1486        unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1487        assert_eq!(abandoned.lane_head(NodeId::new(1)), 0);
1488
1489        let replacement = ShmRing::open_or_create_for_fleet(&fleet_name, 198, spec, 2)
1490            .expect("replacement attaches");
1491        let id = replacement
1492            .write(NodeId::new(1), 1, 9, Bytes::from_static(b"recovered"))
1493            .expect("replacement commits");
1494
1495        assert_eq!(id.counter(), 0);
1496        assert_eq!(replacement.lane_head(NodeId::new(1)), 1);
1497        assert_eq!(&replacement.read(id).expect("frame visible").payload[..], b"recovered");
1498
1499        replacement.unlink().expect("unlink test ring");
1500    }
1501
1502    #[test]
1503    fn per_node_lane_serializes_concurrent_local_publishers() {
1504        static TEST_ID: AtomicU64 = AtomicU64::new(0);
1505
1506        let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1507        let fleet_name = format!("c{:x}{test_id:x}", std::process::id());
1508        let ring = std::sync::Arc::new(
1509            ShmRing::open_or_create_for_fleet(&fleet_name, 197, RingSpec::per_node(512, 0), 2)
1510                .expect("create concurrent writer ring")
1511        );
1512        ring.reset();
1513
1514        let mut writers = Vec::new();
1515        for _ in 0..4 {
1516            let ring = ring.clone();
1517            writers.push(std::thread::spawn(move || {
1518                (0..64)
1519                    .map(|_| {
1520                        ring.write(NodeId::new(1), 1, 0, Bytes::new()).expect("publish").counter()
1521                    })
1522                    .collect::<Vec<_>>()
1523            }));
1524        }
1525
1526        let mut counters = writers
1527            .into_iter()
1528            .flat_map(|writer| writer.join().expect("writer joins"))
1529            .collect::<Vec<_>>();
1530        counters.sort_unstable();
1531        assert_eq!(counters, (0..256).collect::<Vec<_>>());
1532        assert_eq!(ring.lane_head(NodeId::new(1)), 256);
1533
1534        ring.unlink().expect("unlink test ring");
1535    }
1536
1537    #[test]
1538    fn shared_ordered_recovers_after_a_locked_writer_process_dies() {
1539        static TEST_ID: AtomicU64 = AtomicU64::new(0);
1540
1541        let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1542        let fleet_name = format!("o{:x}{test_id:x}", std::process::id());
1543        let spec = RingSpec::shared_ordered(4, 16);
1544        let ring = ShmRing::open_or_create_for_fleet(&fleet_name, 196, spec, 2)
1545            .expect("create shared-ordered test ring");
1546        ring.reset();
1547
1548        match unsafe { fork() }.expect("fork test writer") {
1549            ForkResult::Child => {
1550                // Use the handle inherited while it was idle. It must not carry
1551                // a persistent lock descriptor across fork; the child opens a
1552                // fresh file description for this critical section.
1553                let _lock = ring.region.lock_exclusive().expect("child locks ring");
1554                let slot_ptr = ring.slot_ptr(0, 0);
1555                unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1556
1557                // Exit without destructors: the kernel, not Rust cleanup,
1558                // must release the descriptor lock.
1559                unsafe { libc::_exit(0) };
1560            }
1561            ForkResult::Parent { child } => {
1562                let status = waitpid(child, None).expect("wait for abandoned writer");
1563                assert!(matches!(status, WaitStatus::Exited(_, 0)));
1564
1565                // A separately opened handle must acquire the lock after the
1566                // child dies. If the original region retained an idle flock fd,
1567                // fork would duplicate its open-file-description into the child
1568                // and the dead writer's lock would still be attached here.
1569                let replacement = ShmRing::open_or_create_for_fleet(&fleet_name, 196, spec, 2)
1570                    .expect("replacement attaches");
1571                let recovered_lock = replacement
1572                    .region
1573                    .try_lock_exclusive()
1574                    .expect("kernel released dead writer lock");
1575                drop(recovered_lock);
1576
1577                let id = replacement
1578                    .write(NodeId::new(1), 1, 9, Bytes::from_static(b"recovered"))
1579                    .expect("replacement commits counter zero");
1580                assert_eq!(id.counter(), 0);
1581                assert_eq!(replacement.head(), 1);
1582                assert_eq!(
1583                    &replacement.read(id).expect("recovered frame").payload[..],
1584                    b"recovered"
1585                );
1586
1587                replacement.unlink().expect("unlink shared-ordered test ring");
1588            }
1589        }
1590    }
1591}