1#![cfg(unix)]
56
57use std::ptr;
58use std::sync::Mutex;
59use std::sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering, fence};
60
61use bytes::Bytes;
62
63use crate::NodeId;
64use crate::id::NetId64;
65use crate::ring::{Frame, RingSpec, RingTopology};
66use crate::shm::{self, ShmRegion};
67
68const MAGIC: u32 = 0x4F524254; const VERSION: u32 = 3;
74const SLOT_ALIGNMENT: usize = 64;
75
76#[repr(C, align(64))]
78struct ShmRingHeader {
79 version_counter: AtomicU64,
80 capacity: u64,
81 magic: u32,
82 version: u32,
83 payload_capacity: u32,
84 slot_stride: u32,
85 kind: u8,
86 topology: u8,
87 lane_count: u16,
88 notification_generation: AtomicU32,
92 notification_waiters: AtomicU32,
98 _reserved: [u8; 20]
101}
102
103#[repr(C, align(64))]
104struct ShmLaneHeader {
105 write_pos: AtomicU64,
106 _reserved: [u8; 56]
107}
108
109#[repr(C)]
111struct ShmSlotHeader {
112 seq: AtomicU64,
116 id: AtomicU64,
118 ver: AtomicU64,
120 payload_len: AtomicU32,
122 kind: AtomicU8,
124 _reserved: [u8; 3]
125}
126
127const SLOT_HEADER_SIZE: usize = std::mem::size_of::<ShmSlotHeader>();
137const HEADER_SIZE: usize = std::mem::size_of::<ShmRingHeader>();
138const LANE_HEADER_SIZE: usize = std::mem::size_of::<ShmLaneHeader>();
139
140const _: () = assert!(HEADER_SIZE == 64);
141const _: () = assert!(LANE_HEADER_SIZE == 64);
142const _: () = assert!(SLOT_HEADER_SIZE == 32);
143
144fn invalid_input(message: impl Into<String>) -> std::io::Error {
145 std::io::Error::new(std::io::ErrorKind::InvalidInput, message.into())
146}
147
148fn lane_count_for(
149 spec: RingSpec,
150 fleet_capacity: u16
151) -> std::io::Result<usize> {
152 if fleet_capacity == 0 {
153 return Err(invalid_input("ShmRing fleet capacity must be > 0"));
154 }
155 Ok(match spec.topology {
156 RingTopology::Shared | RingTopology::SharedOrdered => 1,
157 RingTopology::PerNode => usize::from(fleet_capacity)
158 })
159}
160
161fn checked_layout(
162 spec: RingSpec,
163 fleet_capacity: u16
164) -> std::io::Result<(usize, usize, usize)> {
165 if spec.capacity == 0 {
166 return Err(invalid_input("ShmRing capacity must be > 0"));
167 }
168 if !spec.capacity.is_power_of_two() {
169 return Err(invalid_input("ShmRing capacity must be a power of two"));
170 }
171 if spec.payload_capacity > u32::MAX as usize {
172 return Err(invalid_input("ShmRing payload capacity must fit in u32"));
173 }
174
175 let unaligned = SLOT_HEADER_SIZE
176 .checked_add(spec.payload_capacity)
177 .ok_or_else(|| invalid_input("ShmRing slot size overflow"))?;
178 let slot_stride = unaligned
179 .checked_add(SLOT_ALIGNMENT - 1)
180 .map(|value| value & !(SLOT_ALIGNMENT - 1))
181 .ok_or_else(|| invalid_input("ShmRing slot stride overflow"))?;
182 if slot_stride > u32::MAX as usize {
183 return Err(invalid_input("ShmRing slot stride must fit in u32"));
184 }
185 let lane_count = lane_count_for(spec, fleet_capacity)?;
186 let lane_headers_size = lane_count
187 .checked_mul(LANE_HEADER_SIZE)
188 .ok_or_else(|| invalid_input("ShmRing lane header size overflow"))?;
189 let slots_offset = HEADER_SIZE
190 .checked_add(lane_headers_size)
191 .ok_or_else(|| invalid_input("ShmRing slots offset overflow"))?;
192 let slots_per_lane = spec
193 .capacity
194 .checked_mul(slot_stride)
195 .ok_or_else(|| invalid_input("ShmRing lane size overflow"))?;
196 let slots_size = lane_count
197 .checked_mul(slots_per_lane)
198 .ok_or_else(|| invalid_input("ShmRing slots size overflow"))?;
199 let segment_size = slots_offset
200 .checked_add(slots_size)
201 .ok_or_else(|| invalid_input("ShmRing segment size overflow"))?;
202 Ok((slot_stride, slots_offset, segment_size))
203}
204
205pub fn segment_size_for_spec(spec: RingSpec) -> std::io::Result<usize> {
207 segment_size_for_spec_and_fleet(spec, 1)
208}
209
210pub fn segment_size_for_spec_and_fleet(
212 spec: RingSpec,
213 fleet_capacity: u16
214) -> std::io::Result<usize> {
215 checked_layout(spec, fleet_capacity).map(|(_, _, segment_size)| segment_size)
216}
217
218pub struct ShmRing {
229 region: ShmRegion,
230 kind: u8,
231 capacity: usize,
232 payload_capacity: usize,
233 topology: RingTopology,
234 lane_count: usize,
235 slot_stride: usize,
236 slots_offset: usize,
237 lane_stride: usize,
238 write_locks: Vec<Mutex<()>>
239}
240
241impl ShmRing {
242 pub fn open_or_create(
246 fleet_name: &str,
247 kind: u8,
248 spec: RingSpec
249 ) -> std::io::Result<Self> {
250 Self::open_or_create_for_fleet(fleet_name, kind, spec, 1)
251 }
252
253 pub fn open_or_create_for_fleet(
256 fleet_name: &str,
257 kind: u8,
258 spec: RingSpec,
259 fleet_capacity: u16
260 ) -> std::io::Result<Self> {
261 let lane_count = lane_count_for(spec, fleet_capacity)?;
262 let (slot_stride, slots_offset, size) = checked_layout(spec, fleet_capacity)?;
263 let lane_stride = spec
264 .capacity
265 .checked_mul(slot_stride)
266 .ok_or_else(|| invalid_input("ShmRing lane stride overflow"))?;
267 let name = shm::ring_segment_name(fleet_name, kind);
268
269 let (region, _initialization_lock) = ShmRegion::open_or_create_locked(&name, size)?;
279
280 if region.created() {
283 unsafe {
286 let header_ptr = region.as_ptr() as *mut ShmRingHeader;
287 ptr::write(
288 header_ptr,
289 ShmRingHeader {
290 version_counter: AtomicU64::new(0),
291 capacity: spec.capacity as u64,
292 magic: MAGIC,
293 version: VERSION,
294 payload_capacity: spec.payload_capacity as u32,
295 slot_stride: slot_stride as u32,
296 kind,
297 topology: spec.topology as u8,
298 lane_count: lane_count as u16,
299 notification_generation: AtomicU32::new(0),
300 notification_waiters: AtomicU32::new(0),
301 _reserved: [0; 20]
302 }
303 );
304
305 for lane in 0..lane_count {
306 let lane_ptr = region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
307 as *mut ShmLaneHeader;
308 ptr::write(
309 lane_ptr,
310 ShmLaneHeader { write_pos: AtomicU64::new(0), _reserved: [0; 56] }
311 );
312 }
313
314 let slots_ptr = region.as_ptr().add(slots_offset);
319 ptr::write_bytes(slots_ptr, 0, lane_count * lane_stride);
320 }
321 } else {
322 let header = unsafe { &*(region.as_ptr() as *const ShmRingHeader) };
325 if header.magic != MAGIC {
326 return Err(std::io::Error::new(
327 std::io::ErrorKind::InvalidData,
328 format!(
329 "SHM segment {} has wrong magic 0x{:08X} (expected 0x{:08X})",
330 name, header.magic, MAGIC
331 )
332 ));
333 }
334 if header.version != VERSION {
335 return Err(std::io::Error::new(
336 std::io::ErrorKind::InvalidData,
337 format!("SHM segment {} version {} != local {}", name, header.version, VERSION)
338 ));
339 }
340 if header.kind != kind {
341 return Err(std::io::Error::new(
342 std::io::ErrorKind::InvalidData,
343 format!("SHM segment {} kind {} != requested {}", name, header.kind, kind)
344 ));
345 }
346 if header.topology != spec.topology as u8 {
347 return Err(std::io::Error::new(
348 std::io::ErrorKind::InvalidData,
349 format!(
350 "SHM segment {} topology {} != requested {}",
351 name, header.topology, spec.topology as u8
352 )
353 ));
354 }
355 if header.lane_count as usize != lane_count {
356 return Err(std::io::Error::new(
357 std::io::ErrorKind::InvalidData,
358 format!(
359 "SHM segment {} lane count {} != requested {}",
360 name, header.lane_count, lane_count
361 )
362 ));
363 }
364 if header.capacity as usize != spec.capacity {
365 return Err(std::io::Error::new(
366 std::io::ErrorKind::InvalidData,
367 format!(
368 "SHM segment {} capacity {} != requested {}",
369 name, header.capacity, spec.capacity
370 )
371 ));
372 }
373 if header.payload_capacity as usize != spec.payload_capacity {
374 return Err(std::io::Error::new(
375 std::io::ErrorKind::InvalidData,
376 format!(
377 "SHM segment {} payload capacity {} != requested {}",
378 name, header.payload_capacity, spec.payload_capacity
379 )
380 ));
381 }
382 if header.slot_stride as usize != slot_stride {
383 return Err(std::io::Error::new(
384 std::io::ErrorKind::InvalidData,
385 format!(
386 "SHM segment {} slot stride {} != requested {}",
387 name, header.slot_stride, slot_stride
388 )
389 ));
390 }
391 }
392
393 Ok(Self {
394 region,
395 kind,
396 capacity: spec.capacity,
397 payload_capacity: spec.payload_capacity,
398 topology: spec.topology,
399 lane_count,
400 slot_stride,
401 slots_offset,
402 lane_stride,
403 write_locks: (0..lane_count).map(|_| Mutex::new(())).collect()
404 })
405 }
406
407 pub fn created(&self) -> bool {
410 self.region.created()
411 }
412
413 pub fn unlink(&self) -> std::io::Result<()> {
417 self.region.unlink()
418 }
419
420 pub fn kind(&self) -> u8 {
422 self.kind
423 }
424
425 pub fn capacity(&self) -> usize {
427 self.capacity
428 }
429
430 pub fn lane_count(&self) -> usize {
432 self.lane_count
433 }
434
435 pub fn payload_capacity(&self) -> usize {
437 self.payload_capacity
438 }
439
440 pub fn spec(&self) -> RingSpec {
441 RingSpec {
442 capacity: self.capacity,
443 payload_capacity: self.payload_capacity,
444 topology: self.topology
445 }
446 }
447
448 fn lane_header(
449 &self,
450 lane: usize
451 ) -> &ShmLaneHeader {
452 debug_assert!(lane < self.lane_count);
453 unsafe {
454 &*(self.region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
455 as *const ShmLaneHeader)
456 }
457 }
458
459 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
460 pub(crate) fn notification_generation(&self) -> &AtomicU32 {
461 unsafe { &(*(self.region.as_ptr() as *const ShmRingHeader)).notification_generation }
464 }
465
466 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
467 pub(crate) fn notification_waiters(&self) -> &AtomicU32 {
468 unsafe { &(*(self.region.as_ptr() as *const ShmRingHeader)).notification_waiters }
470 }
471
472 fn slot_ptr(
473 &self,
474 lane: usize,
475 idx: usize
476 ) -> *mut ShmSlotHeader {
477 debug_assert!(lane < self.lane_count);
478 debug_assert!(idx < self.capacity);
479 unsafe {
482 let base = self.region.as_ptr().add(self.slots_offset + lane * self.lane_stride);
483 base.add(idx * self.slot_stride).cast::<ShmSlotHeader>()
484 }
485 }
486
487 unsafe fn payload_ptr(slot_ptr: *mut ShmSlotHeader) -> *mut AtomicU8 {
498 unsafe { slot_ptr.cast::<u8>().add(SLOT_HEADER_SIZE).cast::<AtomicU8>() }
499 }
500
501 pub fn head(&self) -> u64 {
503 self.lane_header(0).write_pos.load(Ordering::Acquire)
504 }
505
506 pub fn lane_head(
508 &self,
509 node_id: NodeId
510 ) -> u64 {
511 let lane = self.lane_index(node_id);
512 self.lane_header(lane).write_pos.load(Ordering::Acquire)
513 }
514
515 pub fn next_version(&self) -> u64 {
518 let header = unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
519 header
520 .version_counter
521 .fetch_add(1, Ordering::AcqRel)
522 .checked_add(1)
523 .expect("SHM ring semantic version exhausted")
524 }
525
526 pub fn current_version(&self) -> u64 {
528 let header = unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
529 header.version_counter.load(Ordering::Acquire)
530 }
531
532 pub fn write(
535 &self,
536 node_id: NodeId,
537 frame_kind: u8,
538 ver: u64,
539 payload: Bytes
540 ) -> std::io::Result<NetId64> {
541 if payload.len() > self.payload_capacity {
542 return Err(std::io::Error::new(
543 std::io::ErrorKind::InvalidInput,
544 format!(
545 "payload {} > ring payload capacity {}",
546 payload.len(),
547 self.payload_capacity
548 )
549 ));
550 }
551
552 let lane = self.lane_index(node_id);
553 match self.topology {
554 RingTopology::Shared => {
555 let counter = self.lane_header(lane).write_pos.fetch_add(1, Ordering::AcqRel);
556 Ok(self.write_slot(lane, node_id, counter, frame_kind, ver, &payload))
557 }
558 RingTopology::PerNode => {
559 let _write =
560 self.write_locks[lane].lock().unwrap_or_else(|error| error.into_inner());
561 let lane_header = self.lane_header(lane);
562 let counter = lane_header.write_pos.load(Ordering::Relaxed);
563 let id = self.write_slot(lane, node_id, counter, frame_kind, ver, &payload);
564 lane_header.write_pos.store(counter.wrapping_add(1), Ordering::Release);
565 Ok(id)
566 }
567 RingTopology::SharedOrdered => {
568 let _write =
569 self.write_locks[lane].lock().unwrap_or_else(|error| error.into_inner());
570 let _cross_process = self.region.lock_exclusive()?;
571 let lane_header = self.lane_header(lane);
572 let counter = lane_header.write_pos.load(Ordering::Relaxed);
573 let id = self.write_slot(lane, node_id, counter, frame_kind, ver, &payload);
574 lane_header.write_pos.store(counter.wrapping_add(1), Ordering::Release);
575 Ok(id)
576 }
577 }
578 }
579
580 pub fn write_batch(
585 &self,
586 node_id: NodeId,
587 frame_kind: u8,
588 ver: u64,
589 payloads: Vec<Bytes>
590 ) -> std::io::Result<Vec<NetId64>> {
591 if payloads.len() > self.capacity {
592 return Err(std::io::Error::new(
593 std::io::ErrorKind::InvalidInput,
594 format!("batch {} > ring capacity {}", payloads.len(), self.capacity)
595 ));
596 }
597 if let Some(payload) = payloads.iter().find(|payload| payload.len() > self.payload_capacity)
598 {
599 return Err(std::io::Error::new(
600 std::io::ErrorKind::InvalidInput,
601 format!(
602 "payload {} > ring payload capacity {}",
603 payload.len(),
604 self.payload_capacity
605 )
606 ));
607 }
608 if payloads.is_empty() {
609 return Ok(Vec::new());
610 }
611
612 let lane = self.lane_index(node_id);
613 let write_slots = |start: u64, payloads: Vec<Bytes>| {
614 payloads
615 .into_iter()
616 .enumerate()
617 .map(|(offset, payload)| {
618 self.write_slot(
619 lane,
620 node_id,
621 start.wrapping_add(offset as u64),
622 frame_kind,
623 ver,
624 &payload
625 )
626 })
627 .collect::<Vec<_>>()
628 };
629
630 match self.topology {
631 RingTopology::Shared => {
632 let start = self
633 .lane_header(lane)
634 .write_pos
635 .fetch_add(payloads.len() as u64, Ordering::AcqRel);
636 Ok(write_slots(start, payloads))
637 }
638 RingTopology::PerNode => {
639 let _write =
640 self.write_locks[lane].lock().unwrap_or_else(|error| error.into_inner());
641 let lane_header = self.lane_header(lane);
642 let start = lane_header.write_pos.load(Ordering::Relaxed);
643 let ids = write_slots(start, payloads);
644 lane_header
645 .write_pos
646 .store(start.wrapping_add(ids.len() as u64), Ordering::Release);
647 Ok(ids)
648 }
649 RingTopology::SharedOrdered => {
650 let _write =
651 self.write_locks[lane].lock().unwrap_or_else(|error| error.into_inner());
652 let _cross_process = self.region.lock_exclusive()?;
653 let lane_header = self.lane_header(lane);
654 let start = lane_header.write_pos.load(Ordering::Relaxed);
655 let ids = write_slots(start, payloads);
656 lane_header
657 .write_pos
658 .store(start.wrapping_add(ids.len() as u64), Ordering::Release);
659 Ok(ids)
660 }
661 }
662 }
663
664 pub fn read(
668 &self,
669 id: NetId64
670 ) -> Option<Frame> {
671 if id.kind() != self.kind {
672 return None;
673 }
674 let lane = self.lane_index_for_frame(id)?;
675 let counter = id.counter();
676 let slot_idx = (counter as usize) & (self.capacity - 1);
677 let slot_ptr = self.slot_ptr(lane, slot_idx);
678
679 for _ in 0..3 {
681 let Some(frame) = (unsafe { read_committed_frame(slot_ptr, self.payload_capacity) })
682 else {
683 continue;
684 };
685 if frame.id.counter() == counter {
686 return Some(frame);
687 } else {
688 return None;
690 }
691 }
692 None
693 }
694
695 pub fn read_head(&self) -> Option<Frame> {
698 let head = self.head();
699 if head == 0 {
700 return None;
701 }
702 let counter = head - 1;
703 let slot_idx = (counter as usize) & (self.capacity - 1);
704 let slot_ptr = self.slot_ptr(0, slot_idx);
705
706 for _ in 0..3 {
707 if let Some(frame) = unsafe { read_committed_frame(slot_ptr, self.payload_capacity) } {
708 return Some(frame);
709 }
710 }
711 None
712 }
713
714 pub fn read_at(
719 &self,
720 counter: u64
721 ) -> Option<Frame> {
722 self.read_lane_index_at(0, counter)
723 }
724
725 pub fn read_lane_at(
727 &self,
728 node_id: NodeId,
729 counter: u64
730 ) -> Option<Frame> {
731 let lane = self.lane_index(node_id);
732 self.read_lane_index_at(lane, counter)
733 }
734
735 fn read_lane_index_at(
736 &self,
737 lane: usize,
738 counter: u64
739 ) -> Option<Frame> {
740 let slot_idx = (counter as usize) & (self.capacity - 1);
741 let slot_ptr = self.slot_ptr(lane, slot_idx);
742 for _ in 0..3 {
743 if let Some(frame) = unsafe { read_committed_frame(slot_ptr, self.payload_capacity) } {
744 return Some(frame);
745 }
746 }
747 None
748 }
749
750 pub(crate) fn read_state_at(
751 &self,
752 counter: u64
753 ) -> crate::ring::cursor::RingRead {
754 self.read_lane_index_state_at(0, counter)
755 }
756
757 pub(crate) fn read_lane_state_at(
758 &self,
759 node_id: NodeId,
760 counter: u64
761 ) -> crate::ring::cursor::RingRead {
762 let lane = self.lane_index(node_id);
763 self.read_lane_index_state_at(lane, counter)
764 }
765
766 fn read_lane_index_state_at(
767 &self,
768 lane: usize,
769 counter: u64
770 ) -> crate::ring::cursor::RingRead {
771 use crate::ring::cursor::RingRead;
772
773 let slot_idx = (counter as usize) & (self.capacity - 1);
774 let slot_ptr = self.slot_ptr(lane, slot_idx);
775 let expected_committed =
776 counter.checked_mul(2).and_then(|value| value.checked_add(2)).expect("seq overflow");
777
778 for _ in 0..3 {
779 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
780 if sequence < expected_committed {
781 return if self.topology != RingTopology::Shared {
782 RingRead::Unavailable
783 } else {
784 RingRead::Pending
785 };
786 }
787 if sequence > expected_committed {
788 return RingRead::Unavailable;
789 }
790 if let Some(frame) = unsafe { read_committed_frame(slot_ptr, self.payload_capacity) } {
791 return if frame.id.counter() == counter {
792 RingRead::Ready(frame)
793 } else {
794 RingRead::Unavailable
795 };
796 }
797 }
798
799 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
800 if sequence < expected_committed {
801 if self.topology != RingTopology::Shared {
802 RingRead::Unavailable
803 } else {
804 RingRead::Pending
805 }
806 } else {
807 RingRead::Unavailable
808 }
809 }
810
811 pub fn reset(&self) {
817 unsafe {
820 let slots_ptr = self.region.as_ptr().add(self.slots_offset);
821 ptr::write_bytes(slots_ptr, 0, self.lane_count * self.lane_stride);
822 }
823 for lane in 0..self.lane_count {
824 self.lane_header(lane).write_pos.store(0, Ordering::Release);
825 }
826 let header = unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
827 header.version_counter.store(0, Ordering::Release);
828 }
829
830 fn lane_index(
831 &self,
832 node_id: NodeId
833 ) -> usize {
834 let lane = match self.topology {
835 RingTopology::Shared | RingTopology::SharedOrdered => 0,
836 RingTopology::PerNode => usize::from(node_id.get())
837 };
838 assert!(
839 lane < self.lane_count,
840 "node {} is outside SHM ring lane count {}",
841 node_id.get(),
842 self.lane_count
843 );
844 lane
845 }
846
847 fn lane_index_for_frame(
848 &self,
849 id: NetId64
850 ) -> Option<usize> {
851 let lane = match self.topology {
852 RingTopology::Shared | RingTopology::SharedOrdered => 0,
853 RingTopology::PerNode => usize::from(id.node())
854 };
855 (lane < self.lane_count).then_some(lane)
856 }
857
858 fn write_slot(
859 &self,
860 lane: usize,
861 node_id: NodeId,
862 counter: u64,
863 frame_kind: u8,
864 ver: u64,
865 payload: &[u8]
866 ) -> NetId64 {
867 let id = NetId64::make(self.kind, node_id.get(), counter);
868 let slot_idx = (counter as usize) & (self.capacity - 1);
869 let slot_ptr = self.slot_ptr(lane, slot_idx);
870
871 unsafe {
888 let slot = &*slot_ptr;
889 let mid_seq = counter
890 .checked_mul(2)
891 .and_then(|value| value.checked_add(1))
892 .expect("seq overflow");
893 let final_seq = mid_seq.wrapping_add(1);
894
895 slot.seq.store(mid_seq, Ordering::Relaxed);
896 fence(Ordering::Release);
897 slot.id.store(id.raw(), Ordering::Relaxed);
898 slot.ver.store(ver, Ordering::Relaxed);
899 slot.payload_len.store(payload.len() as u32, Ordering::Relaxed);
900 slot.kind.store(frame_kind, Ordering::Relaxed);
901 let bytes = Self::payload_ptr(slot_ptr);
902 for (index, byte) in payload.iter().enumerate() {
903 (*bytes.add(index)).store(*byte, Ordering::Relaxed);
904 }
905 slot.seq.store(final_seq, Ordering::Release);
906 }
907
908 id
909 }
910}
911
912#[derive(Clone, Debug, PartialEq, Eq)]
922pub struct ShmRingMetadata {
923 pub name: String,
925 pub segment_size: usize,
927 pub format_version: u32,
929 pub kind: u8,
931 pub spec: RingSpec,
933 pub lane_count: usize
935}
936
937pub struct ShmRingView {
945 region: ShmRegion,
946 metadata: ShmRingMetadata,
947 slot_stride: usize,
948 slots_offset: usize,
949 lane_stride: usize
950}
951
952impl std::fmt::Debug for ShmRingView {
953 fn fmt(
954 &self,
955 formatter: &mut std::fmt::Formatter<'_>
956 ) -> std::fmt::Result {
957 formatter.debug_struct("ShmRingView").field("metadata", &self.metadata).finish()
958 }
959}
960
961impl ShmRingView {
962 pub fn attach_existing(
964 fleet_name: &str,
965 kind: u8
966 ) -> std::io::Result<Self> {
967 let uid = unsafe { libc::geteuid() };
969 Self::attach_existing_for_uid(fleet_name, kind, uid)
970 }
971
972 pub fn attach_existing_for_uid(
977 fleet_name: &str,
978 kind: u8,
979 uid: u32
980 ) -> std::io::Result<Self> {
981 let name = shm::ring_segment_name_for_uid(fleet_name, kind, uid);
982 let region = ShmRegion::open_existing_read_only(&name, HEADER_SIZE)?;
983
984 let header = unsafe { &*(region.as_ptr() as *const ShmRingHeader) };
987 if header.magic != MAGIC {
988 return Err(std::io::Error::new(
989 std::io::ErrorKind::InvalidData,
990 format!(
991 "SHM segment {} has wrong magic 0x{:08X} (expected 0x{:08X})",
992 name, header.magic, MAGIC
993 )
994 ));
995 }
996 if header.version != VERSION {
997 return Err(std::io::Error::new(
998 std::io::ErrorKind::InvalidData,
999 format!("SHM segment {} version {} != local {}", name, header.version, VERSION)
1000 ));
1001 }
1002 if header.kind != kind {
1003 return Err(std::io::Error::new(
1004 std::io::ErrorKind::InvalidData,
1005 format!("SHM segment {} kind {} != requested {}", name, header.kind, kind)
1006 ));
1007 }
1008
1009 let topology = match header.topology {
1010 value if value == RingTopology::Shared as u8 => RingTopology::Shared,
1011 value if value == RingTopology::PerNode as u8 => RingTopology::PerNode,
1012 value if value == RingTopology::SharedOrdered as u8 => RingTopology::SharedOrdered,
1013 value => {
1014 return Err(std::io::Error::new(
1015 std::io::ErrorKind::InvalidData,
1016 format!("SHM segment {name} has unknown topology {value}")
1017 ));
1018 }
1019 };
1020 let capacity = usize::try_from(header.capacity).map_err(|_| {
1021 std::io::Error::new(
1022 std::io::ErrorKind::InvalidData,
1023 format!(
1024 "SHM segment {name} capacity {} does not fit this platform",
1025 header.capacity
1026 )
1027 )
1028 })?;
1029 let lane_count = usize::from(header.lane_count);
1030 if lane_count == 0 {
1031 return Err(std::io::Error::new(
1032 std::io::ErrorKind::InvalidData,
1033 format!("SHM segment {name} has zero lanes")
1034 ));
1035 }
1036 if topology != RingTopology::PerNode && lane_count != 1 {
1037 return Err(std::io::Error::new(
1038 std::io::ErrorKind::InvalidData,
1039 format!(
1040 "SHM segment {name} topology {topology:?} requires one lane, found {lane_count}"
1041 )
1042 ));
1043 }
1044 let spec =
1045 RingSpec { capacity, payload_capacity: header.payload_capacity as usize, topology };
1046 let fleet_capacity = if topology == RingTopology::PerNode { header.lane_count } else { 1 };
1047 let (slot_stride, slots_offset, expected_size) = checked_layout(spec, fleet_capacity)
1048 .map_err(|error| {
1049 std::io::Error::new(
1050 std::io::ErrorKind::InvalidData,
1051 format!("SHM segment {name} has invalid geometry: {error}")
1052 )
1053 })?;
1054 if header.slot_stride as usize != slot_stride {
1055 return Err(std::io::Error::new(
1056 std::io::ErrorKind::InvalidData,
1057 format!(
1058 "SHM segment {} slot stride {} != geometry {}",
1059 name, header.slot_stride, slot_stride
1060 )
1061 ));
1062 }
1063 if region.len() < expected_size {
1064 return Err(std::io::Error::new(
1065 std::io::ErrorKind::InvalidData,
1066 format!(
1067 "SHM segment {} size {} is smaller than persisted geometry {}",
1068 name,
1069 region.len(),
1070 expected_size
1071 )
1072 ));
1073 }
1074 let lane_stride = capacity
1075 .checked_mul(slot_stride)
1076 .ok_or_else(|| invalid_input(format!("SHM segment {name} lane stride overflow")))?;
1077
1078 Ok(Self {
1079 metadata: ShmRingMetadata {
1080 name,
1081 segment_size: region.len(),
1082 format_version: header.version,
1083 kind,
1084 spec,
1085 lane_count
1086 },
1087 region,
1088 slot_stride,
1089 slots_offset,
1090 lane_stride
1091 })
1092 }
1093
1094 pub fn attach_typed<T: crate::OrbitTyped>(fleet_name: &str) -> std::io::Result<Self> {
1096 let view = Self::attach_existing(fleet_name, T::KIND)?;
1097 if view.metadata.spec != T::RING_SPEC {
1098 return Err(std::io::Error::new(
1099 std::io::ErrorKind::InvalidData,
1100 format!(
1101 "OrbitTyped KIND {} declares {:?}; existing spec is {:?}",
1102 T::KIND,
1103 T::RING_SPEC,
1104 view.metadata.spec
1105 )
1106 ));
1107 }
1108 Ok(view)
1109 }
1110
1111 pub fn metadata(&self) -> &ShmRingMetadata {
1112 &self.metadata
1113 }
1114
1115 pub fn current_version(&self) -> u64 {
1117 self.header().version_counter.load(Ordering::Acquire)
1118 }
1119
1120 pub fn notification_generation(&self) -> u32 {
1122 self.header().notification_generation.load(Ordering::Acquire)
1123 }
1124
1125 pub fn lane(
1127 &self,
1128 lane: usize
1129 ) -> std::io::Result<ShmRingLaneView<'_>> {
1130 if lane >= self.metadata.lane_count {
1131 return Err(std::io::Error::new(
1132 std::io::ErrorKind::InvalidInput,
1133 format!("lane {lane} is outside SHM ring lane count {}", self.metadata.lane_count)
1134 ));
1135 }
1136 Ok(ShmRingLaneView { ring: self, lane })
1137 }
1138
1139 fn header(&self) -> &ShmRingHeader {
1140 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) }
1142 }
1143
1144 fn lane_header(
1145 &self,
1146 lane: usize
1147 ) -> &ShmLaneHeader {
1148 debug_assert!(lane < self.metadata.lane_count);
1149 unsafe {
1151 &*(self.region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
1152 as *const ShmLaneHeader)
1153 }
1154 }
1155
1156 fn slot_ptr(
1157 &self,
1158 lane: usize,
1159 counter: u64
1160 ) -> *mut ShmSlotHeader {
1161 debug_assert!(lane < self.metadata.lane_count);
1162 let slot = (counter as usize) & (self.metadata.spec.capacity - 1);
1163 unsafe {
1166 self.region
1167 .as_ptr()
1168 .add(self.slots_offset + lane * self.lane_stride + slot * self.slot_stride)
1169 .cast::<ShmSlotHeader>()
1170 }
1171 }
1172
1173 fn read_lane_state_at(
1174 &self,
1175 lane: usize,
1176 counter: u64
1177 ) -> crate::ring::cursor::RingRead {
1178 use crate::ring::cursor::RingRead;
1179
1180 let slot_ptr = self.slot_ptr(lane, counter);
1181 let Some(expected_committed) =
1182 counter.checked_mul(2).and_then(|value| value.checked_add(2))
1183 else {
1184 return RingRead::Unavailable;
1185 };
1186
1187 for _ in 0..3 {
1188 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
1190 if sequence < expected_committed {
1191 return if self.metadata.spec.topology == RingTopology::Shared {
1192 RingRead::Pending
1193 } else {
1194 RingRead::Unavailable
1195 };
1196 }
1197 if sequence > expected_committed {
1198 return RingRead::Unavailable;
1199 }
1200 if let Some(frame) =
1202 unsafe { read_committed_frame(slot_ptr, self.metadata.spec.payload_capacity) }
1203 {
1204 let lane_matches = self.metadata.spec.topology != RingTopology::PerNode
1205 || usize::from(frame.id.node()) == lane;
1206 return if frame.id.kind() == self.metadata.kind
1207 && frame.id.counter() == counter
1208 && lane_matches
1209 {
1210 RingRead::Ready(frame)
1211 } else {
1212 RingRead::Unavailable
1213 };
1214 }
1215 }
1216 RingRead::Unavailable
1217 }
1218}
1219
1220#[derive(Clone, Copy, Debug)]
1222pub struct ShmRingLaneView<'a> {
1223 ring: &'a ShmRingView,
1224 lane: usize
1225}
1226
1227impl ShmRingLaneView<'_> {
1228 pub fn index(self) -> usize {
1229 self.lane
1230 }
1231
1232 pub fn head(self) -> u64 {
1234 self.ring.lane_header(self.lane).write_pos.load(Ordering::Acquire)
1235 }
1236
1237 pub fn retained_range(self) -> std::ops::Range<u64> {
1239 let head = self.head();
1240 head.saturating_sub(self.ring.metadata.spec.capacity as u64)..head
1241 }
1242
1243 pub fn read_at(
1245 self,
1246 counter: u64
1247 ) -> Option<Frame> {
1248 match self.ring.read_lane_state_at(self.lane, counter) {
1249 crate::ring::cursor::RingRead::Ready(frame) => Some(frame),
1250 crate::ring::cursor::RingRead::Pending | crate::ring::cursor::RingRead::Unavailable => {
1251 None
1252 }
1253 }
1254 }
1255
1256 pub fn read_head(self) -> Option<Frame> {
1258 let head = self.head();
1259 head.checked_sub(1).and_then(|counter| self.read_at(counter))
1260 }
1261}
1262
1263impl crate::ring::cursor::RingFrameSource for ShmRingLaneView<'_> {
1264 fn kind(&self) -> u8 {
1265 self.ring.metadata.kind
1266 }
1267
1268 fn head(&self) -> u64 {
1269 (*self).head()
1270 }
1271
1272 fn capacity(&self) -> usize {
1273 self.ring.metadata.spec.capacity
1274 }
1275
1276 fn read_at(
1277 &self,
1278 counter: u64
1279 ) -> Option<Frame> {
1280 (*self).read_at(counter)
1281 }
1282
1283 fn read_state_at(
1284 &self,
1285 counter: u64
1286 ) -> crate::ring::cursor::RingRead {
1287 self.ring.read_lane_state_at(self.lane, counter)
1288 }
1289}
1290
1291pub struct ShmRingRegistry {
1299 fleet_name: String,
1300 fleet_capacity: u16,
1301 rings: dashmap::DashMap<u8, std::sync::Arc<ShmRing>>
1302}
1303
1304impl ShmRingRegistry {
1305 pub fn new(
1306 fleet_name: impl Into<String>,
1307 fleet_capacity: u16
1308 ) -> Self {
1309 Self { fleet_name: fleet_name.into(), fleet_capacity, rings: dashmap::DashMap::new() }
1310 }
1311
1312 pub fn get_or_create_for<T: crate::OrbitTyped>(
1316 &self
1317 ) -> std::io::Result<std::sync::Arc<ShmRing>> {
1318 if let Some(entry) = self.rings.get(&T::KIND) {
1319 if entry.spec() != T::RING_SPEC {
1320 return Err(std::io::Error::new(
1321 std::io::ErrorKind::InvalidData,
1322 format!(
1323 "OrbitTyped KIND {} was reused with ring spec {:?}; existing spec is {:?}",
1324 T::KIND,
1325 T::RING_SPEC,
1326 entry.spec()
1327 )
1328 ));
1329 }
1330 return Ok(entry.clone());
1331 }
1332 let ring = std::sync::Arc::new(ShmRing::open_or_create_for_fleet(
1333 &self.fleet_name,
1334 T::KIND,
1335 T::RING_SPEC,
1336 self.fleet_capacity
1337 )?);
1338 let entry = self.rings.entry(T::KIND).or_insert_with(|| ring.clone());
1339 if entry.spec() != T::RING_SPEC {
1340 return Err(std::io::Error::new(
1341 std::io::ErrorKind::InvalidData,
1342 format!(
1343 "OrbitTyped KIND {} raced with ring spec {:?}; installed spec is {:?}",
1344 T::KIND,
1345 T::RING_SPEC,
1346 entry.spec()
1347 )
1348 ));
1349 }
1350 Ok(entry.clone())
1351 }
1352
1353 pub fn lookup(
1356 &self,
1357 kind: u8
1358 ) -> Option<std::sync::Arc<ShmRing>> {
1359 self.rings.get(&kind).map(|e| e.clone())
1360 }
1361}
1362
1363unsafe fn read_committed_frame(
1371 slot_ptr: *mut ShmSlotHeader,
1372 payload_capacity: usize
1373) -> Option<Frame> {
1374 let slot = unsafe { &*slot_ptr };
1375 let seq_pre = slot.seq.load(Ordering::Acquire);
1376 if seq_pre == 0 {
1377 return None;
1379 }
1380 if seq_pre & 1 == 1 {
1381 return None;
1383 }
1384
1385 let id = NetId64::from_raw(slot.id.load(Ordering::Relaxed));
1389 let kind = slot.kind.load(Ordering::Relaxed);
1390 let ver = slot.ver.load(Ordering::Relaxed);
1391 let payload_len = slot.payload_len.load(Ordering::Relaxed) as usize;
1392 if payload_len > payload_capacity {
1393 return None;
1395 }
1396 let payload_src = unsafe { ShmRing::payload_ptr(slot_ptr) };
1401 let mut payload_buf = Vec::with_capacity(payload_len);
1402 for index in 0..payload_len {
1403 payload_buf.push(unsafe { (*payload_src.add(index)).load(Ordering::Relaxed) });
1404 }
1405
1406 fence(Ordering::Acquire);
1414 let seq_post = slot.seq.load(Ordering::Relaxed);
1415 if seq_pre != seq_post {
1416 return None;
1418 }
1419
1420 Some(Frame { id, kind, ver, payload: Bytes::from(payload_buf) })
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425 use std::sync::atomic::{AtomicU64, Ordering};
1426
1427 use nix::sys::wait::{WaitStatus, waitpid};
1428 use nix::unistd::{ForkResult, fork};
1429
1430 use super::*;
1431 use crate::ring::cursor::{RingCursor, poll_ring};
1432
1433 #[test]
1434 fn cursor_retries_a_claimed_slot_after_it_commits() {
1435 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1436
1437 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1438 let fleet_name = format!("p{:x}{test_id:x}", std::process::id());
1439 let ring = ShmRing::open_or_create(&fleet_name, 199, RingSpec::new(4, 16))
1440 .expect("create test ring");
1441 ring.reset();
1442 let slot_ptr = ring.slot_ptr(0, 0);
1443
1444 ring.lane_header(0).write_pos.store(1, Ordering::Release);
1445 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1446
1447 let mut cursor = RingCursor::from_start();
1448 let pending = poll_ring(&ring, &mut cursor);
1449 assert!(pending.is_empty());
1450 assert_eq!(cursor.next_counter(), 0);
1451
1452 let payload = b"ready";
1453 unsafe {
1454 let slot = &*slot_ptr;
1455 slot.id.store(NetId64::make(199, NodeId::ZERO.get(), 0).raw(), Ordering::Relaxed);
1456 slot.ver.store(7, Ordering::Relaxed);
1457 slot.payload_len.store(payload.len() as u32, Ordering::Relaxed);
1458 slot.kind.store(1, Ordering::Relaxed);
1459 let bytes = ShmRing::payload_ptr(slot_ptr);
1460 for (index, byte) in payload.iter().enumerate() {
1461 (*bytes.add(index)).store(*byte, Ordering::Relaxed);
1462 }
1463 }
1464 unsafe { &*slot_ptr }.seq.store(2, Ordering::Release);
1465
1466 let committed = poll_ring(&ring, &mut cursor);
1467 assert_eq!(committed.frames.len(), 1);
1468 assert_eq!(&committed.frames[0].payload[..], payload);
1469 assert_eq!(cursor.next_counter(), 1);
1470
1471 ring.unlink().expect("unlink test ring");
1472 }
1473
1474 #[test]
1475 fn per_node_head_ignores_a_writer_that_dies_before_commit() {
1476 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1477
1478 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1479 let fleet_name = format!("d{:x}{test_id:x}", std::process::id());
1480 let spec = RingSpec::per_node(4, 16);
1481 let abandoned = ShmRing::open_or_create_for_fleet(&fleet_name, 198, spec, 2)
1482 .expect("create per-node test ring");
1483 abandoned.reset();
1484
1485 let slot_ptr = abandoned.slot_ptr(1, 0);
1486 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1487 assert_eq!(abandoned.lane_head(NodeId::new(1)), 0);
1488
1489 let replacement = ShmRing::open_or_create_for_fleet(&fleet_name, 198, spec, 2)
1490 .expect("replacement attaches");
1491 let id = replacement
1492 .write(NodeId::new(1), 1, 9, Bytes::from_static(b"recovered"))
1493 .expect("replacement commits");
1494
1495 assert_eq!(id.counter(), 0);
1496 assert_eq!(replacement.lane_head(NodeId::new(1)), 1);
1497 assert_eq!(&replacement.read(id).expect("frame visible").payload[..], b"recovered");
1498
1499 replacement.unlink().expect("unlink test ring");
1500 }
1501
1502 #[test]
1503 fn per_node_lane_serializes_concurrent_local_publishers() {
1504 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1505
1506 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1507 let fleet_name = format!("c{:x}{test_id:x}", std::process::id());
1508 let ring = std::sync::Arc::new(
1509 ShmRing::open_or_create_for_fleet(&fleet_name, 197, RingSpec::per_node(512, 0), 2)
1510 .expect("create concurrent writer ring")
1511 );
1512 ring.reset();
1513
1514 let mut writers = Vec::new();
1515 for _ in 0..4 {
1516 let ring = ring.clone();
1517 writers.push(std::thread::spawn(move || {
1518 (0..64)
1519 .map(|_| {
1520 ring.write(NodeId::new(1), 1, 0, Bytes::new()).expect("publish").counter()
1521 })
1522 .collect::<Vec<_>>()
1523 }));
1524 }
1525
1526 let mut counters = writers
1527 .into_iter()
1528 .flat_map(|writer| writer.join().expect("writer joins"))
1529 .collect::<Vec<_>>();
1530 counters.sort_unstable();
1531 assert_eq!(counters, (0..256).collect::<Vec<_>>());
1532 assert_eq!(ring.lane_head(NodeId::new(1)), 256);
1533
1534 ring.unlink().expect("unlink test ring");
1535 }
1536
1537 #[test]
1538 fn shared_ordered_recovers_after_a_locked_writer_process_dies() {
1539 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1540
1541 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1542 let fleet_name = format!("o{:x}{test_id:x}", std::process::id());
1543 let spec = RingSpec::shared_ordered(4, 16);
1544 let ring = ShmRing::open_or_create_for_fleet(&fleet_name, 196, spec, 2)
1545 .expect("create shared-ordered test ring");
1546 ring.reset();
1547
1548 match unsafe { fork() }.expect("fork test writer") {
1549 ForkResult::Child => {
1550 let _lock = ring.region.lock_exclusive().expect("child locks ring");
1554 let slot_ptr = ring.slot_ptr(0, 0);
1555 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1556
1557 unsafe { libc::_exit(0) };
1560 }
1561 ForkResult::Parent { child } => {
1562 let status = waitpid(child, None).expect("wait for abandoned writer");
1563 assert!(matches!(status, WaitStatus::Exited(_, 0)));
1564
1565 let replacement = ShmRing::open_or_create_for_fleet(&fleet_name, 196, spec, 2)
1570 .expect("replacement attaches");
1571 let recovered_lock = replacement
1572 .region
1573 .try_lock_exclusive()
1574 .expect("kernel released dead writer lock");
1575 drop(recovered_lock);
1576
1577 let id = replacement
1578 .write(NodeId::new(1), 1, 9, Bytes::from_static(b"recovered"))
1579 .expect("replacement commits counter zero");
1580 assert_eq!(id.counter(), 0);
1581 assert_eq!(replacement.head(), 1);
1582 assert_eq!(
1583 &replacement.read(id).expect("recovered frame").payload[..],
1584 b"recovered"
1585 );
1586
1587 replacement.unlink().expect("unlink shared-ordered test ring");
1588 }
1589 }
1590 }
1591}