1use 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
29struct 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 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 unsafe { std::alloc::dealloc(self.ptr, self.layout) }
51 }
52}
53
54unsafe impl Send for AlignedBytes {}
57unsafe impl Sync for AlignedBytes {}
58
59pub(crate) struct Table {
60 _fleet: Arc<Fleet>,
63 backing: Backing,
64 #[cfg(unix)]
65 group: Option<orbit_core::shm::GroupAccess>,
66 geometry: Geometry,
67 kind: u8,
69 node: u16,
70 incarnation: u64,
71 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 #[cfg(unix)]
81 bell: Option<crate::bell::Bell>,
82 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 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 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 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 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 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 pub(crate) fn buffer(
207 &self,
208 slot: usize,
209 direction: usize,
210 ) -> *mut u8 {
211 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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 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
617fn 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 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
686enum 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#[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
752pub(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 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 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 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 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 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
1002pub fn segment_size(fleet_capacity: u16) -> usize {
1005 segment_size_for(fleet_capacity, StreamSpec::DEFAULT)
1006}
1007
1008pub fn segment_size_for(
1010 fleet_capacity: u16,
1011 spec: StreamSpec,
1012) -> usize {
1013 Geometry::new(fleet_capacity, spec).segment_size
1014}
1015
1016#[derive(Clone, Copy)]
1026pub(crate) enum Ring {
1027 Nobody,
1028 Side(usize),
1030 Both,
1032}