Skip to main content

orbit_pool/
table.rs

1//! The table behind a `Pool` handle: the mapped segment (or its in-memory
2//! twin), this process's resource lane, the key table any node may install
3//! into, and the process-local readiness pieces. One table per process per
4//! fleet and node.
5
6use std::collections::HashMap;
7use std::mem::size_of;
8use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
9use std::sync::{Arc, LazyLock, Mutex, Weak};
10use std::task::Waker;
11use std::thread::JoinHandle;
12
13#[cfg(unix)]
14use orbit_core::shm::{ShmRegion, ring_segment_name};
15use orbit_core::{Fleet, OrbitEpoch};
16
17use crate::layout::{
18    Doorbell, Geometry, Header, KEY_EMPTY, KEY_LIVE, KeySlot, RESOURCE_EMPTY, ResourceSlot,
19};
20use orbit_core::readiness::{Readiness, Signal};
21
22use crate::{Error, Incarnation, PoolSpec, Result, lock_unpoisoned};
23
24enum Backing {
25    Memory(AlignedBytes),
26    #[cfg(unix)]
27    Shm(ShmRegion),
28}
29
30struct AlignedBytes {
31    ptr: *mut u8,
32    layout: std::alloc::Layout,
33}
34
35impl AlignedBytes {
36    fn zeroed(size: usize) -> Self {
37        let layout = std::alloc::Layout::from_size_align(size, 64).expect("segment layout");
38        // SAFETY: the layout has a nonzero size (a header at least).
39        let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
40        assert!(!ptr.is_null(), "pool table allocation failed");
41        Self { ptr, layout }
42    }
43}
44
45impl Drop for AlignedBytes {
46    fn drop(&mut self) {
47        // SAFETY: allocated with this layout in `zeroed`.
48        unsafe { std::alloc::dealloc(self.ptr, self.layout) }
49    }
50}
51
52// SAFETY: the bytes are only ever read through atomics; the pointer is not
53// shared outside `Table`.
54unsafe impl Send for AlignedBytes {}
55unsafe impl Sync for AlignedBytes {}
56
57/// Wakers parked on a key in this process, and the thread that turns the
58/// node's doorbell into their wakes.
59struct Driver {
60    stop: Arc<std::sync::atomic::AtomicBool>,
61    thread: Option<JoinHandle<()>>,
62    pid: u32,
63}
64
65pub(crate) struct Table {
66    /// Held so the fleet's address, which keys the in-memory registry,
67    /// cannot be reused by another fleet while this table lives.
68    _fleet: Arc<Fleet>,
69    backing: Backing,
70    geometry: Geometry,
71    /// The segment's kind, from the spec this table was opened with.
72    kind: u8,
73    node: u16,
74    incarnation: u64,
75    /// This process's allocations in its own lane, and its installs into
76    /// the shared key table (those also take the region's process lock).
77    structural: Mutex<usize>,
78    wakers: Box<[Mutex<Vec<Waker>>]>,
79    /// The signalling end of this process's readiness descriptor, once
80    /// somebody has asked for one.
81    readiness: std::sync::OnceLock<Signal>,
82    driver: Mutex<Option<Driver>>,
83}
84
85impl Table {
86    fn base(&self) -> *mut u8 {
87        match &self.backing {
88            Backing::Memory(bytes) => bytes.ptr,
89            #[cfg(unix)]
90            Backing::Shm(region) => region.as_ptr(),
91        }
92    }
93
94    pub(crate) fn is_shared(&self) -> bool {
95        match &self.backing {
96            Backing::Memory(_) => false,
97            #[cfg(unix)]
98            Backing::Shm(_) => true,
99        }
100    }
101
102    pub(crate) fn geometry(&self) -> &Geometry {
103        &self.geometry
104    }
105
106    /// The kind this table's segment lives under.
107    pub(crate) fn kind(&self) -> u8 {
108        self.kind
109    }
110
111    pub(crate) fn node(&self) -> u16 {
112        self.node
113    }
114
115    pub(crate) fn incarnation(&self) -> u64 {
116        self.incarnation
117    }
118
119    fn header(&self) -> &Header {
120        // SAFETY: written at creation, validated at open; mapping outlives self.
121        unsafe { &*self.base().cast::<Header>() }
122    }
123
124    pub(crate) fn epoch(&self) -> u64 {
125        self.header().epoch.load(Ordering::Acquire)
126    }
127
128    fn doorbell(&self, node: usize) -> &Doorbell {
129        debug_assert!(node < self.geometry.fleet_capacity);
130        // SAFETY: `fleet_capacity` doorbells follow the header; atomics only.
131        unsafe {
132            &*self
133                .base()
134                .add(self.geometry.doorbells_offset + node * size_of::<Doorbell>())
135                .cast::<Doorbell>()
136        }
137    }
138
139    fn key_word(&self, offset: usize, node: usize, word: usize) -> &AtomicU64 {
140        debug_assert!(node < self.geometry.fleet_capacity && word < self.geometry.key_words);
141        // SAFETY: inside the bitmaps by construction of `Geometry`.
142        unsafe {
143            &*self
144                .base()
145                .add(offset + (node * self.geometry.key_words + word) * size_of::<AtomicU64>())
146                .cast::<AtomicU64>()
147        }
148    }
149
150    fn pending_word(&self, node: usize, word: usize) -> &AtomicU64 {
151        self.key_word(self.geometry.pending_offset, node, word)
152    }
153
154    fn interest_word(&self, node: usize, word: usize) -> &AtomicU64 {
155        self.key_word(self.geometry.interest_offset, node, word)
156    }
157
158    /// Creation claims `node` holds on key `key_index`.
159    pub(crate) fn claims(&self, node: usize, key_index: usize) -> &AtomicU32 {
160        debug_assert!(node < self.geometry.fleet_capacity && key_index < self.geometry.key_capacity);
161        // SAFETY: inside the claims area by construction of `Geometry`.
162        unsafe {
163            &*self
164                .base()
165                .add(
166                    self.geometry.claims_offset
167                        + (node * self.geometry.key_capacity + key_index) * size_of::<AtomicU32>(),
168                )
169                .cast::<AtomicU32>()
170        }
171    }
172
173    pub(crate) fn key(&self, index: usize) -> &KeySlot {
174        debug_assert!(index < self.geometry.key_capacity);
175        // SAFETY: key slots are `key_stride` apart from `keys_offset`;
176        // atomics only.
177        unsafe {
178            &*self
179                .base()
180                .add(self.geometry.keys_offset + index * self.geometry.key_stride)
181                .cast::<KeySlot>()
182        }
183    }
184
185    /// The resource-slot bitmap that follows key slot `index`.
186    pub(crate) fn members(&self, index: usize) -> &[AtomicU64] {
187        // SAFETY: `member_words` words follow each key slot inside its stride.
188        unsafe {
189            std::slice::from_raw_parts(
190                self.base()
191                    .add(self.geometry.keys_offset + index * self.geometry.key_stride)
192                    .add(size_of::<KeySlot>())
193                    .cast::<AtomicU64>(),
194                self.geometry.member_words,
195            )
196        }
197    }
198
199    pub(crate) fn resources(&self) -> &[ResourceSlot] {
200        // SAFETY: `total_resources` slots follow the keys; atomics only.
201        unsafe {
202            std::slice::from_raw_parts(
203                self.base()
204                    .add(self.geometry.resources_offset)
205                    .cast::<ResourceSlot>(),
206                self.geometry.total_resources,
207            )
208        }
209    }
210
211    /// Find the key, installing it if absent. Open addressing on the
212    /// caller's 128-bit digest; keys are never removed within an epoch.
213    pub(crate) fn key_index(&self, lo: u64, hi: u64) -> Result<usize> {
214        if let Some(index) = self.find_key(lo, hi) {
215            return Ok(index);
216        }
217        let _local = lock_unpoisoned(&self.structural);
218        #[cfg(unix)]
219        let _shared = match &self.backing {
220            Backing::Shm(region) => Some(region.lock_exclusive()?),
221            Backing::Memory(_) => None,
222        };
223        let hash = mix(lo, hi);
224        for offset in 0..self.geometry.key_capacity {
225            let index = (hash as usize).wrapping_add(offset) & (self.geometry.key_capacity - 1);
226            let slot = self.key(index);
227            match slot.state.load(Ordering::Acquire) {
228                KEY_LIVE if slot.holds(lo, hi) => return Ok(index),
229                KEY_EMPTY => {
230                    slot.key_lo.store(lo, Ordering::Relaxed);
231                    slot.key_hi.store(hi, Ordering::Relaxed);
232                    slot.counts.store(0, Ordering::Relaxed);
233                    slot.changes.store(0, Ordering::Relaxed);
234                    slot.waiters.store(0, Ordering::Relaxed);
235                    for word in self.members(index) {
236                        word.store(0, Ordering::Relaxed);
237                    }
238                    slot.state.store(KEY_LIVE, Ordering::Release);
239                    return Ok(index);
240                }
241                _ => {}
242            }
243        }
244        Err(Error::KeyFull {
245            capacity: self.geometry.key_capacity,
246        })
247    }
248
249    fn find_key(&self, lo: u64, hi: u64) -> Option<usize> {
250        let hash = mix(lo, hi);
251        for offset in 0..self.geometry.key_capacity {
252            let index = (hash as usize).wrapping_add(offset) & (self.geometry.key_capacity - 1);
253            let slot = self.key(index);
254            match slot.state.load(Ordering::Acquire) {
255                KEY_LIVE if slot.holds(lo, hi) => return Some(index),
256                KEY_EMPTY => return None,
257                _ => {}
258            }
259        }
260        None
261    }
262
263    /// Take a free slot in this process's lane. Returns the index and the
264    /// generation it was installed in.
265    pub(crate) fn allocate(
266        &self,
267        key: (u64, u64),
268        key_index: usize,
269        capacity: u32,
270    ) -> Result<(usize, u32)> {
271        let mut hint = lock_unpoisoned(&self.structural);
272        let lane_start = usize::from(self.node) * self.geometry.lane_capacity;
273        let slots = self.resources();
274        let now = OrbitEpoch::now().as_unix_ms();
275        for offset in 0..self.geometry.lane_capacity {
276            let index = lane_start + ((*hint + offset) & (self.geometry.lane_capacity - 1));
277            let slot = &slots[index];
278            let state = slot.state.load(Ordering::Acquire);
279            if (state == RESOURCE_EMPTY || state == crate::layout::RESOURCE_CLOSED)
280                && let Some(generation) = slot.install(
281                    self.node,
282                    self.incarnation,
283                    key,
284                    key_index as u32,
285                    capacity,
286                    now,
287                )
288            {
289                *hint = (index - lane_start + 1) & (self.geometry.lane_capacity - 1);
290                return Ok((index, generation));
291            }
292        }
293        Err(Error::Full {
294            capacity: self.geometry.lane_capacity,
295        })
296    }
297
298    /// Capacity may have come back on `key_index`: count it for blocking
299    /// waiters and ring every node that registered interest.
300    pub(crate) fn key_changed(&self, key_index: usize) {
301        let key = self.key(key_index);
302        key.changes.fetch_add(1, Ordering::SeqCst);
303        if key.waiters.load(Ordering::SeqCst) > 0 {
304            crate::wake_on(&key.changes);
305        }
306        let word = key_index / 64;
307        let bit = 1_u64 << (key_index % 64);
308        if !self.is_shared() {
309            self.wake(key_index);
310            self.signal_readiness();
311            return;
312        }
313        for node in 0..self.geometry.fleet_capacity {
314            if self.interest_word(node, word).load(Ordering::SeqCst) & bit != 0 {
315                self.pending_word(node, word)
316                    .fetch_or(bit, Ordering::SeqCst);
317                let doorbell = self.doorbell(node);
318                doorbell.generation.fetch_add(1, Ordering::SeqCst);
319                if doorbell.listening.load(Ordering::SeqCst) > 0 {
320                    crate::wake_on(&doorbell.generation);
321                }
322            }
323        }
324    }
325
326    fn wake(&self, key_index: usize) {
327        let taken = std::mem::take(&mut *lock_unpoisoned(&self.wakers[key_index]));
328        for waker in taken {
329            waker.wake();
330        }
331    }
332
333    /// Hand out this table's readiness descriptor, once. The driver is
334    /// what signals it, so asking for one starts it.
335    pub(crate) fn take_readiness(self: &Arc<Self>) -> Result<Readiness> {
336        let (readiness, signal) = orbit_core::readiness::pair()?;
337        self.readiness.set(signal).map_err(|_| {
338            Error::Malformed(
339                "this process already took the pool table's readiness descriptor".to_owned(),
340            )
341        })?;
342        if self.is_shared() {
343            let mut driver = lock_unpoisoned(&self.driver);
344            if driver.is_none() {
345                *driver = Some(self.start_driver()?);
346            }
347        }
348        Ok(readiness)
349    }
350
351    /// Mark this node interested in a key with no waker behind it: the
352    /// descriptor is what gets signalled. Interest is cleared when the
353    /// driver drains it, so this is re-armed before each wait.
354    pub(crate) fn watch(self: &Arc<Self>, key_index: usize) -> Result<()> {
355        if !self.is_shared() {
356            return Ok(());
357        }
358        self.interest_word(usize::from(self.node), key_index / 64)
359            .fetch_or(1 << (key_index % 64), Ordering::SeqCst);
360        let mut driver = lock_unpoisoned(&self.driver);
361        if driver.is_none() {
362            *driver = Some(self.start_driver()?);
363        }
364        Ok(())
365    }
366
367    fn signal_readiness(&self) {
368        if let Some(signal) = self.readiness.get() {
369            // A consumer that has gone away is not this table's problem.
370            let _ = signal.signal();
371        }
372    }
373
374    /// Park a task on a key: remember its waker, mark this node interested,
375    /// start the driver on first use.
376    pub(crate) fn register(self: &Arc<Self>, key_index: usize, waker: &Waker) -> Result<()> {
377        {
378            let mut wakers = lock_unpoisoned(&self.wakers[key_index]);
379            if !wakers.iter().any(|existing| existing.will_wake(waker)) {
380                wakers.push(waker.clone());
381            }
382        }
383        if !self.is_shared() {
384            return Ok(());
385        }
386        self.interest_word(usize::from(self.node), key_index / 64)
387            .fetch_or(1 << (key_index % 64), Ordering::SeqCst);
388        let mut driver = lock_unpoisoned(&self.driver);
389        if driver.is_none() {
390            *driver = Some(self.start_driver()?);
391        }
392        Ok(())
393    }
394
395    fn start_driver(self: &Arc<Self>) -> std::io::Result<Driver> {
396        let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
397        let thread_stop = Arc::clone(&stop);
398        let target = SendPtr(Arc::as_ptr(self));
399        let thread = std::thread::Builder::new()
400            .name(format!("orbit-pool-{}-driver", self.node))
401            .spawn(move || {
402                let target = target;
403                // SAFETY: `Table::drop` stops and joins this thread before
404                // the mapping goes away.
405                let table = unsafe { &*target.0 };
406                table.run_driver(&thread_stop);
407            })?;
408        Ok(Driver {
409            stop,
410            thread: Some(thread),
411            pid: std::process::id(),
412        })
413    }
414
415    fn run_driver(&self, stop: &std::sync::atomic::AtomicBool) {
416        let node = usize::from(self.node);
417        let doorbell = self.doorbell(node);
418        doorbell
419            .incarnation
420            .store(self.incarnation, Ordering::SeqCst);
421        doorbell.listening.fetch_add(1, Ordering::SeqCst);
422        let mut seen = doorbell.generation.load(Ordering::SeqCst);
423        while !stop.load(Ordering::Acquire) {
424            let mut drained = false;
425            for word in 0..self.geometry.key_words {
426                let mut bits = self.pending_word(node, word).swap(0, Ordering::SeqCst);
427                if bits != 0 {
428                    drained = true;
429                    // Interest is re-registered by whoever polls again.
430                    self.interest_word(node, word)
431                        .fetch_and(!bits, Ordering::SeqCst);
432                }
433                while bits != 0 {
434                    let bit = bits.trailing_zeros() as usize;
435                    bits &= bits - 1;
436                    let key_index = word * 64 + bit;
437                    if key_index < self.geometry.key_capacity {
438                        self.wake(key_index);
439                    }
440                }
441            }
442            if drained {
443                // After the bits are taken, never before: a consumer that
444                // drains the descriptor and looks again cannot miss what
445                // this pass carried.
446                self.signal_readiness();
447            }
448            let now = doorbell.generation.load(Ordering::SeqCst);
449            if now != seen {
450                seen = now;
451                continue;
452            }
453            // The table refused to open where the platform cannot wait, so
454            // a failure here is the table going away, not a system to poll
455            // around.
456            if crate::wait_on(&doorbell.generation, seen).is_err() {
457                break;
458            }
459        }
460        doorbell.listening.fetch_sub(1, Ordering::SeqCst);
461    }
462
463    /// A confirmed death: close every resource that incarnation of `node`
464    /// owned and drop the creation claims it held.
465    pub(crate) fn node_dead(&self, node: u16, incarnation: u64) {
466        for (index, slot) in self.resources().iter().enumerate() {
467            let state = slot.state.load(Ordering::Acquire);
468            if (state != crate::layout::RESOURCE_LIVE && state != crate::layout::RESOURCE_DRAINING)
469                || slot.owner_node.load(Ordering::Acquire) != node
470                || slot.owner_incarnation.load(Ordering::Acquire) != incarnation
471            {
472                continue;
473            }
474            self.close_resource(index, slot);
475        }
476        for key_index in 0..self.geometry.key_capacity {
477            let held = self
478                .claims(usize::from(node), key_index)
479                .swap(0, Ordering::SeqCst);
480            if held > 0 {
481                let key = self.key(key_index);
482                let _ = key
483                    .counts
484                    .try_update(Ordering::SeqCst, Ordering::SeqCst, |counts| {
485                        let (live, creating) = crate::layout::unpack_counts(counts);
486                        Some(crate::layout::pack_counts(
487                            live,
488                            creating.saturating_sub(held),
489                        ))
490                    });
491                self.key_changed(key_index);
492            }
493        }
494        let doorbell = self.doorbell(usize::from(node));
495        if doorbell.incarnation.load(Ordering::SeqCst) == incarnation {
496            doorbell.listening.store(0, Ordering::SeqCst);
497            doorbell.incarnation.store(0, Ordering::SeqCst);
498        }
499    }
500
501    /// The resource is gone: mark it, take it out of its key's members and
502    /// live count, wake the key. The slot is reused by its lane later.
503    pub(crate) fn close_resource(&self, index: usize, slot: &ResourceSlot) {
504        if slot
505            .state
506            .try_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
507                (state == crate::layout::RESOURCE_LIVE || state == crate::layout::RESOURCE_DRAINING)
508                    .then_some(crate::layout::RESOURCE_CLOSED)
509            })
510            .is_err()
511        {
512            return;
513        }
514        let key_index = slot.key_index.load(Ordering::Acquire) as usize;
515        self.members(key_index)[index / 64].fetch_and(!(1_u64 << (index % 64)), Ordering::SeqCst);
516        let key = self.key(key_index);
517        let _ = key
518            .counts
519            .try_update(Ordering::SeqCst, Ordering::SeqCst, |counts| {
520                let (live, creating) = crate::layout::unpack_counts(counts);
521                Some(crate::layout::pack_counts(live.saturating_sub(1), creating))
522            });
523        self.key_changed(key_index);
524    }
525
526    pub(crate) fn reset_all(&self) {
527        let mut hint = lock_unpoisoned(&self.structural);
528        *hint = 0;
529        let header = self.header();
530        let epoch = header.epoch.load(Ordering::Acquire);
531        header.epoch.store(next_epoch(epoch), Ordering::Release);
532        for slot in self.resources() {
533            slot.state.store(RESOURCE_EMPTY, Ordering::Release);
534            slot.generation.store(0, Ordering::Relaxed);
535        }
536        for index in 0..self.geometry.key_capacity {
537            let key = self.key(index);
538            key.state.store(KEY_EMPTY, Ordering::Release);
539            key.changes.fetch_add(1, Ordering::SeqCst);
540            crate::wake_on(&key.changes);
541        }
542        for node in 0..self.geometry.fleet_capacity {
543            for word in 0..self.geometry.key_words {
544                self.pending_word(node, word).store(0, Ordering::Relaxed);
545                self.interest_word(node, word).store(0, Ordering::Relaxed);
546            }
547            for key_index in 0..self.geometry.key_capacity {
548                self.claims(node, key_index).store(0, Ordering::Relaxed);
549            }
550        }
551        for wakers in &self.wakers {
552            lock_unpoisoned(wakers).clear();
553        }
554    }
555
556    #[cfg(unix)]
557    pub(crate) fn unlink(&self) -> Result<()> {
558        match &self.backing {
559            Backing::Memory(_) => {
560                self.reset_all();
561                Ok(())
562            }
563            Backing::Shm(region) => region.unlink().map_err(Error::Io),
564        }
565    }
566}
567
568impl Drop for Table {
569    fn drop(&mut self) {
570        if let Some(mut driver) = lock_unpoisoned(&self.driver).take() {
571            driver.stop.store(true, Ordering::Release);
572            let generation = &self.doorbell(usize::from(self.node)).generation;
573            generation.fetch_add(1, Ordering::SeqCst);
574            crate::wake_on(generation);
575            if let Some(thread) = driver.thread.take()
576                && driver.pid == std::process::id()
577            {
578                let _ = thread.join();
579            }
580        }
581    }
582}
583
584struct SendPtr<T>(*const T);
585
586// SAFETY: the pointee is `Sync` and outlives the thread (see `start_driver`).
587unsafe impl<T: Sync> Send for SendPtr<T> {}
588
589fn mix(lo: u64, hi: u64) -> u64 {
590    // The caller brings a digest; one multiply spreads a weak one.
591    (lo ^ hi.rotate_left(32)).wrapping_mul(0x9E37_79B9_7F4A_7C15)
592}
593
594fn next_epoch(previous: u64) -> u64 {
595    OrbitEpoch::now().as_unix_ms().max(previous + 1)
596}
597
598#[derive(Hash, PartialEq, Eq)]
599enum Key {
600    Memory(usize, u8),
601    #[cfg(unix)]
602    Shm(String, u16),
603}
604
605static TABLES: LazyLock<Mutex<HashMap<Key, Weak<Table>>>> =
606    LazyLock::new(|| Mutex::new(HashMap::new()));
607
608pub(crate) fn open(
609    fleet: &Arc<Fleet>,
610    incarnation: Incarnation,
611    spec: PoolSpec,
612) -> Result<Arc<Table>> {
613    if !crate::waits_supported() {
614        return Err(Error::Io(std::io::Error::new(
615            std::io::ErrorKind::Unsupported,
616            "orbit-pool needs a platform that can wait on a shared word: Linux, FreeBSD, or macOS 14.4 or later",
617        )));
618    }
619    // The kind is part of the identity in both backings: two specs are two
620    // tables, in one process as in the fleet.
621    let key = if fleet.is_shm() {
622        #[cfg(unix)]
623        {
624            Key::Shm(
625                ring_segment_name(fleet.name(), spec.kind),
626                fleet.node_id().get(),
627            )
628        }
629        #[cfg(not(unix))]
630        unreachable!("non-Unix fleets cannot use POSIX SHM")
631    } else {
632        Key::Memory(Arc::as_ptr(fleet) as usize, spec.kind)
633    };
634    let mut tables = lock_unpoisoned(&TABLES);
635    tables.retain(|_, table| table.strong_count() > 0);
636    if let Some(table) = tables.get(&key).and_then(Weak::upgrade) {
637        if table.incarnation != incarnation.get() {
638            return Err(Error::Malformed(format!(
639                "this process already opened the pool table as incarnation {}",
640                table.incarnation
641            )));
642        }
643        // One kind, one geometry: a second spec for the same segment is a
644        // mismatch here rather than a silently shared table.
645        if table.geometry.key_capacity != spec.key_capacity
646            || table.geometry.lane_capacity != spec.lane_capacity
647        {
648            return Err(Error::Malformed(format!(
649                "this process already opened kind {} with key_capacity={} lane_capacity={}",
650                spec.kind, table.geometry.key_capacity, table.geometry.lane_capacity
651            )));
652        }
653        return Ok(table);
654    }
655    let geometry = Geometry::new(fleet.fleet_capacity(), spec);
656    let backing = match &key {
657        Key::Memory(..) => {
658            let bytes = AlignedBytes::zeroed(geometry.segment_size);
659            // SAFETY: freshly allocated, aligned, large enough for the header.
660            unsafe {
661                std::ptr::write(
662                    bytes.ptr.cast::<Header>(),
663                    Header::new(fleet.fleet_capacity(), &geometry, next_epoch(0)),
664                )
665            };
666            Backing::Memory(bytes)
667        }
668        #[cfg(unix)]
669        Key::Shm(name, _) => Backing::Shm(open_shm(name, fleet.fleet_capacity(), &geometry)?),
670    };
671    let table = Arc::new(Table {
672        _fleet: Arc::clone(fleet),
673        backing,
674        geometry,
675        kind: spec.kind,
676        node: fleet.node_id().get(),
677        incarnation: incarnation.get(),
678        structural: Mutex::new(0),
679        wakers: (0..geometry.key_capacity)
680            .map(|_| Mutex::new(Vec::new()))
681            .collect(),
682        readiness: std::sync::OnceLock::new(),
683        driver: Mutex::new(None),
684    });
685    tables.insert(key, Arc::downgrade(&table));
686    Ok(table)
687}
688
689#[cfg(unix)]
690fn open_shm(name: &str, fleet_capacity: u16, geometry: &Geometry) -> Result<ShmRegion> {
691    use std::io;
692
693    let (region, _initialization_lock) =
694        ShmRegion::open_or_create_locked(name, geometry.segment_size)?;
695    if region.created() {
696        // SAFETY: a fresh zero-filled mapping at least a header long.
697        unsafe {
698            std::ptr::write(
699                region.as_ptr().cast::<Header>(),
700                Header::new(fleet_capacity, geometry, next_epoch(0)),
701            );
702        }
703    } else {
704        if region.len() < geometry.segment_size {
705            return Err(Error::Io(io::Error::new(
706                io::ErrorKind::InvalidData,
707                format!("SHM segment {name} is smaller than the pool table"),
708            )));
709        }
710        // SAFETY: the region is at least a header long.
711        let header = unsafe { &*region.as_ptr().cast::<Header>() };
712        if header.magic != crate::layout::MAGIC {
713            return Err(Error::Io(io::Error::new(
714                io::ErrorKind::InvalidData,
715                format!(
716                    "SHM segment {name} has wrong magic 0x{:08X} (expected 0x{:08X})",
717                    header.magic,
718                    crate::layout::MAGIC
719                ),
720            )));
721        }
722        if !header.compatible(fleet_capacity, geometry) {
723            return Err(Error::Io(io::Error::new(
724                io::ErrorKind::InvalidData,
725                format!("SHM segment {name} has an incompatible pool-table layout"),
726            )));
727        }
728    }
729    Ok(region)
730}
731
732/// Bytes the default spec's segment needs for `fleet_capacity` lanes.
733pub fn segment_size(fleet_capacity: u16) -> usize {
734    segment_size_for(fleet_capacity, PoolSpec::DEFAULT)
735}
736
737/// Bytes `spec`'s segment needs for `fleet_capacity` lanes.
738pub fn segment_size_for(fleet_capacity: u16, spec: PoolSpec) -> usize {
739    Geometry::new(fleet_capacity, spec).segment_size
740}