Skip to main content

orbit_stream/
table.rs

1//! The table behind a `Streams` handle: the mapped segment (or its in-memory
2//! twin), the lane this process allocates in, and the process-local
3//! readiness pieces. One table per process per fleet and node, so two
4//! handles in one process share one driver and one waker registry.
5
6use std::collections::HashMap;
7use std::mem::size_of;
8use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
9use std::sync::{Arc, LazyLock, Mutex, Weak};
10
11use orbit_core::readiness::{Readiness, Signal};
12#[cfg(unix)]
13use orbit_core::shm::{ShmRegion, ring_segment_name};
14use orbit_core::{Fleet, OrbitEpoch};
15
16use crate::layout::{
17    Direction, Doorbell, FLAG_READER_GONE, FLAG_RESET, Geometry, Header,
18    SIDE_CLAIMED, SIDE_DEAD, SLOT_EMPTY, SLOT_LIVE, Slot,
19};
20use crate::wake::{Doorstep, Driver, Interest, Registry};
21use crate::{Error, Incarnation, Result, StreamSpec, lock_unpoisoned};
22
23enum Backing {
24    Memory(AlignedBytes),
25    #[cfg(unix)]
26    Shm(ShmRegion),
27}
28
29/// Heap bytes aligned like the mapping, so slots keep their cache lines in
30/// the standalone backend too.
31struct AlignedBytes {
32    ptr: *mut u8,
33    layout: std::alloc::Layout,
34}
35
36impl AlignedBytes {
37    fn zeroed(size: usize) -> Self {
38        let layout = std::alloc::Layout::from_size_align(size, 64)
39            .expect("segment layout");
40        // SAFETY: the layout has a nonzero size (a header at least).
41        let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
42        assert!(!ptr.is_null(), "stream table allocation failed");
43        Self { ptr, layout }
44    }
45}
46
47impl Drop for AlignedBytes {
48    fn drop(&mut self) {
49        // SAFETY: allocated with this layout in `zeroed`.
50        unsafe { std::alloc::dealloc(self.ptr, self.layout) }
51    }
52}
53
54// SAFETY: the bytes are only ever read through atomics or under the
55// direction ownership rules; the pointer is not shared outside `Table`.
56unsafe impl Send for AlignedBytes {}
57unsafe impl Sync for AlignedBytes {}
58
59pub(crate) struct Table {
60    /// Held so the fleet's address, which keys the in-memory registry,
61    /// cannot be reused by another fleet while this table lives.
62    _fleet: Arc<Fleet>,
63    backing: Backing,
64    #[cfg(unix)]
65    group: Option<orbit_core::shm::GroupAccess>,
66    geometry: Geometry,
67    /// The segment's kind, from the spec this table was opened with.
68    kind: u8,
69    node: u16,
70    incarnation: u64,
71    /// Serialises this process's allocations in its own lane. One live
72    /// process per node id is already the fleet's rule for per-node rings,
73    /// so no cross-process lock is needed here.
74    allocate: Mutex<usize>,
75    pub(crate) registry: Registry,
76    pub(crate) capacity: crate::capacity::Waiters,
77    capacity_seen: AtomicU32,
78    driver: Mutex<Option<Driving>>,
79    /// Where this table rings a node whose doorbell a socket answers.
80    #[cfg(unix)]
81    bell: Option<crate::bell::Bell>,
82    /// The driver's end of this process's readiness descriptor, once
83    /// somebody has asked for one. Read on every drain, so it is a
84    /// `OnceLock` rather than a lock.
85    readiness: std::sync::OnceLock<Signal>,
86}
87
88impl Table {
89    fn base(&self) -> *mut u8 {
90        match &self.backing {
91            Backing::Memory(bytes) => bytes.ptr,
92            #[cfg(unix)]
93            Backing::Shm(region) => region.as_ptr(),
94        }
95    }
96
97    pub(crate) fn is_shared(&self) -> bool {
98        match &self.backing {
99            Backing::Memory(_) => false,
100            #[cfg(unix)]
101            Backing::Shm(_) => true,
102        }
103    }
104
105    pub(crate) fn geometry(&self) -> &Geometry {
106        &self.geometry
107    }
108
109    /// The kind this table's segment lives under.
110    pub(crate) fn kind(&self) -> u8 {
111        self.kind
112    }
113
114    pub(crate) fn node(&self) -> u16 {
115        self.node
116    }
117
118    pub(crate) fn incarnation(&self) -> u64 {
119        self.incarnation
120    }
121
122    fn header(&self) -> &Header {
123        // SAFETY: written at creation, validated at open; mapping outlives self.
124        unsafe { &*self.base().cast::<Header>() }
125    }
126
127    pub(crate) fn epoch(&self) -> u64 {
128        self.header().epoch.load(Ordering::Acquire)
129    }
130
131    pub(crate) fn own_doorbell(&self) -> &Doorbell {
132        self.doorbell(usize::from(self.node))
133    }
134
135    fn doorbell(
136        &self,
137        node: usize,
138    ) -> &Doorbell {
139        debug_assert!(node < self.geometry.fleet_capacity);
140        // SAFETY: `fleet_capacity` doorbells follow the header, zeroed at
141        // creation; every field is atomic.
142        unsafe {
143            &*self
144                .base()
145                .add(
146                    self.geometry.doorbells_offset
147                        + node * size_of::<Doorbell>(),
148                )
149                .cast::<Doorbell>()
150        }
151    }
152
153    fn bitmap_word(
154        &self,
155        offset: usize,
156        node: usize,
157        word: usize,
158    ) -> &AtomicU64 {
159        debug_assert!(
160            node < self.geometry.fleet_capacity
161                && word < self.geometry.bitmap_words
162        );
163        // SAFETY: both bitmaps sit between the doorbells and the slots;
164        // atomic words only.
165        unsafe {
166            &*self
167                .base()
168                .add(
169                    offset
170                        + (node * self.geometry.bitmap_words + word)
171                            * size_of::<AtomicU64>(),
172                )
173                .cast::<AtomicU64>()
174        }
175    }
176
177    fn pending_word(
178        &self,
179        node: usize,
180        word: usize,
181    ) -> &AtomicU64 {
182        self.bitmap_word(self.geometry.pending_offset, node, word)
183    }
184
185    fn offer_word(
186        &self,
187        node: usize,
188        word: usize,
189    ) -> &AtomicU64 {
190        self.bitmap_word(self.geometry.offers_offset, node, word)
191    }
192
193    pub(crate) fn slots(&self) -> &[Slot] {
194        // SAFETY: `total_slots` slots follow the bitmaps, zeroed at creation;
195        // every field is atomic, so shared references are sound.
196        unsafe {
197            std::slice::from_raw_parts(
198                self.base().add(self.geometry.slots_offset).cast::<Slot>(),
199                self.geometry.total_slots,
200            )
201        }
202    }
203
204    /// The bytes of one direction's ring. Only the direction's writer
205    /// writes and only its reader reads, at disjoint positions.
206    pub(crate) fn buffer(
207        &self,
208        slot: usize,
209        direction: usize,
210    ) -> *mut u8 {
211        // SAFETY: inside the segment by construction of `Geometry`.
212        unsafe { self.base().add(self.geometry.buffer_offset(slot, direction)) }
213    }
214
215    pub(crate) fn has_capacity(&self) -> bool {
216        let start = usize::from(self.node) * self.geometry.lane_capacity;
217        self.slots()[start..start + self.geometry.lane_capacity]
218            .iter()
219            .any(|slot| slot.state.load(Ordering::Acquire) == SLOT_EMPTY)
220    }
221
222    /// Take a free slot in this process's lane and install a fresh stream.
223    /// Returns the slot index and its generation.
224    pub(crate) fn allocate(&self) -> Result<(usize, u32)> {
225        let mut hint = lock_unpoisoned(&self.allocate);
226        let lane_capacity = self.geometry.lane_capacity;
227        let lane_start = usize::from(self.node) * lane_capacity;
228        let slots = self.slots();
229        for offset in 0..lane_capacity {
230            let index = lane_start + ((*hint + offset) & (lane_capacity - 1));
231            let slot = &slots[index];
232            if slot.state.load(Ordering::Acquire) == SLOT_EMPTY
233                && let Some(generation) =
234                    slot.install(self.node, self.incarnation)
235            {
236                *hint = (index - lane_start + 1) & (lane_capacity - 1);
237                return Ok((index, generation));
238            }
239        }
240        Err(Error::Full { capacity: lane_capacity })
241    }
242
243    /// After anything changed on `direction` of `slot`: count it for
244    /// blocking waiters, then tell whichever process `ring` names. Blocking
245    /// waiters are always counted; they park on the word itself and are woken
246    /// only when present.
247    pub(crate) fn notify(
248        &self,
249        index: usize,
250        slot: &Slot,
251        direction: &Direction,
252        ring: Ring,
253    ) {
254        direction.changes.fetch_add(1, Ordering::SeqCst);
255        if direction.waiters.load(Ordering::SeqCst) > 0 {
256            crate::wake_on(&direction.changes);
257        }
258        let sides: &[usize] = match ring {
259            Ring::Nobody => return,
260            Ring::Side(side) => &[side],
261            Ring::Both => &[0, 1],
262        };
263        if !self.is_shared() {
264            self.registry.wake(index);
265            self.signal_readiness();
266            return;
267        }
268        let mut rung = usize::MAX;
269        for &side in sides {
270            let node = usize::from(slot.node[side].load(Ordering::Acquire));
271            if node < self.geometry.fleet_capacity && node != rung {
272                self.ring(node, Some(index));
273                rung = node;
274            }
275        }
276    }
277
278    /// Set the slot's bit for `node` (when there is one) and ring its
279    /// doorbell if a driver or a blocking waiter is listening.
280    fn ring(
281        &self,
282        node: usize,
283        pending: Option<usize>,
284    ) {
285        if let Some(index) = pending {
286            self.pending_word(node, index / 64)
287                .fetch_or(1 << (index % 64), Ordering::SeqCst);
288        }
289        let doorbell = self.doorbell(node);
290        doorbell.generation.fetch_add(1, Ordering::SeqCst);
291        let listening = doorbell.listening.load(Ordering::SeqCst);
292        #[cfg(unix)]
293        let listening = {
294            use crate::bell::ARMED;
295            if listening & ARMED != 0
296                && doorbell.listening.fetch_and(!ARMED, Ordering::SeqCst)
297                    & ARMED
298                    != 0
299                && let Some(bell) = &self.bell
300            {
301                bell.ring(node);
302            }
303            listening & !ARMED
304        };
305        if listening > 0 {
306            crate::wake_on(&doorbell.generation);
307        }
308    }
309
310    /// A creator's EMPTY slot or released peer can now be used. Unobserved
311    /// releases add no generation update or kernel notification.
312    pub(crate) fn notify_capacity(
313        &self,
314        index: usize,
315    ) {
316        let creator = index / self.geometry.lane_capacity;
317        let bell = self.doorbell(creator);
318        if bell.capacity_waiting.load(Ordering::SeqCst) == 0 {
319            return;
320        }
321        bell.capacity_changes.fetch_add(1, Ordering::SeqCst);
322        if self.is_shared() {
323            self.ring(creator, None);
324        } else {
325            self.capacity.wake();
326            self.signal_readiness();
327        }
328    }
329
330    /// Put the stream in `node`'s offer bitmap and ring it.
331    pub(crate) fn offer(
332        &self,
333        index: usize,
334        node: u16,
335    ) -> Result<()> {
336        let node = usize::from(node);
337        if node >= self.geometry.fleet_capacity {
338            return Err(Error::Malformed(format!(
339                "node {node} is outside the fleet"
340            )));
341        }
342        // One process holds every node of a memory table: the offer goes to
343        // whoever takes offers here, and the local waker is told directly.
344        let node = if self.is_shared() { node } else { usize::from(self.node) };
345        self.offer_word(node, index / 64)
346            .fetch_or(1 << (index % 64), Ordering::SeqCst);
347        self.ring(node, None);
348        if !self.is_shared() {
349            self.registry.wake_offer();
350            self.signal_readiness();
351        }
352        Ok(())
353    }
354
355    /// Take one offered slot for this node, if any, clearing its bit.
356    pub(crate) fn take_offer(&self) -> Option<usize> {
357        let node = usize::from(self.node);
358        for word in 0..self.geometry.bitmap_words {
359            let place = self.offer_word(node, word);
360            let mut bits = place.load(Ordering::SeqCst);
361            while bits != 0 {
362                let bit = bits.trailing_zeros() as u64;
363                let mask = 1_u64 << bit;
364                if place.fetch_and(!mask, Ordering::SeqCst) & mask != 0 {
365                    let index = word * 64 + bit as usize;
366                    if index < self.geometry.total_slots {
367                        return Some(index);
368                    }
369                }
370                bits &= !mask;
371            }
372        }
373        None
374    }
375
376    fn has_offer(&self) -> bool {
377        let node = usize::from(self.node);
378        (0..self.geometry.bitmap_words)
379            .any(|word| self.offer_word(node, word).load(Ordering::SeqCst) != 0)
380    }
381
382    /// Park the calling thread until an offer arrives for this node.
383    /// The same wait, bounded: `false` is the timeout and nothing else.
384    #[cfg(any(
385        target_os = "linux",
386        target_os = "freebsd",
387        target_os = "macos"
388    ))]
389    pub(crate) fn wait_offer_timeout(
390        &self,
391        timeout: std::time::Duration,
392    ) -> Result<bool> {
393        let doorbell = self.doorbell(usize::from(self.node));
394        if self.has_offer() {
395            return Ok(true);
396        }
397        doorbell.listening.fetch_add(1, Ordering::SeqCst);
398        let seen = doorbell.generation.load(Ordering::SeqCst);
399        let outcome = if self.has_offer() {
400            Ok(true)
401        } else {
402            crate::wait_on_timeout(&doorbell.generation, seen, timeout)
403        };
404        doorbell.listening.fetch_sub(1, Ordering::SeqCst);
405        outcome
406    }
407
408    pub(crate) fn wait_offer(&self) -> Result<()> {
409        let doorbell = self.doorbell(usize::from(self.node));
410        loop {
411            if self.has_offer() {
412                return Ok(());
413            }
414            doorbell.listening.fetch_add(1, Ordering::SeqCst);
415            let seen = doorbell.generation.load(Ordering::SeqCst);
416            let outcome = if self.has_offer() {
417                Ok(())
418            } else {
419                crate::wait_on(&doorbell.generation, seen)
420            };
421            doorbell.listening.fetch_sub(1, Ordering::SeqCst);
422            outcome?;
423        }
424    }
425
426    /// Park a task on `slot`; in a fleet this also starts the process's
427    /// driver the first time anyone asks.
428    pub(crate) fn register(
429        self: &Arc<Self>,
430        slot: usize,
431        interest: Interest,
432        waker: &std::task::Waker,
433    ) -> Result<()> {
434        self.registry.register(slot, interest, waker);
435        self.ensure_driver()
436    }
437
438    pub(crate) fn register_offer(
439        self: &Arc<Self>,
440        waker: &std::task::Waker,
441    ) -> Result<()> {
442        self.registry.register_offer(waker);
443        self.ensure_driver()
444    }
445
446    /// Hand out this table's readiness descriptor, once. The driver is
447    /// what signals it, so asking for one starts it.
448    pub(crate) fn take_readiness(self: &Arc<Self>) -> Result<Readiness> {
449        let (readiness, signal) = orbit_core::readiness::pair()?;
450        self.readiness.set(signal).map_err(|_| {
451            Error::Malformed(
452                "this process already took the stream table's readiness descriptor".to_owned()
453            )
454        })?;
455        self.ensure_driver()?;
456        Ok(readiness)
457    }
458
459    fn signal_readiness(&self) {
460        if let Some(signal) = self.readiness.get() {
461            // A consumer that has gone away is not this table's problem;
462            // its descriptor stops mattering with it.
463            let _ = signal.signal();
464        }
465    }
466
467    pub(crate) fn ensure_driver(self: &Arc<Self>) -> Result<()> {
468        if self.is_shared() {
469            let mut driver = lock_unpoisoned(&self.driver);
470            if driver.is_none() {
471                #[cfg(all(unix, feature = "tokio"))]
472                if let (Some(bell), Ok(runtime)) =
473                    (&self.bell, tokio::runtime::Handle::try_current())
474                {
475                    *driver = Some(Driving::Reactor(
476                        crate::bell::ReactorDriver::start(self, bell, &runtime)
477                            .map_err(Error::Io)?,
478                    ));
479                    return Ok(());
480                }
481                *driver = Some(Driving::Thread(Driver::start(
482                    Arc::as_ptr(self),
483                    format!("orbit-stream-{}-driver", self.node),
484                )?));
485            }
486        }
487        Ok(())
488    }
489
490    /// A confirmed death: every side that incarnation of `node` held is
491    /// finished. Its writes reset, its reads are gone, the other holder is
492    /// woken; the slot itself empties only when nobody holds it any more.
493    pub(crate) fn node_dead(
494        &self,
495        node: u16,
496        incarnation: u64,
497    ) {
498        self.end_sides(node, |held| held == incarnation);
499        let doorbell = self.doorbell(usize::from(node));
500        if doorbell.incarnation.load(Ordering::SeqCst) == incarnation {
501            doorbell.listening.store(0, Ordering::SeqCst);
502            doorbell.capacity_waiting.store(0, Ordering::SeqCst);
503            doorbell.incarnation.store(0, Ordering::SeqCst);
504        }
505    }
506
507    /// This process took over its node: every side an earlier life of the
508    /// node held is finished, as [`Self::node_dead`] finishes one life's.
509    pub(crate) fn supersede(&self) {
510        let mine = self.incarnation;
511        self.end_sides(self.node, |held| held != mine);
512        let doorbell = self.doorbell(usize::from(self.node));
513        if doorbell.incarnation.load(Ordering::SeqCst) != mine {
514            doorbell.listening.store(0, Ordering::SeqCst);
515            doorbell.capacity_waiting.store(0, Ordering::SeqCst);
516            doorbell.incarnation.store(0, Ordering::SeqCst);
517        }
518    }
519
520    fn end_sides(
521        &self,
522        node: u16,
523        dead: impl Fn(u64) -> bool,
524    ) {
525        for (index, slot) in self.slots().iter().enumerate() {
526            if slot.state.load(Ordering::Acquire) != SLOT_LIVE {
527                continue;
528            }
529            let mut touched = false;
530            for side in 0..2 {
531                if slot.claimed[side].load(Ordering::SeqCst) != SIDE_CLAIMED
532                    || slot.node[side].load(Ordering::Acquire) != node
533                    || !dead(slot.incarnation[side].load(Ordering::Acquire))
534                {
535                    continue;
536                }
537                if slot.claimed[side]
538                    .compare_exchange(
539                        SIDE_CLAIMED,
540                        SIDE_DEAD,
541                        Ordering::SeqCst,
542                        Ordering::SeqCst,
543                    )
544                    .is_err()
545                {
546                    continue;
547                }
548                touched = true;
549                let writes = &slot.directions[side];
550                let reads = &slot.directions[1 - side];
551                writes.flags.fetch_or(FLAG_RESET, Ordering::SeqCst);
552                reads.flags.fetch_or(FLAG_READER_GONE, Ordering::SeqCst);
553                self.notify(index, slot, writes, Ring::Both);
554                self.notify(index, slot, reads, Ring::Both);
555            }
556            if touched
557                && slot
558                    .claimed
559                    .iter()
560                    .all(|side| side.load(Ordering::SeqCst) != SIDE_CLAIMED)
561            {
562                let _ = slot.state.compare_exchange(
563                    SLOT_LIVE,
564                    SLOT_EMPTY,
565                    Ordering::SeqCst,
566                    Ordering::SeqCst,
567                );
568            }
569            if touched {
570                self.notify_capacity(index);
571            }
572        }
573    }
574
575    /// Clear every slot, doorbell and bitmap during quiescent owner boot and
576    /// start a new epoch, so every ticket minted before is refused.
577    pub(crate) fn reset_all(&self) {
578        let mut hint = lock_unpoisoned(&self.allocate);
579        *hint = 0;
580        let header = self.header();
581        let epoch = header.epoch.load(Ordering::Acquire);
582        header.epoch.store(next_epoch(epoch), Ordering::Release);
583        for slot in self.slots() {
584            slot.state.store(SLOT_EMPTY, Ordering::Release);
585            slot.generation.store(0, Ordering::Relaxed);
586            for direction in &slot.directions {
587                direction.changes.fetch_add(1, Ordering::SeqCst);
588                crate::wake_on(&direction.changes);
589            }
590        }
591        for node in 0..self.geometry.fleet_capacity {
592            for word in 0..self.geometry.bitmap_words {
593                self.pending_word(node, word).store(0, Ordering::Relaxed);
594                self.offer_word(node, word).store(0, Ordering::Relaxed);
595            }
596        }
597        self.registry.clear();
598        self.capacity.wake();
599    }
600
601    #[cfg(unix)]
602    pub(crate) fn unlink(&self) -> Result<()> {
603        match &self.backing {
604            Backing::Memory(_) => {
605                self.reset_all();
606                Ok(())
607            }
608            Backing::Shm(region) => match &self.group {
609                Some(access) => access.unlink_region(region),
610                None => region.unlink(),
611            }
612            .map_err(Error::Io),
613        }
614    }
615}
616
617/// A fresh epoch: the clock, unless the previous epoch is already ahead
618/// of it, so a recreated segment never repeats an epoch a ticket may hold.
619fn next_epoch(previous: u64) -> u64 {
620    OrbitEpoch::now().as_unix_ms().max(previous + 1)
621}
622
623impl Doorstep for Table {
624    fn generation(&self) -> &AtomicU32 {
625        &self.doorbell(usize::from(self.node)).generation
626    }
627
628    fn listening(
629        &self,
630        delta: i32,
631    ) {
632        let doorbell = self.doorbell(usize::from(self.node));
633        if delta > 0 {
634            doorbell.incarnation.store(self.incarnation, Ordering::SeqCst);
635            doorbell.listening.fetch_add(1, Ordering::SeqCst);
636        } else {
637            doorbell.listening.fetch_sub(1, Ordering::SeqCst);
638        }
639    }
640
641    fn drain(&self) {
642        let node = usize::from(self.node);
643        let changed =
644            self.own_doorbell().capacity_changes.load(Ordering::SeqCst);
645        if self.capacity_seen.load(Ordering::Relaxed) != changed {
646            self.capacity_seen.store(changed, Ordering::Relaxed);
647            self.capacity.wake();
648        }
649        for word in 0..self.geometry.bitmap_words {
650            let mut bits =
651                self.pending_word(node, word).swap(0, Ordering::SeqCst);
652            while bits != 0 {
653                let bit = bits.trailing_zeros() as usize;
654                bits &= bits - 1;
655                let index = word * 64 + bit;
656                if index < self.geometry.total_slots {
657                    self.registry.wake(index);
658                }
659            }
660        }
661        if self.has_offer() {
662            self.registry.wake_offer();
663        }
664        // After the bits are taken, never before: a consumer that drains
665        // the descriptor and re-tries its streams cannot miss what this
666        // pass just made ready.
667        self.signal_readiness();
668    }
669}
670
671impl Drop for Table {
672    fn drop(&mut self) {
673        match lock_unpoisoned(&self.driver).take() {
674            Some(Driving::Thread(mut driver)) => driver.stop(self.generation()),
675            #[cfg(all(unix, feature = "tokio"))]
676            Some(Driving::Reactor(driver)) => {
677                if let Some(bell) = &self.bell {
678                    driver.stop(self.own_doorbell(), bell);
679                }
680            }
681            None => {}
682        }
683    }
684}
685
686/// What answers this process's doorbell: the thread parked on the
687/// generation word, or a task on the runtime woken through the bell.
688enum Driving {
689    Thread(Driver),
690    #[cfg(all(unix, feature = "tokio"))]
691    Reactor(crate::bell::ReactorDriver),
692}
693
694#[derive(Hash, PartialEq, Eq)]
695enum Key {
696    Memory(usize, u8),
697    #[cfg(unix)]
698    Shm(String, u16),
699    #[cfg(unix)]
700    Group(String, u16, orbit_core::shm::GroupAccess),
701}
702
703static TABLES: LazyLock<Mutex<HashMap<Key, Weak<Table>>>> =
704    LazyLock::new(|| Mutex::new(HashMap::new()));
705
706/// Recovery has no allocation identity of its own. Reuse any local mapping
707/// of this segment, or attach a temporary view without starting a driver.
708#[cfg(unix)]
709pub(crate) fn recover_shm(
710    name: &str,
711    fleet_capacity: u16,
712    spec: StreamSpec,
713    node: u16,
714    incarnation: u64,
715) -> Result<()> {
716    let segment = ring_segment_name(name, spec.kind);
717    let held =
718        lock_unpoisoned(&TABLES).iter().find_map(|(key, table)| match key {
719            Key::Shm(mapped, _) if mapped == &segment => table.upgrade(),
720            _ => None,
721        });
722    let table = match held {
723        Some(table) => {
724            if table.geometry.fleet_capacity != usize::from(fleet_capacity)
725                || table.geometry.lane_capacity != spec.lane_capacity
726                || table.geometry.buffer_bytes != spec.buffer_bytes
727            {
728                return Err(Error::Malformed(
729                    "recovery stream geometry differs".into(),
730                ));
731            }
732            table
733        }
734        None => {
735            let fleet = Arc::new(
736                Fleet::join_shm_as(
737                    name,
738                    fleet_capacity,
739                    orbit_core::NodeId::ZERO,
740                )
741                .map_err(|error| {
742                    Error::Io(std::io::Error::other(error.to_string()))
743                })?,
744            );
745            open_inner(&fleet, Incarnation::new(0), spec, true, None)?
746        }
747    };
748    table.node_dead(node, incarnation);
749    Ok(())
750}
751
752/// The table `spec` names for `fleet`, shared by every handle in this
753/// process. The kind is part of the identity in both backings: two specs
754/// are two tables, in one process as in the fleet.
755pub(crate) fn open(
756    fleet: &Arc<Fleet>,
757    incarnation: Incarnation,
758    spec: StreamSpec,
759) -> Result<Arc<Table>> {
760    open_inner(fleet, incarnation, spec, false, None)
761}
762
763#[cfg(unix)]
764pub(crate) fn open_group(
765    name: &str,
766    capacity: u16,
767    node: orbit_core::NodeId,
768    incarnation: Incarnation,
769    spec: StreamSpec,
770    access: orbit_core::shm::GroupAccess,
771    recovery: bool,
772) -> Result<Arc<Table>> {
773    let fleet = Arc::new(
774        Fleet::join_as(name, capacity, node)
775            .map_err(|e| Error::Io(std::io::Error::other(e.to_string())))?,
776    );
777    access.prepare()?;
778    crate::bell::Bell::validate_group_address(
779        &access,
780        &access.segment_name(name, spec.kind),
781        usize::from(capacity - 1),
782    )?;
783    open_inner(&fleet, incarnation, spec, recovery, Some(access))
784}
785
786fn open_inner(
787    fleet: &Arc<Fleet>,
788    incarnation: Incarnation,
789    spec: StreamSpec,
790    recovery: bool,
791    #[cfg(unix)] group: Option<orbit_core::shm::GroupAccess>,
792    #[cfg(not(unix))] _group: Option<()>,
793) -> Result<Arc<Table>> {
794    if !crate::waits_supported() {
795        return Err(Error::Io(std::io::Error::new(
796            std::io::ErrorKind::Unsupported,
797            "orbit-stream needs a platform that can wait on a shared word: Linux, FreeBSD, or macOS 14.4 or later",
798        )));
799    }
800    let key = if fleet.is_shm() {
801        #[cfg(unix)]
802        {
803            Key::Shm(
804                ring_segment_name(fleet.name(), spec.kind),
805                fleet.node_id().get(),
806            )
807        }
808        #[cfg(not(unix))]
809        unreachable!("non-Unix fleets cannot use POSIX SHM")
810    } else {
811        Key::Memory(Arc::as_ptr(fleet) as usize, spec.kind)
812    };
813    #[cfg(unix)]
814    let key = match &group {
815        Some(access) => Key::Group(
816            access.segment_name(fleet.name(), spec.kind),
817            fleet.node_id().get(),
818            access.clone(),
819        ),
820        None => key,
821    };
822    let mut tables = lock_unpoisoned(&TABLES);
823    tables.retain(|_, table| table.strong_count() > 0);
824    if let Some(table) = tables.get(&key).and_then(Weak::upgrade) {
825        if !recovery && table.incarnation != incarnation.get() {
826            return Err(Error::Malformed(format!(
827                "this process already opened the stream table as incarnation {}",
828                table.incarnation
829            )));
830        }
831        // One kind, one geometry: a second spec for the same segment is a
832        // mismatch here rather than a silently shared table.
833        if table.geometry.fleet_capacity != usize::from(fleet.fleet_capacity())
834            || table.geometry.lane_capacity != spec.lane_capacity
835            || table.geometry.buffer_bytes != spec.buffer_bytes
836        {
837            return Err(Error::Malformed(format!(
838                "this process already opened kind {} with lane_capacity={} buffer_bytes={}",
839                spec.kind,
840                table.geometry.lane_capacity,
841                table.geometry.buffer_bytes
842            )));
843        }
844        return Ok(table);
845    }
846    let geometry = Geometry::new(fleet.fleet_capacity(), spec);
847    let backing = match &key {
848        Key::Memory(..) => {
849            let bytes = AlignedBytes::zeroed(geometry.segment_size);
850            // SAFETY: freshly allocated, aligned, large enough for the header.
851            unsafe {
852                std::ptr::write(
853                    bytes.ptr.cast::<Header>(),
854                    Header::new(
855                        fleet.fleet_capacity(),
856                        &geometry,
857                        next_epoch(0),
858                    ),
859                )
860            };
861            Backing::Memory(bytes)
862        }
863        #[cfg(unix)]
864        Key::Shm(name, _) => Backing::Shm(open_shm(
865            name,
866            fleet.fleet_capacity(),
867            &geometry,
868            None,
869        )?),
870        #[cfg(unix)]
871        Key::Group(name, _, access) => Backing::Shm(open_shm(
872            name,
873            fleet.fleet_capacity(),
874            &geometry,
875            Some(access),
876        )?),
877    };
878    let table = Arc::new(Table {
879        _fleet: Arc::clone(fleet),
880        backing,
881        #[cfg(unix)]
882        group,
883        geometry,
884        kind: spec.kind,
885        node: fleet.node_id().get(),
886        incarnation: incarnation.get(),
887        allocate: Mutex::new(0),
888        registry: Registry::new(geometry.total_slots),
889        capacity: crate::capacity::Waiters::default(),
890        capacity_seen: AtomicU32::new(0),
891        driver: Mutex::new(None),
892        #[cfg(unix)]
893        bell: match &key {
894            Key::Shm(name, _) => {
895                Some(crate::bell::Bell::new(name).map_err(Error::Io)?)
896            }
897            Key::Group(name, _, access) => Some(
898                crate::bell::Bell::with_group(name, access.clone())
899                    .map_err(Error::Io)?,
900            ),
901            Key::Memory(..) => None,
902        },
903        readiness: std::sync::OnceLock::new(),
904    });
905    if !recovery {
906        tables.insert(key, Arc::downgrade(&table));
907    }
908    Ok(table)
909}
910
911#[cfg(unix)]
912pub(crate) fn persisted_spec(
913    name: &str,
914    kind: u8,
915) -> Result<Option<(u16, StreamSpec)>> {
916    use std::io;
917
918    let segment = ring_segment_name(name, kind);
919    let Some((region, _initialization_lock)) =
920        ShmRegion::open_existing_locked(&segment, size_of::<Header>())?
921    else {
922        return Ok(None);
923    };
924    // SAFETY: the region is at least a header long, and its creator wrote the
925    // header before releasing the initialization lock this call now holds.
926    let header = unsafe { &*region.as_ptr().cast::<Header>() };
927    if header.magic != crate::layout::MAGIC
928        || header.version != crate::layout::VERSION
929        || usize::from(header.header_size) != size_of::<Header>()
930        || header.slot_size as usize != size_of::<crate::layout::Slot>()
931    {
932        return Err(Error::Io(io::Error::new(
933            io::ErrorKind::InvalidData,
934            format!(
935                "SHM segment {segment} is not a stream table of this layout"
936            ),
937        )));
938    }
939    let spec = StreamSpec::new(
940        kind,
941        header.lane_capacity as usize,
942        header.buffer_bytes as usize,
943    );
944    spec.validate()?;
945    Ok(Some((header.fleet_capacity, spec)))
946}
947
948#[cfg(unix)]
949fn open_shm(
950    name: &str,
951    fleet_capacity: u16,
952    geometry: &Geometry,
953    group: Option<&orbit_core::shm::GroupAccess>,
954) -> Result<ShmRegion> {
955    use std::io;
956
957    let (region, _initialization_lock) = match group {
958        Some(access) => access.open_region(name, geometry.segment_size)?,
959        None => ShmRegion::open_or_create_locked(name, geometry.segment_size)?,
960    };
961    if region.created() {
962        // The kernel hands out zero pages; only the header is written, so
963        // the buffers stay untouched and unbacked until a stream uses them.
964        // SAFETY: a fresh mapping at least a header long.
965        unsafe {
966            std::ptr::write(
967                region.as_ptr().cast::<Header>(),
968                Header::new(fleet_capacity, geometry, next_epoch(0)),
969            );
970        }
971    } else {
972        if region.len() < geometry.segment_size {
973            return Err(Error::Io(io::Error::new(
974                io::ErrorKind::InvalidData,
975                format!("SHM segment {name} is smaller than the stream table"),
976            )));
977        }
978        // SAFETY: the region is at least a header long.
979        let header = unsafe { &*region.as_ptr().cast::<Header>() };
980        if header.magic != crate::layout::MAGIC {
981            return Err(Error::Io(io::Error::new(
982                io::ErrorKind::InvalidData,
983                format!(
984                    "SHM segment {name} has wrong magic 0x{:08X} (expected 0x{:08X})",
985                    header.magic,
986                    crate::layout::MAGIC
987                ),
988            )));
989        }
990        if !header.compatible(fleet_capacity, geometry) {
991            return Err(Error::Io(io::Error::new(
992                io::ErrorKind::InvalidData,
993                format!(
994                    "SHM segment {name} has an incompatible stream-table layout"
995                ),
996            )));
997        }
998    }
999    Ok(region)
1000}
1001
1002/// Bytes the segment needs for `fleet_capacity` lanes; what a lifecycle
1003/// tool validates an existing object against without mapping it read-write.
1004pub fn segment_size(fleet_capacity: u16) -> usize {
1005    segment_size_for(fleet_capacity, StreamSpec::DEFAULT)
1006}
1007
1008/// Bytes `spec`'s segment needs for `fleet_capacity` lanes.
1009pub fn segment_size_for(
1010    fleet_capacity: u16,
1011    spec: StreamSpec,
1012) -> usize {
1013    Geometry::new(fleet_capacity, spec).segment_size
1014}
1015
1016/// Whose doorbell a change rings.
1017///
1018/// A change on a direction is news to one side only: bytes, `FIN` and `RESET`
1019/// are the writer's doing and the reader's concern, room freed and a reader
1020/// gone are the reader's doing and the writer's concern. So a handle rings its
1021/// peer and not itself. It used to ring both, and the process that made the
1022/// change woke its own driver thread to find nothing there — on every
1023/// response body and every end of one. Two sides in one process share a
1024/// node, and ringing the peer rings it all the same.
1025#[derive(Clone, Copy)]
1026pub(crate) enum Ring {
1027    Nobody,
1028    /// The side at this index: the one that can be waiting on the change.
1029    Side(usize),
1030    /// A death report, on behalf of a process that is gone: both.
1031    Both,
1032}