1use std::sync::atomic::{AtomicU64, Ordering};
50use std::sync::{Arc, Mutex, OnceLock, RwLock};
51
52use bytes::Bytes;
53
54use crate::id::NetId64;
55use crate::{NodeId, OrbitTyped};
56
57pub mod cursor;
58#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
59mod readiness;
60#[cfg(unix)]
61pub mod shm;
62
63#[cfg(any(
64 target_os = "linux",
65 target_os = "freebsd",
66 target_os = "macos"
67))]
68pub use readiness::RingEventFd;
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72#[repr(u8)]
73pub enum RingTopology {
74 Shared = 0,
76 PerNode = 1,
80 SharedOrdered = 2,
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub struct RingSpec {
92 pub capacity: usize,
94 pub payload_capacity: usize,
96 pub topology: RingTopology,
98}
99
100impl RingSpec {
101 pub const fn new(
102 capacity: usize,
103 payload_capacity: usize,
104 ) -> Self {
105 Self { capacity, payload_capacity, topology: RingTopology::Shared }
106 }
107
108 pub const fn per_node(
110 capacity: usize,
111 payload_capacity: usize,
112 ) -> Self {
113 Self { capacity, payload_capacity, topology: RingTopology::PerNode }
114 }
115
116 pub const fn shared_ordered(
118 capacity: usize,
119 payload_capacity: usize,
120 ) -> Self {
121 Self {
122 capacity,
123 payload_capacity,
124 topology: RingTopology::SharedOrdered,
125 }
126 }
127
128 pub(crate) fn assert_valid(self) {
129 assert!(self.capacity > 0, "ring capacity must be > 0");
130 assert!(
131 self.capacity.is_power_of_two(),
132 "ring capacity must be a power of two"
133 );
134 assert!(
135 self.payload_capacity <= u32::MAX as usize,
136 "ring payload capacity must fit in u32"
137 );
138 }
139}
140
141struct RingLane {
142 write_pos: AtomicU64,
143 write_lock: Mutex<()>,
144 slots: Vec<RwLock<Option<Frame>>>,
145}
146
147impl RingLane {
148 fn new(capacity: usize) -> Self {
149 let mut slots = Vec::with_capacity(capacity);
150 for _ in 0..capacity {
151 slots.push(RwLock::new(None));
152 }
153 Self { write_pos: AtomicU64::new(0), write_lock: Mutex::new(()), slots }
154 }
155}
156
157#[derive(Clone, Debug, PartialEq, Eq)]
159pub struct Frame {
160 pub id: NetId64,
161 pub kind: u8,
162 pub ver: u64,
163 pub payload: Bytes,
164}
165
166pub struct Ring {
170 kind: u8,
173 capacity: usize,
175 payload_capacity: usize,
177 topology: RingTopology,
178 version_counter: AtomicU64,
180 pub(crate) publication: RwLock<()>,
181 lanes: Vec<RingLane>,
182}
183
184impl Ring {
185 pub fn new<T: OrbitTyped>() -> Self {
188 Self::new_for_fleet::<T>(1)
189 }
190
191 pub fn new_for_fleet<T: OrbitTyped>(fleet_capacity: u16) -> Self {
194 assert!(fleet_capacity > 0, "ring fleet capacity must be > 0");
195 let spec = T::RING_SPEC;
196 spec.assert_valid();
197 let capacity = spec.capacity;
198 let lane_count = match spec.topology {
199 RingTopology::Shared | RingTopology::SharedOrdered => 1,
200 RingTopology::PerNode => usize::from(fleet_capacity),
201 };
202 let mut lanes = Vec::with_capacity(lane_count);
203 for _ in 0..lane_count {
204 lanes.push(RingLane::new(capacity));
205 }
206 Self {
207 kind: T::KIND,
208 capacity,
209 payload_capacity: spec.payload_capacity,
210 topology: spec.topology,
211 version_counter: AtomicU64::new(0),
212 publication: RwLock::new(()),
213 lanes,
214 }
215 }
216
217 pub fn kind(&self) -> u8 {
219 self.kind
220 }
221
222 pub fn capacity(&self) -> usize {
224 self.capacity
225 }
226
227 pub fn payload_capacity(&self) -> usize {
229 self.payload_capacity
230 }
231
232 pub fn spec(&self) -> RingSpec {
233 RingSpec {
234 capacity: self.capacity,
235 payload_capacity: self.payload_capacity,
236 topology: self.topology,
237 }
238 }
239
240 pub fn head(&self) -> u64 {
242 self.lanes[0].write_pos.load(Ordering::Acquire)
243 }
244
245 pub fn lane_count(&self) -> usize {
247 self.lanes.len()
248 }
249
250 pub fn lane_head(
252 &self,
253 node_id: NodeId,
254 ) -> u64 {
255 self.lane(node_id).write_pos.load(Ordering::Acquire)
256 }
257
258 pub fn next_version(&self) -> u64 {
264 self.version_counter
265 .fetch_add(1, Ordering::AcqRel)
266 .checked_add(1)
267 .expect("ring semantic version exhausted")
268 }
269
270 pub fn current_version(&self) -> u64 {
272 self.version_counter.load(Ordering::Acquire)
273 }
274
275 pub fn write(
283 &self,
284 node_id: NodeId,
285 frame_kind: u8,
286 ver: u64,
287 payload: Bytes,
288 ) -> NetId64 {
289 assert!(
290 payload.len() <= self.payload_capacity,
291 "payload {} > ring payload capacity {}",
292 payload.len(),
293 self.payload_capacity
294 );
295 let lane = self.lane(node_id);
296 match self.topology {
297 RingTopology::Shared => {
298 let counter = lane.write_pos.fetch_add(1, Ordering::AcqRel);
299 self.write_frame(
300 lane, node_id, counter, frame_kind, ver, payload,
301 )
302 }
303 RingTopology::PerNode | RingTopology::SharedOrdered => {
304 let _write = lane
305 .write_lock
306 .lock()
307 .unwrap_or_else(|error| error.into_inner());
308 let counter = lane.write_pos.load(Ordering::Relaxed);
309 let id = self.write_frame(
310 lane, node_id, counter, frame_kind, ver, payload,
311 );
312 lane.write_pos
313 .store(counter.wrapping_add(1), Ordering::Release);
314 id
315 }
316 }
317 }
318
319 pub fn write_batch(
326 &self,
327 node_id: NodeId,
328 frame_kind: u8,
329 ver: u64,
330 payloads: Vec<Bytes>,
331 ) -> Vec<NetId64> {
332 assert!(
333 payloads.len() <= self.capacity,
334 "batch {} > ring capacity {}",
335 payloads.len(),
336 self.capacity
337 );
338 for payload in &payloads {
339 assert!(
340 payload.len() <= self.payload_capacity,
341 "payload {} > ring payload capacity {}",
342 payload.len(),
343 self.payload_capacity
344 );
345 }
346 if payloads.is_empty() {
347 return Vec::new();
348 }
349
350 let lane = self.lane(node_id);
351 match self.topology {
352 RingTopology::Shared => {
353 let start = lane
354 .write_pos
355 .fetch_add(payloads.len() as u64, Ordering::AcqRel);
356 payloads
357 .into_iter()
358 .enumerate()
359 .map(|(offset, payload)| {
360 self.write_frame(
361 lane,
362 node_id,
363 start.wrapping_add(offset as u64),
364 frame_kind,
365 ver,
366 payload,
367 )
368 })
369 .collect()
370 }
371 RingTopology::PerNode | RingTopology::SharedOrdered => {
372 let _write = lane
373 .write_lock
374 .lock()
375 .unwrap_or_else(|error| error.into_inner());
376 let start = lane.write_pos.load(Ordering::Relaxed);
377 let ids = payloads
378 .into_iter()
379 .enumerate()
380 .map(|(offset, payload)| {
381 self.write_frame(
382 lane,
383 node_id,
384 start.wrapping_add(offset as u64),
385 frame_kind,
386 ver,
387 payload,
388 )
389 })
390 .collect::<Vec<_>>();
391 lane.write_pos.store(
392 start.wrapping_add(ids.len() as u64),
393 Ordering::Release,
394 );
395 ids
396 }
397 }
398 }
399
400 pub fn read(
408 &self,
409 id: NetId64,
410 ) -> Option<Frame> {
411 if id.kind() != self.kind {
412 return None;
413 }
414 let lane = self.lane_for_frame(id)?;
415 let slot_idx = (id.counter() as usize) % self.capacity;
416 let guard = lane.slots[slot_idx].read().expect("ring slot poisoned");
417 match &*guard {
418 Some(f) if f.id == id => Some(f.clone()),
419 _ => None,
420 }
421 }
422
423 pub fn read_head(&self) -> Option<Frame> {
427 let head = self.head();
428 if head == 0 {
429 return None;
430 }
431 let slot_idx = ((head - 1) as usize) % self.capacity;
432 self.lanes[0].slots[slot_idx]
433 .read()
434 .expect("ring slot poisoned")
435 .clone()
436 }
437
438 pub fn read_at(
445 &self,
446 counter: u64,
447 ) -> Option<Frame> {
448 let slot_idx = (counter as usize) % self.capacity;
449 self.lanes[0].slots[slot_idx]
450 .read()
451 .expect("ring slot poisoned")
452 .clone()
453 }
454
455 pub(crate) fn read_state_at(
456 &self,
457 counter: u64,
458 ) -> cursor::RingRead {
459 match self.read_at(counter) {
460 Some(frame) if frame.id.counter() == counter => {
461 cursor::RingRead::Ready(frame)
462 }
463 Some(frame) if frame.id.counter() > counter => {
464 cursor::RingRead::Unavailable
465 }
466 Some(_) | None => cursor::RingRead::Pending,
467 }
468 }
469
470 pub(crate) fn read_lane_at(
471 &self,
472 node_id: NodeId,
473 counter: u64,
474 ) -> Option<Frame> {
475 let lane = self.lane(node_id);
476 let slot_idx = (counter as usize) % self.capacity;
477 lane.slots[slot_idx].read().expect("ring slot poisoned").clone()
478 }
479
480 pub(crate) fn read_lane_state_at(
481 &self,
482 node_id: NodeId,
483 counter: u64,
484 ) -> cursor::RingRead {
485 match self.read_lane_at(node_id, counter) {
486 Some(frame) if frame.id.counter() == counter => {
487 cursor::RingRead::Ready(frame)
488 }
489 Some(frame) if frame.id.counter() > counter => {
490 cursor::RingRead::Unavailable
491 }
492 Some(_) | None if self.topology != RingTopology::Shared => {
493 cursor::RingRead::Unavailable
494 }
495 Some(_) | None => cursor::RingRead::Pending,
496 }
497 }
498
499 pub fn reset(&self) {
504 for lane in &self.lanes {
505 for slot in &lane.slots {
506 *slot.write().expect("ring slot poisoned") = None;
507 }
508 lane.write_pos.store(0, Ordering::Release);
509 }
510 self.version_counter.store(0, Ordering::Release);
511 }
512
513 fn lane(
514 &self,
515 node_id: NodeId,
516 ) -> &RingLane {
517 let index = match self.topology {
518 RingTopology::Shared | RingTopology::SharedOrdered => 0,
519 RingTopology::PerNode => usize::from(node_id.get()),
520 };
521 self.lanes.get(index).unwrap_or_else(|| {
522 panic!(
523 "node {} is outside ring lane count {}",
524 node_id.get(),
525 self.lanes.len()
526 )
527 })
528 }
529
530 fn lane_for_frame(
531 &self,
532 id: NetId64,
533 ) -> Option<&RingLane> {
534 let index = match self.topology {
535 RingTopology::Shared | RingTopology::SharedOrdered => 0,
536 RingTopology::PerNode => usize::from(id.node()),
537 };
538 self.lanes.get(index)
539 }
540
541 fn write_frame(
542 &self,
543 lane: &RingLane,
544 node_id: NodeId,
545 counter: u64,
546 frame_kind: u8,
547 ver: u64,
548 payload: Bytes,
549 ) -> NetId64 {
550 let id = NetId64::make(self.kind, node_id.get(), counter);
551 let slot_idx = (counter as usize) % self.capacity;
552 let frame = Frame { id, kind: frame_kind, ver, payload };
553 let mut guard =
554 lane.slots[slot_idx].write().expect("ring slot poisoned");
555 *guard = Some(frame);
556 id
557 }
558}
559
560impl cursor::RingFrameSource for Ring {
561 fn kind(&self) -> u8 {
562 Ring::kind(self)
563 }
564
565 fn head(&self) -> u64 {
566 Ring::head(self)
567 }
568
569 fn capacity(&self) -> usize {
570 Ring::capacity(self)
571 }
572
573 fn read_at(
574 &self,
575 counter: u64,
576 ) -> Option<Frame> {
577 Ring::read_at(self, counter)
578 }
579
580 fn read_state_at(
581 &self,
582 counter: u64,
583 ) -> cursor::RingRead {
584 Ring::read_state_at(self, counter)
585 }
586}
587
588#[cfg(unix)]
589impl cursor::RingFrameSource for shm::ShmRing {
590 fn kind(&self) -> u8 {
591 shm::ShmRing::kind(self)
592 }
593
594 fn head(&self) -> u64 {
595 shm::ShmRing::head(self)
596 }
597
598 fn capacity(&self) -> usize {
599 shm::ShmRing::capacity(self)
600 }
601
602 fn read_at(
603 &self,
604 counter: u64,
605 ) -> Option<Frame> {
606 shm::ShmRing::read_at(self, counter)
607 }
608
609 fn read_state_at(
610 &self,
611 counter: u64,
612 ) -> cursor::RingRead {
613 shm::ShmRing::read_state_at(self, counter)
614 }
615}
616
617impl std::fmt::Debug for Ring {
618 fn fmt(
619 &self,
620 f: &mut std::fmt::Formatter<'_>,
621 ) -> std::fmt::Result {
622 f.debug_struct("Ring")
623 .field("kind", &self.kind)
624 .field("capacity", &self.capacity)
625 .field("payload_capacity", &self.payload_capacity)
626 .field("topology", &self.topology)
627 .field("lane_count", &self.lanes.len())
628 .field("head", &self.head())
629 .finish()
630 }
631}
632
633pub(crate) struct ByKind<V> {
639 slots: Box<[OnceLock<V>; 256]>,
640}
641
642impl<V> ByKind<V> {
643 pub(crate) fn new() -> Self {
644 Self { slots: Box::new(std::array::from_fn(|_| OnceLock::new())) }
645 }
646
647 pub(crate) fn get(
648 &self,
649 kind: u8,
650 ) -> Option<&V> {
651 self.slots[usize::from(kind)].get()
652 }
653
654 pub(crate) fn get_or_init(
655 &self,
656 kind: u8,
657 init: impl FnOnce() -> V,
658 ) -> &V {
659 self.slots[usize::from(kind)].get_or_init(init)
660 }
661
662 pub(crate) fn get_or_insert(
665 &self,
666 kind: u8,
667 value: V,
668 ) -> &V {
669 let slot = &self.slots[usize::from(kind)];
670 let _ = slot.set(value);
671 slot.get().expect("a kind slot holds a value once set")
672 }
673
674 pub(crate) fn len(&self) -> usize {
675 self.slots.iter().filter(|slot| slot.get().is_some()).count()
676 }
677}
678
679pub(crate) struct RingRegistry {
682 fleet_capacity: u16,
683 rings: ByKind<Arc<Ring>>,
684}
685
686impl RingRegistry {
687 pub fn new(fleet_capacity: u16) -> Self {
688 Self { fleet_capacity, rings: ByKind::new() }
689 }
690
691 pub fn ring_for<T: OrbitTyped>(&self) -> &Arc<Ring> {
694 let ring = self.rings.get_or_init(T::KIND, || {
695 Arc::new(Ring::new_for_fleet::<T>(self.fleet_capacity))
696 });
697 assert_eq!(
698 ring.spec(),
699 T::RING_SPEC,
700 "OrbitTyped KIND {} was reused with a different ring spec",
701 T::KIND
702 );
703 ring
704 }
705
706 pub fn get_or_create<T: OrbitTyped>(&self) -> Arc<Ring> {
708 self.ring_for::<T>().clone()
709 }
710
711 pub fn lookup(
713 &self,
714 kind: u8,
715 ) -> Option<Arc<Ring>> {
716 self.rings.get(kind).cloned()
717 }
718}