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)
216 .map(|(_, _, segment_size)| segment_size)
217}
218
219pub struct ShmRing {
230 region: ShmRegion,
231 kind: u8,
232 capacity: usize,
233 payload_capacity: usize,
234 topology: RingTopology,
235 lane_count: usize,
236 slot_stride: usize,
237 slots_offset: usize,
238 lane_stride: usize,
239 write_locks: Vec<Mutex<()>>,
240}
241
242impl ShmRing {
243 pub fn open_or_create_with_policy(
246 fleet_name: &str,
247 kind: u8,
248 spec: RingSpec,
249 policy: shm::ShmAccessPolicy,
250 ) -> std::io::Result<Self> {
251 Self::open_or_create_for_fleet_with_policy(
252 fleet_name, kind, spec, 1, policy,
253 )
254 }
255
256 pub fn open_or_create(
260 fleet_name: &str,
261 kind: u8,
262 spec: RingSpec,
263 ) -> std::io::Result<Self> {
264 Self::open_or_create_for_fleet(fleet_name, kind, spec, 1)
265 }
266
267 pub fn open_or_create_for_fleet(
270 fleet_name: &str,
271 kind: u8,
272 spec: RingSpec,
273 fleet_capacity: u16,
274 ) -> std::io::Result<Self> {
275 Self::open_or_create_for_fleet_with_policy(
276 fleet_name,
277 kind,
278 spec,
279 fleet_capacity,
280 shm::ShmAccessPolicy::default(),
281 )
282 }
283
284 pub fn open_or_create_for_fleet_with_policy(
288 fleet_name: &str,
289 kind: u8,
290 spec: RingSpec,
291 fleet_capacity: u16,
292 policy: shm::ShmAccessPolicy,
293 ) -> std::io::Result<Self> {
294 let lane_count = lane_count_for(spec, fleet_capacity)?;
295 let (slot_stride, slots_offset, size) =
296 checked_layout(spec, fleet_capacity)?;
297 let lane_stride = spec
298 .capacity
299 .checked_mul(slot_stride)
300 .ok_or_else(|| invalid_input("ShmRing lane stride overflow"))?;
301 let name = shm::ring_segment_name(fleet_name, kind);
302
303 let (region, _initialization_lock) =
313 ShmRegion::open_or_create_locked_with_policy(&name, size, policy)?;
314
315 if region.created() {
318 unsafe {
321 let header_ptr = region.as_ptr() as *mut ShmRingHeader;
322 ptr::write(
323 header_ptr,
324 ShmRingHeader {
325 version_counter: AtomicU64::new(0),
326 capacity: spec.capacity as u64,
327 magic: MAGIC,
328 version: VERSION,
329 payload_capacity: spec.payload_capacity as u32,
330 slot_stride: slot_stride as u32,
331 kind,
332 topology: spec.topology as u8,
333 lane_count: lane_count as u16,
334 notification_generation: AtomicU32::new(0),
335 notification_waiters: AtomicU32::new(0),
336 _reserved: [0; 20],
337 },
338 );
339
340 for lane in 0..lane_count {
341 let lane_ptr = region
342 .as_ptr()
343 .add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
344 as *mut ShmLaneHeader;
345 ptr::write(
346 lane_ptr,
347 ShmLaneHeader {
348 write_pos: AtomicU64::new(0),
349 _reserved: [0; 56],
350 },
351 );
352 }
353
354 let slots_ptr = region.as_ptr().add(slots_offset);
359 ptr::write_bytes(slots_ptr, 0, lane_count * lane_stride);
360 }
361 } else {
362 let header = unsafe { &*(region.as_ptr() as *const ShmRingHeader) };
365 if header.magic != MAGIC {
366 return Err(std::io::Error::new(
367 std::io::ErrorKind::InvalidData,
368 format!(
369 "SHM segment {} has wrong magic 0x{:08X} (expected 0x{:08X})",
370 name, header.magic, MAGIC
371 ),
372 ));
373 }
374 if header.version != VERSION {
375 return Err(std::io::Error::new(
376 std::io::ErrorKind::InvalidData,
377 format!(
378 "SHM segment {} version {} != local {}",
379 name, header.version, VERSION
380 ),
381 ));
382 }
383 if header.kind != kind {
384 return Err(std::io::Error::new(
385 std::io::ErrorKind::InvalidData,
386 format!(
387 "SHM segment {} kind {} != requested {}",
388 name, header.kind, kind
389 ),
390 ));
391 }
392 if header.topology != spec.topology as u8 {
393 return Err(std::io::Error::new(
394 std::io::ErrorKind::InvalidData,
395 format!(
396 "SHM segment {} topology {} != requested {}",
397 name, header.topology, spec.topology as u8
398 ),
399 ));
400 }
401 if header.lane_count as usize != lane_count {
402 return Err(std::io::Error::new(
403 std::io::ErrorKind::InvalidData,
404 format!(
405 "SHM segment {} lane count {} != requested {}",
406 name, header.lane_count, lane_count
407 ),
408 ));
409 }
410 if header.capacity as usize != spec.capacity {
411 return Err(std::io::Error::new(
412 std::io::ErrorKind::InvalidData,
413 format!(
414 "SHM segment {} capacity {} != requested {}",
415 name, header.capacity, spec.capacity
416 ),
417 ));
418 }
419 if header.payload_capacity as usize != spec.payload_capacity {
420 return Err(std::io::Error::new(
421 std::io::ErrorKind::InvalidData,
422 format!(
423 "SHM segment {} payload capacity {} != requested {}",
424 name, header.payload_capacity, spec.payload_capacity
425 ),
426 ));
427 }
428 if header.slot_stride as usize != slot_stride {
429 return Err(std::io::Error::new(
430 std::io::ErrorKind::InvalidData,
431 format!(
432 "SHM segment {} slot stride {} != requested {}",
433 name, header.slot_stride, slot_stride
434 ),
435 ));
436 }
437 }
438
439 Ok(Self {
440 region,
441 kind,
442 capacity: spec.capacity,
443 payload_capacity: spec.payload_capacity,
444 topology: spec.topology,
445 lane_count,
446 slot_stride,
447 slots_offset,
448 lane_stride,
449 write_locks: (0..lane_count).map(|_| Mutex::new(())).collect(),
450 })
451 }
452
453 pub fn created(&self) -> bool {
456 self.region.created()
457 }
458
459 pub fn unlink(&self) -> std::io::Result<()> {
463 self.region.unlink()
464 }
465
466 pub fn kind(&self) -> u8 {
468 self.kind
469 }
470
471 pub fn capacity(&self) -> usize {
473 self.capacity
474 }
475
476 pub fn lane_count(&self) -> usize {
478 self.lane_count
479 }
480
481 pub fn payload_capacity(&self) -> usize {
483 self.payload_capacity
484 }
485
486 pub fn spec(&self) -> RingSpec {
487 RingSpec {
488 capacity: self.capacity,
489 payload_capacity: self.payload_capacity,
490 topology: self.topology,
491 }
492 }
493
494 fn lane_header(
495 &self,
496 lane: usize,
497 ) -> &ShmLaneHeader {
498 debug_assert!(lane < self.lane_count);
499 unsafe {
500 &*(self.region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
501 as *const ShmLaneHeader)
502 }
503 }
504
505 #[cfg(any(
506 target_os = "linux",
507 target_os = "freebsd",
508 target_os = "macos"
509 ))]
510 pub(crate) fn notification_generation(&self) -> &AtomicU32 {
511 unsafe {
514 &(*(self.region.as_ptr() as *const ShmRingHeader))
515 .notification_generation
516 }
517 }
518
519 #[cfg(any(
520 target_os = "linux",
521 target_os = "freebsd",
522 target_os = "macos"
523 ))]
524 pub(crate) fn notification_waiters(&self) -> &AtomicU32 {
525 unsafe {
527 &(*(self.region.as_ptr() as *const ShmRingHeader))
528 .notification_waiters
529 }
530 }
531
532 fn slot_ptr(
533 &self,
534 lane: usize,
535 idx: usize,
536 ) -> *mut ShmSlotHeader {
537 debug_assert!(lane < self.lane_count);
538 debug_assert!(idx < self.capacity);
539 unsafe {
542 let base = self
543 .region
544 .as_ptr()
545 .add(self.slots_offset + lane * self.lane_stride);
546 base.add(idx * self.slot_stride).cast::<ShmSlotHeader>()
547 }
548 }
549
550 unsafe fn payload_ptr(slot_ptr: *mut ShmSlotHeader) -> *mut AtomicU8 {
561 unsafe {
562 slot_ptr.cast::<u8>().add(SLOT_HEADER_SIZE).cast::<AtomicU8>()
563 }
564 }
565
566 pub fn head(&self) -> u64 {
568 self.lane_header(0).write_pos.load(Ordering::Acquire)
569 }
570
571 pub fn lane_head(
573 &self,
574 node_id: NodeId,
575 ) -> u64 {
576 let lane = self.lane_index(node_id);
577 self.lane_header(lane).write_pos.load(Ordering::Acquire)
578 }
579
580 pub fn next_version(&self) -> u64 {
583 let header =
584 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
585 header
586 .version_counter
587 .fetch_add(1, Ordering::AcqRel)
588 .checked_add(1)
589 .expect("SHM ring semantic version exhausted")
590 }
591
592 pub fn current_version(&self) -> u64 {
594 let header =
595 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
596 header.version_counter.load(Ordering::Acquire)
597 }
598
599 pub fn write(
602 &self,
603 node_id: NodeId,
604 frame_kind: u8,
605 ver: u64,
606 payload: Bytes,
607 ) -> std::io::Result<NetId64> {
608 if payload.len() > self.payload_capacity {
609 return Err(std::io::Error::new(
610 std::io::ErrorKind::InvalidInput,
611 format!(
612 "payload {} > ring payload capacity {}",
613 payload.len(),
614 self.payload_capacity
615 ),
616 ));
617 }
618
619 let lane = self.lane_index(node_id);
620 match self.topology {
621 RingTopology::Shared => {
622 let counter = self
623 .lane_header(lane)
624 .write_pos
625 .fetch_add(1, Ordering::AcqRel);
626 Ok(self.write_slot(
627 lane, node_id, counter, frame_kind, ver, &payload,
628 ))
629 }
630 RingTopology::PerNode => {
631 let _write = self.write_locks[lane]
632 .lock()
633 .unwrap_or_else(|error| error.into_inner());
634 let lane_header = self.lane_header(lane);
635 let counter = lane_header.write_pos.load(Ordering::Relaxed);
636 let id = self.write_slot(
637 lane, node_id, counter, frame_kind, ver, &payload,
638 );
639 lane_header
640 .write_pos
641 .store(counter.wrapping_add(1), Ordering::Release);
642 Ok(id)
643 }
644 RingTopology::SharedOrdered => {
645 let _write = self.write_locks[lane]
646 .lock()
647 .unwrap_or_else(|error| error.into_inner());
648 let _cross_process = self.region.lock_exclusive()?;
649 let lane_header = self.lane_header(lane);
650 let counter = lane_header.write_pos.load(Ordering::Relaxed);
651 let id = self.write_slot(
652 lane, node_id, counter, frame_kind, ver, &payload,
653 );
654 lane_header
655 .write_pos
656 .store(counter.wrapping_add(1), Ordering::Release);
657 Ok(id)
658 }
659 }
660 }
661
662 pub fn write_batch(
667 &self,
668 node_id: NodeId,
669 frame_kind: u8,
670 ver: u64,
671 payloads: Vec<Bytes>,
672 ) -> std::io::Result<Vec<NetId64>> {
673 if payloads.len() > self.capacity {
674 return Err(std::io::Error::new(
675 std::io::ErrorKind::InvalidInput,
676 format!(
677 "batch {} > ring capacity {}",
678 payloads.len(),
679 self.capacity
680 ),
681 ));
682 }
683 if let Some(payload) = payloads
684 .iter()
685 .find(|payload| payload.len() > self.payload_capacity)
686 {
687 return Err(std::io::Error::new(
688 std::io::ErrorKind::InvalidInput,
689 format!(
690 "payload {} > ring payload capacity {}",
691 payload.len(),
692 self.payload_capacity
693 ),
694 ));
695 }
696 if payloads.is_empty() {
697 return Ok(Vec::new());
698 }
699
700 let lane = self.lane_index(node_id);
701 let write_slots = |start: u64, payloads: Vec<Bytes>| {
702 payloads
703 .into_iter()
704 .enumerate()
705 .map(|(offset, payload)| {
706 self.write_slot(
707 lane,
708 node_id,
709 start.wrapping_add(offset as u64),
710 frame_kind,
711 ver,
712 &payload,
713 )
714 })
715 .collect::<Vec<_>>()
716 };
717
718 match self.topology {
719 RingTopology::Shared => {
720 let start = self
721 .lane_header(lane)
722 .write_pos
723 .fetch_add(payloads.len() as u64, Ordering::AcqRel);
724 Ok(write_slots(start, payloads))
725 }
726 RingTopology::PerNode => {
727 let _write = self.write_locks[lane]
728 .lock()
729 .unwrap_or_else(|error| error.into_inner());
730 let lane_header = self.lane_header(lane);
731 let start = lane_header.write_pos.load(Ordering::Relaxed);
732 let ids = write_slots(start, payloads);
733 lane_header.write_pos.store(
734 start.wrapping_add(ids.len() as u64),
735 Ordering::Release,
736 );
737 Ok(ids)
738 }
739 RingTopology::SharedOrdered => {
740 let _write = self.write_locks[lane]
741 .lock()
742 .unwrap_or_else(|error| error.into_inner());
743 let _cross_process = self.region.lock_exclusive()?;
744 let lane_header = self.lane_header(lane);
745 let start = lane_header.write_pos.load(Ordering::Relaxed);
746 let ids = write_slots(start, payloads);
747 lane_header.write_pos.store(
748 start.wrapping_add(ids.len() as u64),
749 Ordering::Release,
750 );
751 Ok(ids)
752 }
753 }
754 }
755
756 pub fn read(
760 &self,
761 id: NetId64,
762 ) -> Option<Frame> {
763 if id.kind() != self.kind {
764 return None;
765 }
766 let lane = self.lane_index_for_frame(id)?;
767 let counter = id.counter();
768 let slot_idx = (counter as usize) & (self.capacity - 1);
769 let slot_ptr = self.slot_ptr(lane, slot_idx);
770
771 for _ in 0..3 {
773 let Some(frame) = (unsafe {
774 read_committed_frame(slot_ptr, self.payload_capacity)
775 }) else {
776 continue;
777 };
778 if frame.id.counter() == counter {
779 return Some(frame);
780 } else {
781 return None;
783 }
784 }
785 None
786 }
787
788 pub fn read_head(&self) -> Option<Frame> {
791 let head = self.head();
792 if head == 0 {
793 return None;
794 }
795 let counter = head - 1;
796 let slot_idx = (counter as usize) & (self.capacity - 1);
797 let slot_ptr = self.slot_ptr(0, slot_idx);
798
799 for _ in 0..3 {
800 if let Some(frame) =
801 unsafe { read_committed_frame(slot_ptr, self.payload_capacity) }
802 {
803 return Some(frame);
804 }
805 }
806 None
807 }
808
809 pub fn read_at(
814 &self,
815 counter: u64,
816 ) -> Option<Frame> {
817 self.read_lane_index_at(0, counter)
818 }
819
820 pub fn read_lane_at(
822 &self,
823 node_id: NodeId,
824 counter: u64,
825 ) -> Option<Frame> {
826 let lane = self.lane_index(node_id);
827 self.read_lane_index_at(lane, counter)
828 }
829
830 fn read_lane_index_at(
831 &self,
832 lane: usize,
833 counter: u64,
834 ) -> Option<Frame> {
835 let slot_idx = (counter as usize) & (self.capacity - 1);
836 let slot_ptr = self.slot_ptr(lane, slot_idx);
837 for _ in 0..3 {
838 if let Some(frame) =
839 unsafe { read_committed_frame(slot_ptr, self.payload_capacity) }
840 {
841 return Some(frame);
842 }
843 }
844 None
845 }
846
847 pub(crate) fn read_state_at(
848 &self,
849 counter: u64,
850 ) -> crate::ring::cursor::RingRead {
851 self.read_lane_index_state_at(0, counter)
852 }
853
854 pub(crate) fn read_lane_state_at(
855 &self,
856 node_id: NodeId,
857 counter: u64,
858 ) -> crate::ring::cursor::RingRead {
859 let lane = self.lane_index(node_id);
860 self.read_lane_index_state_at(lane, counter)
861 }
862
863 fn read_lane_index_state_at(
864 &self,
865 lane: usize,
866 counter: u64,
867 ) -> crate::ring::cursor::RingRead {
868 use crate::ring::cursor::RingRead;
869
870 let slot_idx = (counter as usize) & (self.capacity - 1);
871 let slot_ptr = self.slot_ptr(lane, slot_idx);
872 let expected_committed = counter
873 .checked_mul(2)
874 .and_then(|value| value.checked_add(2))
875 .expect("seq overflow");
876
877 for _ in 0..3 {
878 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
879 if sequence < expected_committed {
880 return if self.topology != RingTopology::Shared {
881 RingRead::Unavailable
882 } else {
883 RingRead::Pending
884 };
885 }
886 if sequence > expected_committed {
887 return RingRead::Unavailable;
888 }
889 if let Some(frame) =
890 unsafe { read_committed_frame(slot_ptr, self.payload_capacity) }
891 {
892 return if frame.id.counter() == counter {
893 RingRead::Ready(frame)
894 } else {
895 RingRead::Unavailable
896 };
897 }
898 }
899
900 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
901 if sequence < expected_committed {
902 if self.topology != RingTopology::Shared {
903 RingRead::Unavailable
904 } else {
905 RingRead::Pending
906 }
907 } else {
908 RingRead::Unavailable
909 }
910 }
911
912 pub fn reset(&self) {
918 unsafe {
921 let slots_ptr = self.region.as_ptr().add(self.slots_offset);
922 ptr::write_bytes(slots_ptr, 0, self.lane_count * self.lane_stride);
923 }
924 for lane in 0..self.lane_count {
925 self.lane_header(lane).write_pos.store(0, Ordering::Release);
926 }
927 let header =
928 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
929 header.version_counter.store(0, Ordering::Release);
930 }
931
932 fn lane_index(
933 &self,
934 node_id: NodeId,
935 ) -> usize {
936 let lane = match self.topology {
937 RingTopology::Shared | RingTopology::SharedOrdered => 0,
938 RingTopology::PerNode => usize::from(node_id.get()),
939 };
940 assert!(
941 lane < self.lane_count,
942 "node {} is outside SHM ring lane count {}",
943 node_id.get(),
944 self.lane_count
945 );
946 lane
947 }
948
949 fn lane_index_for_frame(
950 &self,
951 id: NetId64,
952 ) -> Option<usize> {
953 let lane = match self.topology {
954 RingTopology::Shared | RingTopology::SharedOrdered => 0,
955 RingTopology::PerNode => usize::from(id.node()),
956 };
957 (lane < self.lane_count).then_some(lane)
958 }
959
960 fn write_slot(
961 &self,
962 lane: usize,
963 node_id: NodeId,
964 counter: u64,
965 frame_kind: u8,
966 ver: u64,
967 payload: &[u8],
968 ) -> NetId64 {
969 let id = NetId64::make(self.kind, node_id.get(), counter);
970 let slot_idx = (counter as usize) & (self.capacity - 1);
971 let slot_ptr = self.slot_ptr(lane, slot_idx);
972
973 unsafe {
990 let slot = &*slot_ptr;
991 let mid_seq = counter
992 .checked_mul(2)
993 .and_then(|value| value.checked_add(1))
994 .expect("seq overflow");
995 let final_seq = mid_seq.wrapping_add(1);
996
997 slot.seq.store(mid_seq, Ordering::Relaxed);
998 fence(Ordering::Release);
999 slot.id.store(id.raw(), Ordering::Relaxed);
1000 slot.ver.store(ver, Ordering::Relaxed);
1001 slot.payload_len.store(payload.len() as u32, Ordering::Relaxed);
1002 slot.kind.store(frame_kind, Ordering::Relaxed);
1003 let bytes = Self::payload_ptr(slot_ptr);
1004 for (index, byte) in payload.iter().enumerate() {
1005 (*bytes.add(index)).store(*byte, Ordering::Relaxed);
1006 }
1007 slot.seq.store(final_seq, Ordering::Release);
1008 }
1009
1010 id
1011 }
1012}
1013
1014#[derive(Clone, Debug, PartialEq, Eq)]
1024pub struct ShmRingMetadata {
1025 pub name: String,
1027 pub segment_size: usize,
1029 pub format_version: u32,
1031 pub kind: u8,
1033 pub spec: RingSpec,
1035 pub lane_count: usize,
1037}
1038
1039pub struct ShmRingView {
1047 region: ShmRegion,
1048 metadata: ShmRingMetadata,
1049 slot_stride: usize,
1050 slots_offset: usize,
1051 lane_stride: usize,
1052}
1053
1054impl std::fmt::Debug for ShmRingView {
1055 fn fmt(
1056 &self,
1057 formatter: &mut std::fmt::Formatter<'_>,
1058 ) -> std::fmt::Result {
1059 formatter
1060 .debug_struct("ShmRingView")
1061 .field("metadata", &self.metadata)
1062 .finish()
1063 }
1064}
1065
1066impl ShmRingView {
1067 pub fn attach_existing_with_policy(
1069 fleet_name: &str,
1070 kind: u8,
1071 policy: shm::ShmAccessPolicy,
1072 ) -> std::io::Result<Self> {
1073 Self::attach_existing_for_uid_with_policy(
1074 fleet_name,
1075 kind,
1076 unsafe { libc::geteuid() },
1077 policy,
1078 )
1079 }
1080
1081 pub fn attach_existing(
1083 fleet_name: &str,
1084 kind: u8,
1085 ) -> std::io::Result<Self> {
1086 let uid = unsafe { libc::geteuid() };
1088 Self::attach_existing_for_uid(fleet_name, kind, uid)
1089 }
1090
1091 pub fn attach_existing_for_uid(
1096 fleet_name: &str,
1097 kind: u8,
1098 uid: u32,
1099 ) -> std::io::Result<Self> {
1100 Self::attach_existing_for_uid_with_policy(
1101 fleet_name,
1102 kind,
1103 uid,
1104 shm::ShmAccessPolicy::default(),
1105 )
1106 }
1107
1108 pub fn attach_existing_for_uid_with_policy(
1111 fleet_name: &str,
1112 kind: u8,
1113 uid: u32,
1114 policy: shm::ShmAccessPolicy,
1115 ) -> std::io::Result<Self> {
1116 let name = shm::ring_segment_name_for_uid(fleet_name, kind, uid);
1117 let region = ShmRegion::open_existing_read_only(
1118 &name,
1119 HEADER_SIZE,
1120 uid,
1121 policy,
1122 )?;
1123
1124 let header = unsafe { &*(region.as_ptr() as *const ShmRingHeader) };
1127 if header.magic != MAGIC {
1128 return Err(std::io::Error::new(
1129 std::io::ErrorKind::InvalidData,
1130 format!(
1131 "SHM segment {} has wrong magic 0x{:08X} (expected 0x{:08X})",
1132 name, header.magic, MAGIC
1133 ),
1134 ));
1135 }
1136 if header.version != VERSION {
1137 return Err(std::io::Error::new(
1138 std::io::ErrorKind::InvalidData,
1139 format!(
1140 "SHM segment {} version {} != local {}",
1141 name, header.version, VERSION
1142 ),
1143 ));
1144 }
1145 if header.kind != kind {
1146 return Err(std::io::Error::new(
1147 std::io::ErrorKind::InvalidData,
1148 format!(
1149 "SHM segment {} kind {} != requested {}",
1150 name, header.kind, kind
1151 ),
1152 ));
1153 }
1154
1155 let topology = match header.topology {
1156 value if value == RingTopology::Shared as u8 => {
1157 RingTopology::Shared
1158 }
1159 value if value == RingTopology::PerNode as u8 => {
1160 RingTopology::PerNode
1161 }
1162 value if value == RingTopology::SharedOrdered as u8 => {
1163 RingTopology::SharedOrdered
1164 }
1165 value => {
1166 return Err(std::io::Error::new(
1167 std::io::ErrorKind::InvalidData,
1168 format!("SHM segment {name} has unknown topology {value}"),
1169 ));
1170 }
1171 };
1172 let capacity = usize::try_from(header.capacity).map_err(|_| {
1173 std::io::Error::new(
1174 std::io::ErrorKind::InvalidData,
1175 format!(
1176 "SHM segment {name} capacity {} does not fit this platform",
1177 header.capacity
1178 ),
1179 )
1180 })?;
1181 let lane_count = usize::from(header.lane_count);
1182 if lane_count == 0 {
1183 return Err(std::io::Error::new(
1184 std::io::ErrorKind::InvalidData,
1185 format!("SHM segment {name} has zero lanes"),
1186 ));
1187 }
1188 if topology != RingTopology::PerNode && lane_count != 1 {
1189 return Err(std::io::Error::new(
1190 std::io::ErrorKind::InvalidData,
1191 format!(
1192 "SHM segment {name} topology {topology:?} requires one lane, found {lane_count}"
1193 ),
1194 ));
1195 }
1196 let spec = RingSpec {
1197 capacity,
1198 payload_capacity: header.payload_capacity as usize,
1199 topology,
1200 };
1201 let fleet_capacity = if topology == RingTopology::PerNode {
1202 header.lane_count
1203 } else {
1204 1
1205 };
1206 let (slot_stride, slots_offset, expected_size) =
1207 checked_layout(spec, fleet_capacity).map_err(|error| {
1208 std::io::Error::new(
1209 std::io::ErrorKind::InvalidData,
1210 format!("SHM segment {name} has invalid geometry: {error}"),
1211 )
1212 })?;
1213 if header.slot_stride as usize != slot_stride {
1214 return Err(std::io::Error::new(
1215 std::io::ErrorKind::InvalidData,
1216 format!(
1217 "SHM segment {} slot stride {} != geometry {}",
1218 name, header.slot_stride, slot_stride
1219 ),
1220 ));
1221 }
1222 if region.len() < expected_size {
1223 return Err(std::io::Error::new(
1224 std::io::ErrorKind::InvalidData,
1225 format!(
1226 "SHM segment {} size {} is smaller than persisted geometry {}",
1227 name,
1228 region.len(),
1229 expected_size
1230 ),
1231 ));
1232 }
1233 let lane_stride =
1234 capacity.checked_mul(slot_stride).ok_or_else(|| {
1235 invalid_input(format!(
1236 "SHM segment {name} lane stride overflow"
1237 ))
1238 })?;
1239
1240 Ok(Self {
1241 metadata: ShmRingMetadata {
1242 name,
1243 segment_size: region.len(),
1244 format_version: header.version,
1245 kind,
1246 spec,
1247 lane_count,
1248 },
1249 region,
1250 slot_stride,
1251 slots_offset,
1252 lane_stride,
1253 })
1254 }
1255
1256 pub fn attach_typed<T: crate::OrbitTyped>(
1258 fleet_name: &str
1259 ) -> std::io::Result<Self> {
1260 let view = Self::attach_existing(fleet_name, T::KIND)?;
1261 if view.metadata.spec != T::RING_SPEC {
1262 return Err(std::io::Error::new(
1263 std::io::ErrorKind::InvalidData,
1264 format!(
1265 "OrbitTyped KIND {} declares {:?}; existing spec is {:?}",
1266 T::KIND,
1267 T::RING_SPEC,
1268 view.metadata.spec
1269 ),
1270 ));
1271 }
1272 Ok(view)
1273 }
1274
1275 pub fn metadata(&self) -> &ShmRingMetadata {
1276 &self.metadata
1277 }
1278
1279 pub fn current_version(&self) -> u64 {
1281 self.header().version_counter.load(Ordering::Acquire)
1282 }
1283
1284 pub fn notification_generation(&self) -> u32 {
1286 self.header().notification_generation.load(Ordering::Acquire)
1287 }
1288
1289 pub fn lane(
1291 &self,
1292 lane: usize,
1293 ) -> std::io::Result<ShmRingLaneView<'_>> {
1294 if lane >= self.metadata.lane_count {
1295 return Err(std::io::Error::new(
1296 std::io::ErrorKind::InvalidInput,
1297 format!(
1298 "lane {lane} is outside SHM ring lane count {}",
1299 self.metadata.lane_count
1300 ),
1301 ));
1302 }
1303 Ok(ShmRingLaneView { ring: self, lane })
1304 }
1305
1306 fn header(&self) -> &ShmRingHeader {
1307 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) }
1309 }
1310
1311 fn lane_header(
1312 &self,
1313 lane: usize,
1314 ) -> &ShmLaneHeader {
1315 debug_assert!(lane < self.metadata.lane_count);
1316 unsafe {
1318 &*(self.region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
1319 as *const ShmLaneHeader)
1320 }
1321 }
1322
1323 fn slot_ptr(
1324 &self,
1325 lane: usize,
1326 counter: u64,
1327 ) -> *mut ShmSlotHeader {
1328 debug_assert!(lane < self.metadata.lane_count);
1329 let slot = (counter as usize) & (self.metadata.spec.capacity - 1);
1330 unsafe {
1333 self.region
1334 .as_ptr()
1335 .add(
1336 self.slots_offset
1337 + lane * self.lane_stride
1338 + slot * self.slot_stride,
1339 )
1340 .cast::<ShmSlotHeader>()
1341 }
1342 }
1343
1344 fn read_lane_state_at(
1345 &self,
1346 lane: usize,
1347 counter: u64,
1348 ) -> crate::ring::cursor::RingRead {
1349 use crate::ring::cursor::RingRead;
1350
1351 let slot_ptr = self.slot_ptr(lane, counter);
1352 let Some(expected_committed) =
1353 counter.checked_mul(2).and_then(|value| value.checked_add(2))
1354 else {
1355 return RingRead::Unavailable;
1356 };
1357
1358 for _ in 0..3 {
1359 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
1361 if sequence < expected_committed {
1362 return if self.metadata.spec.topology == RingTopology::Shared {
1363 RingRead::Pending
1364 } else {
1365 RingRead::Unavailable
1366 };
1367 }
1368 if sequence > expected_committed {
1369 return RingRead::Unavailable;
1370 }
1371 if let Some(frame) = unsafe {
1373 read_committed_frame(
1374 slot_ptr,
1375 self.metadata.spec.payload_capacity,
1376 )
1377 } {
1378 let lane_matches = self.metadata.spec.topology
1379 != RingTopology::PerNode
1380 || usize::from(frame.id.node()) == lane;
1381 return if frame.id.kind() == self.metadata.kind
1382 && frame.id.counter() == counter
1383 && lane_matches
1384 {
1385 RingRead::Ready(frame)
1386 } else {
1387 RingRead::Unavailable
1388 };
1389 }
1390 }
1391 RingRead::Unavailable
1392 }
1393}
1394
1395#[derive(Clone, Copy, Debug)]
1397pub struct ShmRingLaneView<'a> {
1398 ring: &'a ShmRingView,
1399 lane: usize,
1400}
1401
1402impl ShmRingLaneView<'_> {
1403 pub fn index(self) -> usize {
1404 self.lane
1405 }
1406
1407 pub fn head(self) -> u64 {
1409 self.ring.lane_header(self.lane).write_pos.load(Ordering::Acquire)
1410 }
1411
1412 pub fn retained_range(self) -> std::ops::Range<u64> {
1414 let head = self.head();
1415 head.saturating_sub(self.ring.metadata.spec.capacity as u64)..head
1416 }
1417
1418 pub fn read_at(
1420 self,
1421 counter: u64,
1422 ) -> Option<Frame> {
1423 match self.ring.read_lane_state_at(self.lane, counter) {
1424 crate::ring::cursor::RingRead::Ready(frame) => Some(frame),
1425 crate::ring::cursor::RingRead::Pending
1426 | crate::ring::cursor::RingRead::Unavailable => None,
1427 }
1428 }
1429
1430 pub fn read_head(self) -> Option<Frame> {
1432 let head = self.head();
1433 head.checked_sub(1).and_then(|counter| self.read_at(counter))
1434 }
1435}
1436
1437impl crate::ring::cursor::RingFrameSource for ShmRingLaneView<'_> {
1438 fn kind(&self) -> u8 {
1439 self.ring.metadata.kind
1440 }
1441
1442 fn head(&self) -> u64 {
1443 (*self).head()
1444 }
1445
1446 fn capacity(&self) -> usize {
1447 self.ring.metadata.spec.capacity
1448 }
1449
1450 fn read_at(
1451 &self,
1452 counter: u64,
1453 ) -> Option<Frame> {
1454 (*self).read_at(counter)
1455 }
1456
1457 fn read_state_at(
1458 &self,
1459 counter: u64,
1460 ) -> crate::ring::cursor::RingRead {
1461 self.ring.read_lane_state_at(self.lane, counter)
1462 }
1463}
1464
1465pub struct ShmRingRegistry {
1473 fleet_name: String,
1474 fleet_capacity: u16,
1475 policies: std::collections::BTreeMap<u8, shm::ShmAccessPolicy>,
1476 rings: dashmap::DashMap<u8, std::sync::Arc<ShmRing>>,
1477}
1478
1479impl ShmRingRegistry {
1480 pub fn new(
1481 fleet_name: impl Into<String>,
1482 fleet_capacity: u16,
1483 ) -> Self {
1484 Self::with_policies(fleet_name, fleet_capacity, [])
1485 }
1486
1487 pub fn with_policies(
1490 fleet_name: impl Into<String>,
1491 fleet_capacity: u16,
1492 policies: impl IntoIterator<Item = (u8, shm::ShmAccessPolicy)>,
1493 ) -> Self {
1494 Self {
1495 fleet_name: fleet_name.into(),
1496 fleet_capacity,
1497 policies: policies.into_iter().collect(),
1498 rings: dashmap::DashMap::new(),
1499 }
1500 }
1501
1502 pub fn get_or_create_for<T: crate::OrbitTyped>(
1506 &self
1507 ) -> std::io::Result<std::sync::Arc<ShmRing>> {
1508 if let Some(entry) = self.rings.get(&T::KIND) {
1509 if entry.spec() != T::RING_SPEC {
1510 return Err(std::io::Error::new(
1511 std::io::ErrorKind::InvalidData,
1512 format!(
1513 "OrbitTyped KIND {} was reused with ring spec {:?}; existing spec is {:?}",
1514 T::KIND,
1515 T::RING_SPEC,
1516 entry.spec()
1517 ),
1518 ));
1519 }
1520 return Ok(entry.clone());
1521 }
1522 let ring =
1523 std::sync::Arc::new(ShmRing::open_or_create_for_fleet_with_policy(
1524 &self.fleet_name,
1525 T::KIND,
1526 T::RING_SPEC,
1527 self.fleet_capacity,
1528 self.policies.get(&T::KIND).copied().unwrap_or_default(),
1529 )?);
1530 let entry = self.rings.entry(T::KIND).or_insert_with(|| ring.clone());
1531 if entry.spec() != T::RING_SPEC {
1532 return Err(std::io::Error::new(
1533 std::io::ErrorKind::InvalidData,
1534 format!(
1535 "OrbitTyped KIND {} raced with ring spec {:?}; installed spec is {:?}",
1536 T::KIND,
1537 T::RING_SPEC,
1538 entry.spec()
1539 ),
1540 ));
1541 }
1542 Ok(entry.clone())
1543 }
1544
1545 pub fn lookup(
1548 &self,
1549 kind: u8,
1550 ) -> Option<std::sync::Arc<ShmRing>> {
1551 self.rings.get(&kind).map(|e| e.clone())
1552 }
1553}
1554
1555unsafe fn read_committed_frame(
1563 slot_ptr: *mut ShmSlotHeader,
1564 payload_capacity: usize,
1565) -> Option<Frame> {
1566 let slot = unsafe { &*slot_ptr };
1567 let seq_pre = slot.seq.load(Ordering::Acquire);
1568 if seq_pre == 0 {
1569 return None;
1571 }
1572 if seq_pre & 1 == 1 {
1573 return None;
1575 }
1576
1577 let id = NetId64::from_raw(slot.id.load(Ordering::Relaxed));
1581 let kind = slot.kind.load(Ordering::Relaxed);
1582 let ver = slot.ver.load(Ordering::Relaxed);
1583 let payload_len = slot.payload_len.load(Ordering::Relaxed) as usize;
1584 if payload_len > payload_capacity {
1585 return None;
1587 }
1588 let payload_src = unsafe { ShmRing::payload_ptr(slot_ptr) };
1593 let mut payload_buf = Vec::with_capacity(payload_len);
1594 for index in 0..payload_len {
1595 payload_buf
1596 .push(unsafe { (*payload_src.add(index)).load(Ordering::Relaxed) });
1597 }
1598
1599 fence(Ordering::Acquire);
1607 let seq_post = slot.seq.load(Ordering::Relaxed);
1608 if seq_pre != seq_post {
1609 return None;
1611 }
1612
1613 Some(Frame { id, kind, ver, payload: Bytes::from(payload_buf) })
1614}
1615
1616#[cfg(test)]
1617mod tests {
1618 use std::sync::atomic::{AtomicU64, Ordering};
1619
1620 use nix::sys::wait::{WaitStatus, waitpid};
1621 use nix::unistd::{ForkResult, fork};
1622
1623 use super::*;
1624 use crate::ring::cursor::{RingCursor, poll_ring};
1625
1626 #[test]
1627 fn cursor_retries_a_claimed_slot_after_it_commits() {
1628 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1629
1630 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1631 let fleet_name = format!("p{:x}{test_id:x}", std::process::id());
1632 let ring =
1633 ShmRing::open_or_create(&fleet_name, 199, RingSpec::new(4, 16))
1634 .expect("create test ring");
1635 ring.reset();
1636 let slot_ptr = ring.slot_ptr(0, 0);
1637
1638 ring.lane_header(0).write_pos.store(1, Ordering::Release);
1639 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1640
1641 let mut cursor = RingCursor::from_start();
1642 let pending = poll_ring(&ring, &mut cursor);
1643 assert!(pending.is_empty());
1644 assert_eq!(cursor.next_counter(), 0);
1645
1646 let payload = b"ready";
1647 unsafe {
1648 let slot = &*slot_ptr;
1649 slot.id.store(
1650 NetId64::make(199, NodeId::ZERO.get(), 0).raw(),
1651 Ordering::Relaxed,
1652 );
1653 slot.ver.store(7, Ordering::Relaxed);
1654 slot.payload_len.store(payload.len() as u32, Ordering::Relaxed);
1655 slot.kind.store(1, Ordering::Relaxed);
1656 let bytes = ShmRing::payload_ptr(slot_ptr);
1657 for (index, byte) in payload.iter().enumerate() {
1658 (*bytes.add(index)).store(*byte, Ordering::Relaxed);
1659 }
1660 }
1661 unsafe { &*slot_ptr }.seq.store(2, Ordering::Release);
1662
1663 let committed = poll_ring(&ring, &mut cursor);
1664 assert_eq!(committed.frames.len(), 1);
1665 assert_eq!(&committed.frames[0].payload[..], payload);
1666 assert_eq!(cursor.next_counter(), 1);
1667
1668 ring.unlink().expect("unlink test ring");
1669 }
1670
1671 #[test]
1672 fn per_node_head_ignores_a_writer_that_dies_before_commit() {
1673 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1674
1675 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1676 let fleet_name = format!("d{:x}{test_id:x}", std::process::id());
1677 let spec = RingSpec::per_node(4, 16);
1678 let abandoned =
1679 ShmRing::open_or_create_for_fleet(&fleet_name, 198, spec, 2)
1680 .expect("create per-node test ring");
1681 abandoned.reset();
1682
1683 let slot_ptr = abandoned.slot_ptr(1, 0);
1684 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1685 assert_eq!(abandoned.lane_head(NodeId::new(1)), 0);
1686
1687 let replacement =
1688 ShmRing::open_or_create_for_fleet(&fleet_name, 198, spec, 2)
1689 .expect("replacement attaches");
1690 let id = replacement
1691 .write(NodeId::new(1), 1, 9, Bytes::from_static(b"recovered"))
1692 .expect("replacement commits");
1693
1694 assert_eq!(id.counter(), 0);
1695 assert_eq!(replacement.lane_head(NodeId::new(1)), 1);
1696 assert_eq!(
1697 &replacement.read(id).expect("frame visible").payload[..],
1698 b"recovered"
1699 );
1700
1701 replacement.unlink().expect("unlink test ring");
1702 }
1703
1704 #[test]
1705 fn per_node_lane_serializes_concurrent_local_publishers() {
1706 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1707
1708 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1709 let fleet_name = format!("c{:x}{test_id:x}", std::process::id());
1710 let ring = std::sync::Arc::new(
1711 ShmRing::open_or_create_for_fleet(
1712 &fleet_name,
1713 197,
1714 RingSpec::per_node(512, 0),
1715 2,
1716 )
1717 .expect("create concurrent writer ring"),
1718 );
1719 ring.reset();
1720
1721 let mut writers = Vec::new();
1722 for _ in 0..4 {
1723 let ring = ring.clone();
1724 writers.push(std::thread::spawn(move || {
1725 (0..64)
1726 .map(|_| {
1727 ring.write(NodeId::new(1), 1, 0, Bytes::new())
1728 .expect("publish")
1729 .counter()
1730 })
1731 .collect::<Vec<_>>()
1732 }));
1733 }
1734
1735 let mut counters = writers
1736 .into_iter()
1737 .flat_map(|writer| writer.join().expect("writer joins"))
1738 .collect::<Vec<_>>();
1739 counters.sort_unstable();
1740 assert_eq!(counters, (0..256).collect::<Vec<_>>());
1741 assert_eq!(ring.lane_head(NodeId::new(1)), 256);
1742
1743 ring.unlink().expect("unlink test ring");
1744 }
1745
1746 #[test]
1747 fn shared_ordered_recovers_after_a_locked_writer_process_dies() {
1748 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1749
1750 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1751 let fleet_name = format!("o{:x}{test_id:x}", std::process::id());
1752 let spec = RingSpec::shared_ordered(4, 16);
1753 let ring = ShmRing::open_or_create_for_fleet(&fleet_name, 196, spec, 2)
1754 .expect("create shared-ordered test ring");
1755 ring.reset();
1756
1757 match unsafe { fork() }.expect("fork test writer") {
1758 ForkResult::Child => {
1759 let _lock =
1763 ring.region.lock_exclusive().expect("child locks ring");
1764 let slot_ptr = ring.slot_ptr(0, 0);
1765 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1766
1767 unsafe { libc::_exit(0) };
1770 }
1771 ForkResult::Parent { child } => {
1772 let status =
1773 waitpid(child, None).expect("wait for abandoned writer");
1774 assert!(matches!(status, WaitStatus::Exited(_, 0)));
1775
1776 let replacement = ShmRing::open_or_create_for_fleet(
1781 &fleet_name,
1782 196,
1783 spec,
1784 2,
1785 )
1786 .expect("replacement attaches");
1787 let recovered_lock = replacement
1788 .region
1789 .try_lock_exclusive()
1790 .expect("kernel released dead writer lock");
1791 drop(recovered_lock);
1792
1793 let id = replacement
1794 .write(
1795 NodeId::new(1),
1796 1,
1797 9,
1798 Bytes::from_static(b"recovered"),
1799 )
1800 .expect("replacement commits counter zero");
1801 assert_eq!(id.counter(), 0);
1802 assert_eq!(replacement.head(), 1);
1803 assert_eq!(
1804 &replacement.read(id).expect("recovered frame").payload[..],
1805 b"recovered"
1806 );
1807
1808 replacement.unlink().expect("unlink shared-ordered test ring");
1809 }
1810 }
1811 }
1812}