1use std::collections::HashMap;
16use std::fmt;
17use std::marker::PhantomData;
18use std::str::FromStr;
19use std::sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering};
20use std::sync::{Arc, LazyLock, Mutex, MutexGuard, Weak};
21
22use orbit_core::Fleet;
23#[cfg(unix)]
24use orbit_core::shm::{ShmRegion, ring_segment_name};
25
26mod lifetime;
27mod text;
28mod wake;
29
30use lifetime::{Lease, Owners};
31use wake::Wake;
32
33pub use text::{
34 CELL_TEXT_CAPACITY, CELL_TEXT_KIND, CELL_TEXT_MAX, Text, TextId,
35};
36
37pub const CELL_STATE_KIND: u8 = 187;
39pub const CELL_CAPACITY: usize = orbit_core::compile::usize_from_env(
45 option_env!("ORBIT_CELL_CAPACITY"),
46 4_096,
47);
48
49pub(crate) const SLOT_EMPTY: u8 = 0;
50pub(crate) const SLOT_OCCUPIED: u8 = 1;
51pub(crate) const SLOT_RETIRED: u8 = 2;
52
53#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
57pub(crate) fn waits_supported() -> bool {
58 orbit_core::sync::supported()
59}
60
61#[cfg(not(any(
62 target_os = "linux",
63 target_os = "freebsd",
64 target_os = "macos"
65)))]
66pub(crate) fn waits_supported() -> bool {
67 false
68}
69
70#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
74pub(crate) fn wait_on(
75 word: &AtomicU32,
76 expected: u32,
77) -> Result<()> {
78 orbit_core::sync::wait_word(word, expected).map_err(Error::Io)
79}
80
81#[cfg(not(any(
82 target_os = "linux",
83 target_os = "freebsd",
84 target_os = "macos"
85)))]
86pub(crate) fn wait_on(
87 _word: &AtomicU32,
88 _expected: u32,
89) -> Result<()> {
90 Err(Error::Io(std::io::Error::new(
91 std::io::ErrorKind::Unsupported,
92 "orbit-cell needs a platform that can wait on a shared word",
93 )))
94}
95
96pub(crate) fn wake_on(word: &AtomicU32) {
98 #[cfg(any(
99 target_os = "linux",
100 target_os = "freebsd",
101 target_os = "macos"
102 ))]
103 let _ = orbit_core::sync::wake_word(word);
104 #[cfg(not(any(
105 target_os = "linux",
106 target_os = "freebsd",
107 target_os = "macos"
108 )))]
109 let _ = word;
110}
111
112#[cfg(unix)]
113const STATE_MAGIC: u32 = 0x43_45_4c_4c; #[cfg(unix)]
115const STATE_VERSION: u16 = 4;
116
117pub type Result<T, E = Error> = std::result::Result<T, E>;
118
119#[non_exhaustive]
123#[derive(Debug)]
124pub enum Error {
125 Stale(CellId),
128 StaleText(TextId),
130 TooLong {
132 len: usize,
133 max: usize,
134 },
135 BufferTooSmall {
137 required: usize,
138 available: usize,
139 },
140 Corrupt(String),
143 TypeMismatch {
145 id: CellId,
146 expected: &'static str,
147 found: &'static str,
148 },
149 Overflow,
151 WakeExhausted,
154 Contended,
157 RevisionExhausted,
159 OwnerExhausted,
161 Full {
164 capacity: usize,
165 },
166 Malformed(String),
168 Io(std::io::Error),
169}
170
171impl fmt::Display for Error {
172 fn fmt(
173 &self,
174 f: &mut fmt::Formatter<'_>,
175 ) -> fmt::Result {
176 match self {
177 Self::Stale(id) => write!(f, "cell {id} has been released"),
178 Self::StaleText(id) => {
179 write!(f, "text cell {id} has been released")
180 }
181 Self::TooLong { len, max } => {
182 write!(f, "text does not fit the cell: len={len} max={max}")
183 }
184 Self::BufferTooSmall { required, available } => write!(
185 f,
186 "text read buffer is too small: required={required} available={available}"
187 ),
188 Self::Corrupt(id) => {
189 write!(f, "text cell {id} holds bytes that are not UTF-8")
190 }
191 Self::TypeMismatch { id, expected, found } => {
192 write!(f, "cell {id} holds {found}, not {expected}")
193 }
194 Self::Overflow => {
195 f.write_str("cell value is outside the integer range")
196 }
197 Self::WakeExhausted => {
198 f.write_str("cell parking generation is exhausted")
199 }
200 Self::Contended => f.write_str("text cell is contended"),
201 Self::RevisionExhausted => {
202 f.write_str("text publication sequence is exhausted")
203 }
204 Self::OwnerExhausted => {
205 f.write_str("text owner incarnation is exhausted")
206 }
207 Self::Full { capacity } => {
208 write!(
209 f,
210 "cell table has no reusable slot: capacity={capacity}"
211 )
212 }
213 Self::Malformed(text) => write!(f, "not a cell id: {text:?}"),
214 Self::Io(error) => write!(f, "Orbit cell io error: {error}"),
215 }
216 }
217}
218
219impl std::error::Error for Error {
220 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
221 match self {
222 Self::Io(error) => Some(error),
223 _ => None,
224 }
225 }
226}
227
228impl From<std::io::Error> for Error {
229 fn from(value: std::io::Error) -> Self {
230 Self::Io(value)
231 }
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
239pub struct CellId {
240 index: u32,
241 generation: u32,
242}
243
244impl CellId {
245 pub const fn index(self) -> u32 {
246 self.index
247 }
248
249 pub const fn generation(self) -> u32 {
250 self.generation
251 }
252
253 pub const fn to_bits(self) -> u64 {
255 ((self.index as u64) << 32) | self.generation as u64
256 }
257
258 pub const fn from_bits(bits: u64) -> Self {
259 Self { index: (bits >> 32) as u32, generation: bits as u32 }
260 }
261}
262
263impl fmt::Display for CellId {
264 fn fmt(
265 &self,
266 f: &mut fmt::Formatter<'_>,
267 ) -> fmt::Result {
268 write!(f, "cell:{}:{}", self.index, self.generation)
269 }
270}
271
272impl FromStr for CellId {
273 type Err = Error;
274
275 fn from_str(text: &str) -> Result<Self> {
276 let malformed = || Error::Malformed(text.to_owned());
277 let rest = text.strip_prefix("cell:").ok_or_else(malformed)?;
278 let (index, generation) = rest.split_once(':').ok_or_else(malformed)?;
279 Ok(Self {
280 index: index.parse().map_err(|_| malformed())?,
281 generation: generation.parse().map_err(|_| malformed())?,
282 })
283 }
284}
285
286pub trait CellType: Copy + Send + Sync + 'static {
289 const TAG: u8;
290 const NAME: &'static str;
291
292 fn to_bits(self) -> u64;
293 fn from_bits(bits: u64) -> Self;
294}
295
296impl CellType for i64 {
297 const TAG: u8 = 1;
298 const NAME: &'static str = "int";
299
300 fn to_bits(self) -> u64 {
301 self as u64
302 }
303
304 fn from_bits(bits: u64) -> Self {
305 bits as i64
306 }
307}
308
309impl CellType for u64 {
310 const TAG: u8 = 2;
311 const NAME: &'static str = "unsigned";
312
313 fn to_bits(self) -> u64 {
314 self
315 }
316
317 fn from_bits(bits: u64) -> Self {
318 bits
319 }
320}
321
322impl CellType for f64 {
323 const TAG: u8 = 3;
324 const NAME: &'static str = "float";
325
326 fn to_bits(self) -> u64 {
327 self.to_bits()
328 }
329
330 fn from_bits(bits: u64) -> Self {
331 f64::from_bits(bits)
332 }
333}
334
335impl CellType for bool {
336 const TAG: u8 = 4;
337 const NAME: &'static str = "bool";
338
339 fn to_bits(self) -> u64 {
340 u64::from(self)
341 }
342
343 fn from_bits(bits: u64) -> Self {
344 bits != 0
345 }
346}
347
348fn type_name(tag: u8) -> &'static str {
349 match tag {
350 1 => i64::NAME,
351 2 => u64::NAME,
352 3 => f64::NAME,
353 4 => bool::NAME,
354 _ => "unknown",
355 }
356}
357
358#[derive(Clone)]
360pub struct Cells {
361 backend: CellBackend,
362 text: text::TextBackend,
363}
364
365#[derive(Clone)]
366enum CellBackend {
367 InMemory(Arc<MemoryCellTable>),
368 #[cfg(unix)]
369 Shm(Arc<ShmCellTable>),
370}
371
372impl Cells {
373 pub fn new(fleet: Arc<Fleet>) -> Result<Self> {
374 if !crate::waits_supported() {
375 return Err(Error::Io(std::io::Error::new(
376 std::io::ErrorKind::Unsupported,
377 "orbit-cell needs a platform that can wait on a shared word: Linux, FreeBSD, or macOS 14.4 or later",
378 )));
379 }
380 let backend = if fleet.is_shm() {
381 #[cfg(unix)]
382 {
383 CellBackend::Shm(shm_table(
384 &ring_segment_name(fleet.name(), CELL_STATE_KIND),
385 lifetime::owner_capacity(&fleet),
386 )?)
387 }
388 #[cfg(not(unix))]
389 unreachable!("non-Unix fleets cannot use POSIX SHM")
390 } else {
391 CellBackend::InMemory(memory_table(&fleet))
392 };
393 let text = text::TextBackend::new(&fleet)?;
394 Ok(Self { backend, text })
395 }
396
397 pub fn allocate_text(
399 &self,
400 initial: &str,
401 ) -> Result<Text> {
402 self.text.allocate(initial)
403 }
404
405 pub fn recover_text_writers(&self) -> Result<usize> {
414 self.text.recover_writers()
415 }
416
417 pub fn open_text(
419 &self,
420 id: TextId,
421 ) -> Result<Text> {
422 self.text.open(id)
423 }
424
425 pub fn release_text(
426 &self,
427 id: TextId,
428 ) -> Result<()> {
429 self.text.release(id)
430 }
431
432 pub fn is_text_live(
433 &self,
434 id: TextId,
435 ) -> bool {
436 self.text.is_live(id)
437 }
438
439 pub fn allocate<T: CellType>(
442 &self,
443 initial: T,
444 ) -> Result<Orbital<T>> {
445 let bits = initial.to_bits();
446 let (id, lease) = self.with_structure(|slots, hint, owners| {
447 let capacity = slots.len();
448 let start = hint.load(Ordering::Relaxed) as usize;
449 let allocate = || -> Result<Option<(CellId, Arc<Lease>)>> {
450 for offset in 0..capacity {
451 let index = (start + offset) & (capacity - 1);
452 let slot = &slots[index];
453 if matches!(
454 slot.state.load(Ordering::Acquire),
455 SLOT_EMPTY | SLOT_RETIRED
456 ) && slot.generation.load(Ordering::Relaxed) != u32::MAX
457 && !owners.pinned(index)
458 {
459 let lease = owners.lease(index)?;
460 let generation = slot.install(T::TAG, bits);
461 hint.store(
462 ((index + 1) & (capacity - 1)) as u32,
463 Ordering::Relaxed,
464 );
465 return Ok(Some((
466 CellId { index: index as u32, generation },
467 lease,
468 )));
469 }
470 }
471 Ok(None)
472 };
473 if let Some(allocated) = allocate()? {
474 return Ok(allocated);
475 }
476 if owners.reclaim_dead()?
477 && let Some(allocated) = allocate()?
478 {
479 return Ok(allocated);
480 }
481 Err(Error::Full { capacity })
482 })?;
483 Ok(Orbital {
484 cells: self.clone(),
485 id,
486 _type: PhantomData,
487 _lease: lease,
488 })
489 }
490
491 pub fn open<T: CellType>(
493 &self,
494 id: CellId,
495 ) -> Result<Orbital<T>> {
496 let lease = self.with_structure(|slots, _, owners| {
497 let slot = slots
498 .get(id.index as usize)
499 .filter(|slot| slot.is(id))
500 .ok_or(Error::Stale(id))?;
501 let found = slot.tag.load(Ordering::Relaxed);
502 if found != T::TAG {
503 return Err(Error::TypeMismatch {
504 id,
505 expected: T::NAME,
506 found: type_name(found),
507 });
508 }
509 owners.lease(id.index as usize)
510 })?;
511 Ok(Orbital {
512 cells: self.clone(),
513 id,
514 _type: PhantomData,
515 _lease: lease,
516 })
517 }
518
519 pub fn release(
522 &self,
523 id: CellId,
524 ) -> Result<()> {
525 self.with_structure(|slots, _, _| {
526 let slot = slots
527 .get(id.index as usize)
528 .filter(|slot| slot.is(id))
529 .ok_or(Error::Stale(id))?;
530 slot.state.store(SLOT_RETIRED, Ordering::Release);
531 slot.changed();
533 Ok(())
534 })
535 }
536
537 pub fn is_live(
539 &self,
540 id: CellId,
541 ) -> bool {
542 self.slot(id).is_ok()
543 }
544
545 pub fn reset_all(&self) -> Result<()> {
547 self.with_structure(|slots, hint, _| {
548 for slot in slots {
549 slot.state.store(SLOT_RETIRED, Ordering::Release);
550 slot.changed();
551 }
552 hint.store(0, Ordering::Relaxed);
553 Ok(())
554 })?;
555 self.text.reset_all()
556 }
557
558 #[cfg(unix)]
561 pub fn unlink(&self) -> Result<()> {
562 match &self.backend {
563 CellBackend::InMemory(_) => self.reset_all()?,
564 CellBackend::Shm(table) => {
565 table.shared.region.unlink().map_err(Error::Io)?
566 }
567 }
568 self.text.unlink()
569 }
570
571 fn slots(&self) -> &[CellSlot] {
572 match &self.backend {
573 CellBackend::InMemory(table) => &table.slots,
574 #[cfg(unix)]
575 CellBackend::Shm(table) => table.slots(),
576 }
577 }
578
579 fn slot(
580 &self,
581 id: CellId,
582 ) -> Result<&CellSlot> {
583 self.slots()
584 .get(id.index as usize)
585 .filter(|slot| slot.is(id))
586 .ok_or(Error::Stale(id))
587 }
588
589 fn with_structure<T>(
590 &self,
591 operation: impl FnOnce(&[CellSlot], &AtomicU32, &Owners) -> Result<T>,
592 ) -> Result<T> {
593 match &self.backend {
594 CellBackend::InMemory(table) => {
595 let _local = lock_unpoisoned(&table.structural_lock);
596 operation(&table.slots, &table.hint, &table.owners)
597 }
598 #[cfg(unix)]
599 CellBackend::Shm(table) => table.with_structure(operation),
600 }
601 }
602}
603
604#[derive(Clone, Copy, Debug, Eq, PartialEq)]
608pub enum Claim {
609 Took { taken: i64, after: i64 },
612 Exhausted,
614}
615
616#[derive(Clone)]
618pub struct Orbital<T: CellType> {
619 cells: Cells,
620 id: CellId,
621 _type: PhantomData<T>,
622 _lease: Arc<Lease>,
623}
624
625impl<T: CellType> Orbital<T> {
626 pub fn id(&self) -> CellId {
627 self.id
628 }
629
630 pub fn load(&self) -> Result<T> {
631 Ok(T::from_bits(self.slot()?.value.load(Ordering::Acquire)))
632 }
633
634 pub fn store(
635 &self,
636 value: T,
637 ) -> Result<()> {
638 let slot = self.slot()?;
639 slot.value.store(value.to_bits(), Ordering::Release);
640 slot.changed();
641 Ok(())
642 }
643
644 pub fn swap(
646 &self,
647 value: T,
648 ) -> Result<T> {
649 let slot = self.slot()?;
650 let previous = slot.value.swap(value.to_bits(), Ordering::AcqRel);
651 slot.changed();
652 Ok(T::from_bits(previous))
653 }
654
655 pub fn version(&self) -> Result<u32> {
658 Ok(self.slot()?.changes.load(Ordering::Acquire))
659 }
660
661 pub fn wait_changed(
666 &self,
667 since: u32,
668 ) -> Result<u32> {
669 loop {
670 let slot = self.slot()?;
671 let now = slot.changes.load(Ordering::SeqCst);
672 if now != since {
673 return Ok(now);
674 }
675 slot.wake
676 .wait_if(|| slot.changes.load(Ordering::SeqCst) == since)?;
677 }
678 }
679
680 pub fn compare_exchange(
683 &self,
684 current: T,
685 new: T,
686 ) -> Result<std::result::Result<T, T>> {
687 let slot = self.slot()?;
688 let exchanged = slot.value.compare_exchange(
689 current.to_bits(),
690 new.to_bits(),
691 Ordering::AcqRel,
692 Ordering::Acquire,
693 );
694 if exchanged.is_ok() {
695 slot.changed();
696 }
697 Ok(exchanged.map(T::from_bits).map_err(T::from_bits))
698 }
699
700 pub fn wait_changed_until(
717 &self,
718 since: u32,
719 interrupt: &core::sync::atomic::AtomicBool,
720 ) -> Result<Option<u32>> {
721 loop {
722 if interrupt.load(Ordering::SeqCst) {
723 return Ok(None);
724 }
725
726 let slot = self.slot()?;
727 let now = slot.changes.load(Ordering::SeqCst);
728 if now != since {
729 return Ok(Some(now));
730 }
731
732 slot.wake.wait_if(|| {
733 slot.changes.load(Ordering::SeqCst) == since
734 && !interrupt.load(Ordering::SeqCst)
735 })?;
736 }
737 }
738
739 pub fn wake_waiters(&self) -> Result<()> {
744 self.slot()?.wake.notify();
745 Ok(())
746 }
747
748 pub fn release(self) -> Result<()> {
751 self.cells.release(self.id)
752 }
753
754 fn slot(&self) -> Result<&CellSlot> {
755 self.cells.slot(self.id)
756 }
757}
758
759impl Orbital<i64> {
760 pub fn fetch_add(
762 &self,
763 by: i64,
764 ) -> Result<i64> {
765 let slot = self.slot()?;
766 slot.value
767 .try_update(Ordering::AcqRel, Ordering::Acquire, |bits| {
768 (bits as i64).checked_add(by).map(|value| value as u64)
769 })
770 .map(|previous| {
771 slot.changed();
772 (previous as i64) + by
773 })
774 .map_err(|_| Error::Overflow)
775 }
776
777 pub fn fetch_sub(
778 &self,
779 by: i64,
780 ) -> Result<i64> {
781 let slot = self.slot()?;
782 slot.value
783 .try_update(Ordering::AcqRel, Ordering::Acquire, |bits| {
784 (bits as i64).checked_sub(by).map(|value| value as u64)
785 })
786 .map(|previous| {
787 slot.changed();
788 (previous as i64) - by
789 })
790 .map_err(|_| Error::Overflow)
791 }
792
793 pub fn add_until(
822 &self,
823 by: i64,
824 limit: i64,
825 ) -> Result<Claim> {
826 if by <= 0 {
827 return Err(Error::Overflow);
828 }
829 let slot = self.slot()?;
830 loop {
831 let current = slot.value.load(Ordering::Acquire) as i64;
832 if current >= limit {
833 return Ok(Claim::Exhausted);
834 }
835 let taken = by.min(limit.saturating_sub(current));
836 let after = current + taken;
837 if slot
838 .value
839 .compare_exchange(
840 current as u64,
841 after as u64,
842 Ordering::AcqRel,
843 Ordering::Acquire,
844 )
845 .is_ok()
846 {
847 slot.changed();
848 return Ok(Claim::Took { taken, after });
849 }
850 }
854 }
855}
856
857impl Orbital<u64> {
858 pub fn fetch_add(
859 &self,
860 by: u64,
861 ) -> Result<u64> {
862 let slot = self.slot()?;
863 slot.value
864 .try_update(Ordering::AcqRel, Ordering::Acquire, |bits| {
865 bits.checked_add(by)
866 })
867 .map(|previous| {
868 slot.changed();
869 previous + by
870 })
871 .map_err(|_| Error::Overflow)
872 }
873
874 pub fn fetch_sub(
875 &self,
876 by: u64,
877 ) -> Result<u64> {
878 let slot = self.slot()?;
879 slot.value
880 .try_update(Ordering::AcqRel, Ordering::Acquire, |bits| {
881 bits.checked_sub(by)
882 })
883 .map(|previous| {
884 slot.changed();
885 previous - by
886 })
887 .map_err(|_| Error::Overflow)
888 }
889}
890
891impl Orbital<f64> {
892 pub fn fetch_add(
895 &self,
896 by: f64,
897 ) -> Result<f64> {
898 let slot = self.slot()?;
899 let previous = slot
900 .value
901 .try_update(Ordering::AcqRel, Ordering::Acquire, |bits| {
902 Some((f64::from_bits(bits) + by).to_bits())
903 })
904 .unwrap_or_else(|bits| bits);
905 slot.changed();
906 Ok(f64::from_bits(previous) + by)
907 }
908}
909
910impl Orbital<bool> {
911 pub fn set(&self) -> Result<bool> {
913 self.swap(true)
914 }
915
916 pub fn clear(&self) -> Result<bool> {
918 self.swap(false)
919 }
920}
921
922#[repr(C, align(64))]
923struct CellStateHeader {
924 #[cfg(unix)]
925 magic: u32,
926 #[cfg(not(unix))]
927 _magic: u32,
928 #[cfg(unix)]
929 version: u16,
930 #[cfg(not(unix))]
931 _version: u16,
932 header_size: u16,
933 capacity: u32,
934 slot_size: u32,
935 hint: AtomicU32,
937 owners: u32,
938 _reserved: [u8; 40],
939}
940
941impl CellStateHeader {
942 fn new(owners: usize) -> Self {
943 Self {
944 #[cfg(unix)]
945 magic: STATE_MAGIC,
946 #[cfg(not(unix))]
947 _magic: 0,
948 #[cfg(unix)]
949 version: STATE_VERSION,
950 #[cfg(not(unix))]
951 _version: 0,
952 header_size: size_of::<Self>() as u16,
953 capacity: CELL_CAPACITY as u32,
954 slot_size: size_of::<CellSlot>() as u32,
955 hint: AtomicU32::new(0),
956 owners: owners as u32,
957 _reserved: [0; 40],
958 }
959 }
960}
961
962#[repr(C, align(64))]
966struct CellSlot {
967 state: AtomicU8,
968 tag: AtomicU8,
969 _reserved: [u8; 2],
970 generation: AtomicU32,
971 value: AtomicU64,
972 changes: AtomicU32,
975 wake: Wake,
977 _padding: [u8; 36],
978}
979
980impl CellSlot {
981 fn empty() -> Self {
982 Self {
983 state: AtomicU8::new(SLOT_EMPTY),
984 tag: AtomicU8::new(0),
985 _reserved: [0; 2],
986 generation: AtomicU32::new(0),
987 value: AtomicU64::new(0),
988 changes: AtomicU32::new(0),
989 wake: Wake::new(),
990 _padding: [0; 36],
991 }
992 }
993
994 fn changed(&self) {
999 self.changes.fetch_add(1, Ordering::SeqCst);
1000 self.wake.changed();
1001 }
1002
1003 fn is(
1004 &self,
1005 id: CellId,
1006 ) -> bool {
1007 self.state.load(Ordering::Acquire) == SLOT_OCCUPIED
1009 && self.generation.load(Ordering::Relaxed) == id.generation
1010 }
1011
1012 fn install(
1015 &self,
1016 tag: u8,
1017 value: u64,
1018 ) -> u32 {
1019 let generation = self
1020 .generation
1021 .load(Ordering::Relaxed)
1022 .checked_add(1)
1023 .expect("exhausted generations are never reused");
1024 self.tag.store(tag, Ordering::Relaxed);
1025 self.value.store(value, Ordering::Relaxed);
1026 self.changes.store(0, Ordering::Relaxed);
1027 self.wake.reset();
1028 self.generation.store(generation, Ordering::Relaxed);
1029 self.state.store(SLOT_OCCUPIED, Ordering::Release);
1030 generation
1031 }
1032}
1033
1034pub(crate) fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
1035 mutex.lock().unwrap_or_else(|error| error.into_inner())
1036}
1037
1038struct MemoryCellTable {
1039 _fleet: Arc<Fleet>,
1042 slots: Vec<CellSlot>,
1043 hint: AtomicU32,
1044 structural_lock: Mutex<()>,
1045 owners: Owners,
1046}
1047
1048impl MemoryCellTable {
1049 fn new(fleet: Arc<Fleet>) -> Self {
1050 Self {
1051 _fleet: fleet,
1052 slots: (0..CELL_CAPACITY).map(|_| CellSlot::empty()).collect(),
1053 hint: AtomicU32::new(0),
1054 structural_lock: Mutex::new(()),
1055 owners: Owners::memory(CELL_CAPACITY),
1056 }
1057 }
1058}
1059
1060type MemoryRegistry = HashMap<usize, Weak<MemoryCellTable>>;
1061
1062static MEMORY_TABLES: LazyLock<Mutex<MemoryRegistry>> =
1063 LazyLock::new(|| Mutex::new(HashMap::new()));
1064
1065fn memory_table(fleet: &Arc<Fleet>) -> Arc<MemoryCellTable> {
1066 let fleet_identity = Arc::as_ptr(fleet) as usize;
1067 let mut tables = lock_unpoisoned(&MEMORY_TABLES);
1068 tables.retain(|_, table| table.strong_count() > 0);
1069 if let Some(table) = tables.get(&fleet_identity).and_then(Weak::upgrade) {
1070 return table;
1071 }
1072 let table = Arc::new(MemoryCellTable::new(Arc::clone(fleet)));
1073 tables.insert(fleet_identity, Arc::downgrade(&table));
1074 table
1075}
1076
1077#[cfg(unix)]
1078struct ShmCellTable {
1079 shared: Arc<lifetime::Shared>,
1080 structural_lock: Mutex<()>,
1081}
1082
1083#[cfg(unix)]
1084impl ShmCellTable {
1085 fn open_or_create(
1086 name: &str,
1087 owners: usize,
1088 ) -> Result<Self> {
1089 use std::{io, ptr};
1090
1091 let (region, _initialization_lock) =
1092 ShmRegion::open_or_create_locked(name, shm_segment_size(owners)?)?;
1093 if region.created() {
1094 unsafe {
1095 ptr::write(
1096 region.as_ptr().cast::<CellStateHeader>(),
1097 CellStateHeader::new(owners),
1098 );
1099 let slots = region.as_ptr().add(size_of::<CellStateHeader>());
1100 ptr::write_bytes(
1101 slots,
1102 0,
1103 shm_segment_size(owners)? - size_of::<CellStateHeader>(),
1104 );
1105 }
1106 } else {
1107 let header = unsafe { &*region.as_ptr().cast::<CellStateHeader>() };
1108 if header.magic != STATE_MAGIC {
1109 return Err(Error::Io(io::Error::new(
1110 io::ErrorKind::InvalidData,
1111 format!(
1112 "SHM segment {name} has wrong magic 0x{:08X} (expected 0x{STATE_MAGIC:08X})",
1113 header.magic
1114 ),
1115 )));
1116 }
1117 if header.version != STATE_VERSION
1118 || usize::from(header.header_size)
1119 != size_of::<CellStateHeader>()
1120 || header.capacity as usize != CELL_CAPACITY
1121 || header.slot_size as usize != size_of::<CellSlot>()
1122 || header.owners as usize != owners
1123 {
1124 return Err(Error::Io(io::Error::new(
1125 io::ErrorKind::InvalidData,
1126 format!(
1127 "SHM segment {name} has an incompatible cell-state layout"
1128 ),
1129 )));
1130 }
1131 }
1132 let shared = lifetime::Shared::claim_locked(
1133 region,
1134 name,
1135 owners,
1136 CELL_CAPACITY,
1137 owner_offset(),
1138 )?;
1139 Ok(Self { shared, structural_lock: Mutex::new(()) })
1140 }
1141
1142 fn header(&self) -> &CellStateHeader {
1143 unsafe { &*self.shared.region.as_ptr().cast::<CellStateHeader>() }
1144 }
1145
1146 fn slots(&self) -> &[CellSlot] {
1147 unsafe {
1148 std::slice::from_raw_parts(
1149 self.shared
1150 .region
1151 .as_ptr()
1152 .add(size_of::<CellStateHeader>())
1153 .cast::<CellSlot>(),
1154 CELL_CAPACITY,
1155 )
1156 }
1157 }
1158
1159 fn with_structure<T>(
1160 &self,
1161 operation: impl FnOnce(&[CellSlot], &AtomicU32, &Owners) -> Result<T>,
1162 ) -> Result<T> {
1163 let owners = Owners::Shared(self.shared.clone());
1164 owners.check_process()?;
1165 let _local = lock_unpoisoned(&self.structural_lock);
1166 let _process = self.shared.region.lock_exclusive()?;
1167 operation(self.slots(), &self.header().hint, &owners)
1168 }
1169}
1170
1171#[cfg(unix)]
1172fn owner_offset() -> usize {
1173 size_of::<CellStateHeader>() + CELL_CAPACITY * size_of::<CellSlot>()
1174}
1175
1176#[cfg(unix)]
1177fn shm_segment_size(owners: usize) -> Result<usize> {
1178 lifetime::segment_size(owner_offset(), CELL_CAPACITY, owners)
1179}
1180
1181#[cfg(unix)]
1182fn shm_table(
1183 name: &str,
1184 owners: usize,
1185) -> Result<Arc<ShmCellTable>> {
1186 static TABLES: LazyLock<Mutex<HashMap<String, Weak<ShmCellTable>>>> =
1187 LazyLock::new(|| Mutex::new(HashMap::new()));
1188 let mut tables = lock_unpoisoned(&TABLES);
1189 tables.retain(|_, table| table.strong_count() != 0);
1190 if let Some(table) = tables.get(name).and_then(Weak::upgrade)
1191 && table.shared.is_current(owners)
1192 {
1193 return Ok(table);
1194 }
1195 let table = Arc::new(ShmCellTable::open_or_create(name, owners)?);
1196 tables.insert(name.to_owned(), Arc::downgrade(&table));
1197 Ok(table)
1198}
1199
1200const _: () = assert!(CELL_CAPACITY.is_power_of_two());
1201const _: () = assert!(CELL_CAPACITY <= u32::MAX as usize);
1202const _: () = assert!(size_of::<CellStateHeader>() == 64);
1203const _: () = assert!(size_of::<CellSlot>() == 64);
1204
1205#[cfg(test)]
1206mod registry_tests {
1207 use std::sync::Arc;
1208
1209 use orbit_core::Fleet;
1210
1211 use super::Cells;
1212
1213 #[test]
1218 fn a_memory_table_keeps_its_fleet_alive() {
1219 let fleet = Arc::new(Fleet::join("cell-registry-test", 1).unwrap());
1220 let cells = Cells::new(Arc::clone(&fleet)).unwrap();
1221 assert!(Arc::strong_count(&fleet) > 1);
1222 drop(cells);
1223 assert_eq!(Arc::strong_count(&fleet), 1);
1224 }
1225}
1226
1227#[cfg(test)]
1228mod lifetime_tests {
1229 use super::*;
1230
1231 #[test]
1232 fn a_retired_cell_keeps_old_waiter_accounting_until_its_handles_drop() {
1233 let cells =
1234 Cells::new(Arc::new(Fleet::join("example", 1).unwrap())).unwrap();
1235 let old = cells.allocate(7_u64).unwrap();
1236 let id = old.id();
1237 let held: Vec<_> = (1..CELL_CAPACITY)
1238 .map(|_| cells.allocate(0_u64).unwrap())
1239 .collect();
1240 let slot = old.slot().unwrap();
1241 slot.wake.waiters.fetch_add(1, Ordering::SeqCst);
1242 cells.release(id).unwrap();
1243 assert!(matches!(cells.allocate(42_u64), Err(Error::Full { .. })));
1244 assert_eq!(slot.wake.waiters.fetch_sub(1, Ordering::SeqCst), 1);
1245 drop(old);
1246 let fresh = cells.allocate(42_u64).unwrap();
1247 assert_eq!(fresh.id().index(), id.index());
1248 assert_eq!(
1249 fresh.slot().unwrap().wake.waiters.load(Ordering::Acquire),
1250 0
1251 );
1252 assert_eq!(fresh.load().unwrap(), 42);
1253 drop(held);
1254 }
1255
1256 #[test]
1257 fn exhausted_cell_generations_are_never_reissued() {
1258 let cells =
1259 Cells::new(Arc::new(Fleet::join("example", 1).unwrap())).unwrap();
1260 cells
1261 .with_structure(|slots, _, _| {
1262 for slot in slots {
1263 slot.generation.store(u32::MAX, Ordering::Relaxed);
1264 }
1265 Ok(())
1266 })
1267 .unwrap();
1268 assert!(matches!(cells.allocate(1_u64), Err(Error::Full { .. })));
1269 }
1270}
1271
1272#[cfg(test)]
1273mod wait_tests {
1274 use std::sync::Arc;
1275 use std::time::Duration;
1276
1277 use orbit_core::Fleet;
1278
1279 use super::{Cells, Error};
1280
1281 fn cells() -> Cells {
1282 Cells::new(Arc::new(Fleet::join("cell-wait-test", 1).unwrap())).unwrap()
1283 }
1284
1285 #[test]
1289 fn a_write_wakes_a_waiter_once() {
1290 let cells = cells();
1291 let cell = cells.allocate(0_i64).unwrap();
1292 let since = cell.version().unwrap();
1293 let watcher = cell.clone();
1294 let waiter = std::thread::spawn(move || watcher.wait_changed(since));
1295
1296 std::thread::sleep(Duration::from_millis(30));
1297 for _ in 0..10 {
1298 cell.fetch_add(1).unwrap();
1299 }
1300 let now = waiter.join().unwrap().unwrap();
1301 assert!(now > since);
1302 assert_eq!(cell.load().unwrap(), 10);
1303 assert_eq!(cell.version().unwrap(), cell.version().unwrap());
1305 }
1306
1307 #[test]
1310 fn release_wakes_a_waiter_stale() {
1311 let cells = cells();
1312 let cell = cells.allocate(7_i64).unwrap();
1313 let since = cell.version().unwrap();
1314 let watcher = cell.clone();
1315 let waiter = std::thread::spawn(move || watcher.wait_changed(since));
1316
1317 std::thread::sleep(Duration::from_millis(30));
1318 cell.release().unwrap();
1319 assert!(matches!(waiter.join().unwrap(), Err(Error::Stale(_))));
1320 }
1321}