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,
97 _reserved: [u8; 20],
100}
101
102#[repr(C, align(64))]
103struct ShmLaneHeader {
104 write_pos: AtomicU64,
105 _reserved: [u8; 56],
106}
107
108#[repr(C)]
110struct ShmSlotHeader {
111 seq: AtomicU64,
115 id: AtomicU64,
117 ver: AtomicU64,
119 payload_len: AtomicU32,
121 kind: AtomicU8,
123 _reserved: [u8; 3],
124}
125
126const SLOT_HEADER_SIZE: usize = std::mem::size_of::<ShmSlotHeader>();
136const HEADER_SIZE: usize = std::mem::size_of::<ShmRingHeader>();
137const LANE_HEADER_SIZE: usize = std::mem::size_of::<ShmLaneHeader>();
138
139const _: () = assert!(HEADER_SIZE == 64);
140const _: () = assert!(LANE_HEADER_SIZE == 64);
141const _: () = assert!(SLOT_HEADER_SIZE == 32);
142
143fn invalid_input(message: impl Into<String>) -> std::io::Error {
144 std::io::Error::new(std::io::ErrorKind::InvalidInput, message.into())
145}
146
147fn lane_count_for(
148 spec: RingSpec,
149 fleet_capacity: u16,
150) -> std::io::Result<usize> {
151 if fleet_capacity == 0 {
152 return Err(invalid_input("ShmRing fleet capacity must be > 0"));
153 }
154 Ok(match spec.topology {
155 RingTopology::Shared | RingTopology::SharedOrdered => 1,
156 RingTopology::PerNode => usize::from(fleet_capacity),
157 })
158}
159
160fn checked_layout(
161 spec: RingSpec,
162 fleet_capacity: u16,
163) -> std::io::Result<(usize, usize, usize)> {
164 if spec.capacity == 0 {
165 return Err(invalid_input("ShmRing capacity must be > 0"));
166 }
167 if !spec.capacity.is_power_of_two() {
168 return Err(invalid_input("ShmRing capacity must be a power of two"));
169 }
170 if spec.payload_capacity > u32::MAX as usize {
171 return Err(invalid_input("ShmRing payload capacity must fit in u32"));
172 }
173
174 let unaligned = SLOT_HEADER_SIZE
175 .checked_add(spec.payload_capacity)
176 .ok_or_else(|| invalid_input("ShmRing slot size overflow"))?;
177 let slot_stride = unaligned
178 .checked_add(SLOT_ALIGNMENT - 1)
179 .map(|value| value & !(SLOT_ALIGNMENT - 1))
180 .ok_or_else(|| invalid_input("ShmRing slot stride overflow"))?;
181 if slot_stride > u32::MAX as usize {
182 return Err(invalid_input("ShmRing slot stride must fit in u32"));
183 }
184 let lane_count = lane_count_for(spec, fleet_capacity)?;
185 let lane_headers_size = lane_count
186 .checked_mul(LANE_HEADER_SIZE)
187 .ok_or_else(|| invalid_input("ShmRing lane header size overflow"))?;
188 let slots_offset = HEADER_SIZE
189 .checked_add(lane_headers_size)
190 .ok_or_else(|| invalid_input("ShmRing slots offset overflow"))?;
191 let slots_per_lane = spec
192 .capacity
193 .checked_mul(slot_stride)
194 .ok_or_else(|| invalid_input("ShmRing lane size overflow"))?;
195 let slots_size = lane_count
196 .checked_mul(slots_per_lane)
197 .ok_or_else(|| invalid_input("ShmRing slots size overflow"))?;
198 let segment_size = slots_offset
199 .checked_add(slots_size)
200 .ok_or_else(|| invalid_input("ShmRing segment size overflow"))?;
201 Ok((slot_stride, slots_offset, segment_size))
202}
203
204pub fn segment_size_for_spec(spec: RingSpec) -> std::io::Result<usize> {
206 segment_size_for_spec_and_fleet(spec, 1)
207}
208
209pub fn segment_size_for_spec_and_fleet(
211 spec: RingSpec,
212 fleet_capacity: u16,
213) -> std::io::Result<usize> {
214 checked_layout(spec, fleet_capacity)
215 .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_with_policy(
245 fleet_name: &str,
246 kind: u8,
247 spec: RingSpec,
248 policy: shm::ShmAccessPolicy,
249 ) -> std::io::Result<Self> {
250 Self::open_or_create_for_fleet_with_policy(
251 fleet_name, kind, spec, 1, policy,
252 )
253 }
254
255 pub fn open_or_create(
259 fleet_name: &str,
260 kind: u8,
261 spec: RingSpec,
262 ) -> std::io::Result<Self> {
263 Self::open_or_create_for_fleet(fleet_name, kind, spec, 1)
264 }
265
266 pub fn open_or_create_for_fleet(
269 fleet_name: &str,
270 kind: u8,
271 spec: RingSpec,
272 fleet_capacity: u16,
273 ) -> std::io::Result<Self> {
274 Self::open_or_create_for_fleet_with_policy(
275 fleet_name,
276 kind,
277 spec,
278 fleet_capacity,
279 shm::ShmAccessPolicy::default(),
280 )
281 }
282
283 pub fn open_or_create_for_fleet_with_policy(
287 fleet_name: &str,
288 kind: u8,
289 spec: RingSpec,
290 fleet_capacity: u16,
291 policy: shm::ShmAccessPolicy,
292 ) -> std::io::Result<Self> {
293 let lane_count = lane_count_for(spec, fleet_capacity)?;
294 let (slot_stride, slots_offset, size) =
295 checked_layout(spec, fleet_capacity)?;
296 let lane_stride = spec
297 .capacity
298 .checked_mul(slot_stride)
299 .ok_or_else(|| invalid_input("ShmRing lane stride overflow"))?;
300 let name = shm::ring_segment_name(fleet_name, kind);
301
302 let (region, _initialization_lock) =
312 ShmRegion::open_or_create_locked_with_policy(&name, size, policy)?;
313
314 if region.created() {
317 unsafe {
320 let header_ptr = region.as_ptr() as *mut ShmRingHeader;
321 ptr::write(
322 header_ptr,
323 ShmRingHeader {
324 version_counter: AtomicU64::new(0),
325 capacity: spec.capacity as u64,
326 magic: MAGIC,
327 version: VERSION,
328 payload_capacity: spec.payload_capacity as u32,
329 slot_stride: slot_stride as u32,
330 kind,
331 topology: spec.topology as u8,
332 lane_count: lane_count as u16,
333 notification_generation: AtomicU32::new(0),
334 notification_waiters: AtomicU32::new(0),
335 _reserved: [0; 20],
336 },
337 );
338
339 for lane in 0..lane_count {
340 let lane_ptr = region
341 .as_ptr()
342 .add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
343 as *mut ShmLaneHeader;
344 ptr::write(
345 lane_ptr,
346 ShmLaneHeader {
347 write_pos: AtomicU64::new(0),
348 _reserved: [0; 56],
349 },
350 );
351 }
352
353 let slots_ptr = region.as_ptr().add(slots_offset);
358 ptr::write_bytes(slots_ptr, 0, lane_count * lane_stride);
359 }
360 } else {
361 let header = unsafe { &*(region.as_ptr() as *const ShmRingHeader) };
364 if header.magic != MAGIC {
365 return Err(std::io::Error::new(
366 std::io::ErrorKind::InvalidData,
367 format!(
368 "SHM segment {} has wrong magic 0x{:08X} (expected 0x{:08X})",
369 name, header.magic, MAGIC
370 ),
371 ));
372 }
373 if header.version != VERSION {
374 return Err(std::io::Error::new(
375 std::io::ErrorKind::InvalidData,
376 format!(
377 "SHM segment {} version {} != local {}",
378 name, header.version, VERSION
379 ),
380 ));
381 }
382 if header.kind != kind {
383 return Err(std::io::Error::new(
384 std::io::ErrorKind::InvalidData,
385 format!(
386 "SHM segment {} kind {} != requested {}",
387 name, header.kind, kind
388 ),
389 ));
390 }
391 if header.topology != spec.topology as u8 {
392 return Err(std::io::Error::new(
393 std::io::ErrorKind::InvalidData,
394 format!(
395 "SHM segment {} topology {} != requested {}",
396 name, header.topology, spec.topology as u8
397 ),
398 ));
399 }
400 if header.lane_count as usize != lane_count {
401 return Err(std::io::Error::new(
402 std::io::ErrorKind::InvalidData,
403 format!(
404 "SHM segment {} lane count {} != requested {}",
405 name, header.lane_count, lane_count
406 ),
407 ));
408 }
409 if header.capacity as usize != spec.capacity {
410 return Err(std::io::Error::new(
411 std::io::ErrorKind::InvalidData,
412 format!(
413 "SHM segment {} capacity {} != requested {}",
414 name, header.capacity, spec.capacity
415 ),
416 ));
417 }
418 if header.payload_capacity as usize != spec.payload_capacity {
419 return Err(std::io::Error::new(
420 std::io::ErrorKind::InvalidData,
421 format!(
422 "SHM segment {} payload capacity {} != requested {}",
423 name, header.payload_capacity, spec.payload_capacity
424 ),
425 ));
426 }
427 if header.slot_stride as usize != slot_stride {
428 return Err(std::io::Error::new(
429 std::io::ErrorKind::InvalidData,
430 format!(
431 "SHM segment {} slot stride {} != requested {}",
432 name, header.slot_stride, slot_stride
433 ),
434 ));
435 }
436 }
437
438 Ok(Self {
439 region,
440 kind,
441 capacity: spec.capacity,
442 payload_capacity: spec.payload_capacity,
443 topology: spec.topology,
444 lane_count,
445 slot_stride,
446 slots_offset,
447 lane_stride,
448 write_locks: (0..lane_count).map(|_| Mutex::new(())).collect(),
449 })
450 }
451
452 pub fn created(&self) -> bool {
455 self.region.created()
456 }
457
458 pub fn unlink(&self) -> std::io::Result<()> {
462 self.region.unlink()
463 }
464
465 pub fn kind(&self) -> u8 {
467 self.kind
468 }
469
470 pub fn capacity(&self) -> usize {
472 self.capacity
473 }
474
475 pub fn lane_count(&self) -> usize {
477 self.lane_count
478 }
479
480 pub fn payload_capacity(&self) -> usize {
482 self.payload_capacity
483 }
484
485 pub fn spec(&self) -> RingSpec {
486 RingSpec {
487 capacity: self.capacity,
488 payload_capacity: self.payload_capacity,
489 topology: self.topology,
490 }
491 }
492
493 pub(crate) fn publication_lock(
494 &self,
495 exclusive: bool,
496 ) -> std::io::Result<shm::ShmRegionLock> {
497 if exclusive {
498 self.region.lock_exclusive()
499 } else {
500 self.region.lock_shared()
501 }
502 }
503
504 fn lane_header(
505 &self,
506 lane: usize,
507 ) -> &ShmLaneHeader {
508 debug_assert!(lane < self.lane_count);
509 unsafe {
510 &*(self.region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
511 as *const ShmLaneHeader)
512 }
513 }
514
515 #[cfg(any(
516 target_os = "linux",
517 target_os = "freebsd",
518 target_os = "macos"
519 ))]
520 pub(crate) fn notification_generation(&self) -> &AtomicU32 {
521 unsafe {
524 &(*(self.region.as_ptr() as *const ShmRingHeader))
525 .notification_generation
526 }
527 }
528
529 #[cfg(any(
530 target_os = "linux",
531 target_os = "freebsd",
532 target_os = "macos"
533 ))]
534 pub(crate) fn notification_waiters(&self) -> &AtomicU32 {
535 unsafe {
537 &(*(self.region.as_ptr() as *const ShmRingHeader))
538 .notification_waiters
539 }
540 }
541
542 fn slot_ptr(
543 &self,
544 lane: usize,
545 idx: usize,
546 ) -> *mut ShmSlotHeader {
547 debug_assert!(lane < self.lane_count);
548 debug_assert!(idx < self.capacity);
549 unsafe {
552 let base = self
553 .region
554 .as_ptr()
555 .add(self.slots_offset + lane * self.lane_stride);
556 base.add(idx * self.slot_stride).cast::<ShmSlotHeader>()
557 }
558 }
559
560 unsafe fn payload_ptr(slot_ptr: *mut ShmSlotHeader) -> *mut AtomicU8 {
571 unsafe {
572 slot_ptr.cast::<u8>().add(SLOT_HEADER_SIZE).cast::<AtomicU8>()
573 }
574 }
575
576 pub fn head(&self) -> u64 {
578 self.lane_header(0).write_pos.load(Ordering::Acquire)
579 }
580
581 pub fn lane_head(
583 &self,
584 node_id: NodeId,
585 ) -> u64 {
586 let lane = self.lane_index(node_id);
587 self.lane_header(lane).write_pos.load(Ordering::Acquire)
588 }
589
590 pub fn next_version(&self) -> u64 {
593 let header =
594 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
595 header
596 .version_counter
597 .fetch_add(1, Ordering::AcqRel)
598 .checked_add(1)
599 .expect("SHM ring semantic version exhausted")
600 }
601
602 pub fn current_version(&self) -> u64 {
604 let header =
605 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
606 header.version_counter.load(Ordering::Acquire)
607 }
608
609 pub fn write(
612 &self,
613 node_id: NodeId,
614 frame_kind: u8,
615 ver: u64,
616 payload: Bytes,
617 ) -> std::io::Result<NetId64> {
618 if payload.len() > self.payload_capacity {
619 return Err(std::io::Error::new(
620 std::io::ErrorKind::InvalidInput,
621 format!(
622 "payload {} > ring payload capacity {}",
623 payload.len(),
624 self.payload_capacity
625 ),
626 ));
627 }
628
629 let lane = self.lane_index(node_id);
630 match self.topology {
631 RingTopology::Shared => {
632 let counter = self
633 .lane_header(lane)
634 .write_pos
635 .fetch_add(1, Ordering::AcqRel);
636 Ok(self.write_slot(
637 lane, node_id, counter, frame_kind, ver, &payload,
638 ))
639 }
640 RingTopology::PerNode => {
641 let _write = self.write_locks[lane]
642 .lock()
643 .unwrap_or_else(|error| error.into_inner());
644 let lane_header = self.lane_header(lane);
645 let counter = lane_header.write_pos.load(Ordering::Relaxed);
646 let id = self.write_slot(
647 lane, node_id, counter, frame_kind, ver, &payload,
648 );
649 lane_header
650 .write_pos
651 .store(counter.wrapping_add(1), Ordering::Release);
652 Ok(id)
653 }
654 RingTopology::SharedOrdered => {
655 let _write = self.write_locks[lane]
656 .lock()
657 .unwrap_or_else(|error| error.into_inner());
658 let _cross_process = self.region.lock_exclusive()?;
659 let lane_header = self.lane_header(lane);
660 let counter = lane_header.write_pos.load(Ordering::Relaxed);
661 let id = self.write_slot(
662 lane, node_id, counter, frame_kind, ver, &payload,
663 );
664 lane_header
665 .write_pos
666 .store(counter.wrapping_add(1), Ordering::Release);
667 Ok(id)
668 }
669 }
670 }
671
672 pub fn write_batch(
677 &self,
678 node_id: NodeId,
679 frame_kind: u8,
680 ver: u64,
681 payloads: Vec<Bytes>,
682 ) -> std::io::Result<Vec<NetId64>> {
683 if payloads.len() > self.capacity {
684 return Err(std::io::Error::new(
685 std::io::ErrorKind::InvalidInput,
686 format!(
687 "batch {} > ring capacity {}",
688 payloads.len(),
689 self.capacity
690 ),
691 ));
692 }
693 if let Some(payload) = payloads
694 .iter()
695 .find(|payload| payload.len() > self.payload_capacity)
696 {
697 return Err(std::io::Error::new(
698 std::io::ErrorKind::InvalidInput,
699 format!(
700 "payload {} > ring payload capacity {}",
701 payload.len(),
702 self.payload_capacity
703 ),
704 ));
705 }
706 if payloads.is_empty() {
707 return Ok(Vec::new());
708 }
709
710 let lane = self.lane_index(node_id);
711 let write_slots = |start: u64, payloads: Vec<Bytes>| {
712 payloads
713 .into_iter()
714 .enumerate()
715 .map(|(offset, payload)| {
716 self.write_slot(
717 lane,
718 node_id,
719 start.wrapping_add(offset as u64),
720 frame_kind,
721 ver,
722 &payload,
723 )
724 })
725 .collect::<Vec<_>>()
726 };
727
728 match self.topology {
729 RingTopology::Shared => {
730 let start = self
731 .lane_header(lane)
732 .write_pos
733 .fetch_add(payloads.len() as u64, Ordering::AcqRel);
734 Ok(write_slots(start, payloads))
735 }
736 RingTopology::PerNode => {
737 let _write = self.write_locks[lane]
738 .lock()
739 .unwrap_or_else(|error| error.into_inner());
740 let lane_header = self.lane_header(lane);
741 let start = lane_header.write_pos.load(Ordering::Relaxed);
742 let ids = write_slots(start, payloads);
743 lane_header.write_pos.store(
744 start.wrapping_add(ids.len() as u64),
745 Ordering::Release,
746 );
747 Ok(ids)
748 }
749 RingTopology::SharedOrdered => {
750 let _write = self.write_locks[lane]
751 .lock()
752 .unwrap_or_else(|error| error.into_inner());
753 let _cross_process = self.region.lock_exclusive()?;
754 let lane_header = self.lane_header(lane);
755 let start = lane_header.write_pos.load(Ordering::Relaxed);
756 let ids = write_slots(start, payloads);
757 lane_header.write_pos.store(
758 start.wrapping_add(ids.len() as u64),
759 Ordering::Release,
760 );
761 Ok(ids)
762 }
763 }
764 }
765
766 pub fn read(
770 &self,
771 id: NetId64,
772 ) -> Option<Frame> {
773 if id.kind() != self.kind {
774 return None;
775 }
776 let lane = self.lane_index_for_frame(id)?;
777 let counter = id.counter();
778 let slot_idx = (counter as usize) & (self.capacity - 1);
779 let slot_ptr = self.slot_ptr(lane, slot_idx);
780
781 for _ in 0..3 {
783 let Some(frame) = (unsafe {
784 read_committed_frame(slot_ptr, self.payload_capacity)
785 }) else {
786 continue;
787 };
788 if frame.id.counter() == counter {
789 return Some(frame);
790 } else {
791 return None;
793 }
794 }
795 None
796 }
797
798 pub fn read_head(&self) -> Option<Frame> {
801 let head = self.head();
802 if head == 0 {
803 return None;
804 }
805 let counter = head - 1;
806 let slot_idx = (counter as usize) & (self.capacity - 1);
807 let slot_ptr = self.slot_ptr(0, slot_idx);
808
809 for _ in 0..3 {
810 if let Some(frame) =
811 unsafe { read_committed_frame(slot_ptr, self.payload_capacity) }
812 {
813 return Some(frame);
814 }
815 }
816 None
817 }
818
819 pub fn read_at(
824 &self,
825 counter: u64,
826 ) -> Option<Frame> {
827 self.read_lane_index_at(0, counter)
828 }
829
830 pub fn read_lane_at(
832 &self,
833 node_id: NodeId,
834 counter: u64,
835 ) -> Option<Frame> {
836 let lane = self.lane_index(node_id);
837 self.read_lane_index_at(lane, counter)
838 }
839
840 fn read_lane_index_at(
841 &self,
842 lane: usize,
843 counter: u64,
844 ) -> Option<Frame> {
845 let slot_idx = (counter as usize) & (self.capacity - 1);
846 let slot_ptr = self.slot_ptr(lane, slot_idx);
847 for _ in 0..3 {
848 if let Some(frame) =
849 unsafe { read_committed_frame(slot_ptr, self.payload_capacity) }
850 {
851 return Some(frame);
852 }
853 }
854 None
855 }
856
857 pub(crate) fn read_state_at(
858 &self,
859 counter: u64,
860 ) -> crate::ring::cursor::RingRead {
861 self.read_lane_index_state_at(0, counter)
862 }
863
864 pub(crate) fn read_lane_state_at(
865 &self,
866 node_id: NodeId,
867 counter: u64,
868 ) -> crate::ring::cursor::RingRead {
869 let lane = self.lane_index(node_id);
870 self.read_lane_index_state_at(lane, counter)
871 }
872
873 fn read_lane_index_state_at(
874 &self,
875 lane: usize,
876 counter: u64,
877 ) -> crate::ring::cursor::RingRead {
878 use crate::ring::cursor::RingRead;
879
880 let slot_idx = (counter as usize) & (self.capacity - 1);
881 let slot_ptr = self.slot_ptr(lane, slot_idx);
882 let expected_committed = counter
883 .checked_mul(2)
884 .and_then(|value| value.checked_add(2))
885 .expect("seq overflow");
886
887 for _ in 0..3 {
888 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
889 if sequence < expected_committed {
890 return if self.topology != RingTopology::Shared {
891 RingRead::Unavailable
892 } else {
893 RingRead::Pending
894 };
895 }
896 if sequence > expected_committed {
897 return RingRead::Unavailable;
898 }
899 if let Some(frame) =
900 unsafe { read_committed_frame(slot_ptr, self.payload_capacity) }
901 {
902 return if frame.id.counter() == counter {
903 RingRead::Ready(frame)
904 } else {
905 RingRead::Unavailable
906 };
907 }
908 }
909
910 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
911 if sequence < expected_committed {
912 if self.topology != RingTopology::Shared {
913 RingRead::Unavailable
914 } else {
915 RingRead::Pending
916 }
917 } else {
918 RingRead::Unavailable
919 }
920 }
921
922 pub fn reset(&self) {
928 unsafe {
931 let slots_ptr = self.region.as_ptr().add(self.slots_offset);
932 ptr::write_bytes(slots_ptr, 0, self.lane_count * self.lane_stride);
933 }
934 for lane in 0..self.lane_count {
935 self.lane_header(lane).write_pos.store(0, Ordering::Release);
936 }
937 let header =
938 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) };
939 header.version_counter.store(0, Ordering::Release);
940 }
941
942 fn lane_index(
943 &self,
944 node_id: NodeId,
945 ) -> usize {
946 let lane = match self.topology {
947 RingTopology::Shared | RingTopology::SharedOrdered => 0,
948 RingTopology::PerNode => usize::from(node_id.get()),
949 };
950 assert!(
951 lane < self.lane_count,
952 "node {} is outside SHM ring lane count {}",
953 node_id.get(),
954 self.lane_count
955 );
956 lane
957 }
958
959 fn lane_index_for_frame(
960 &self,
961 id: NetId64,
962 ) -> Option<usize> {
963 let lane = match self.topology {
964 RingTopology::Shared | RingTopology::SharedOrdered => 0,
965 RingTopology::PerNode => usize::from(id.node()),
966 };
967 (lane < self.lane_count).then_some(lane)
968 }
969
970 fn write_slot(
971 &self,
972 lane: usize,
973 node_id: NodeId,
974 counter: u64,
975 frame_kind: u8,
976 ver: u64,
977 payload: &[u8],
978 ) -> NetId64 {
979 let id = NetId64::make(self.kind, node_id.get(), counter);
980 let slot_idx = (counter as usize) & (self.capacity - 1);
981 let slot_ptr = self.slot_ptr(lane, slot_idx);
982
983 unsafe {
1000 let slot = &*slot_ptr;
1001 let mid_seq = counter
1002 .checked_mul(2)
1003 .and_then(|value| value.checked_add(1))
1004 .expect("seq overflow");
1005 let final_seq = mid_seq.wrapping_add(1);
1006
1007 slot.seq.store(mid_seq, Ordering::Relaxed);
1008 fence(Ordering::Release);
1009 slot.id.store(id.raw(), Ordering::Relaxed);
1010 slot.ver.store(ver, Ordering::Relaxed);
1011 slot.payload_len.store(payload.len() as u32, Ordering::Relaxed);
1012 slot.kind.store(frame_kind, Ordering::Relaxed);
1013 let bytes = Self::payload_ptr(slot_ptr);
1014 for (index, byte) in payload.iter().enumerate() {
1015 (*bytes.add(index)).store(*byte, Ordering::Relaxed);
1016 }
1017 slot.seq.store(final_seq, Ordering::Release);
1018 }
1019
1020 id
1021 }
1022}
1023
1024#[derive(Clone, Debug, PartialEq, Eq)]
1034pub struct ShmRingMetadata {
1035 pub name: String,
1037 pub segment_size: usize,
1039 pub format_version: u32,
1041 pub kind: u8,
1043 pub spec: RingSpec,
1045 pub lane_count: usize,
1047}
1048
1049pub struct ShmRingView {
1057 region: ShmRegion,
1058 metadata: ShmRingMetadata,
1059 slot_stride: usize,
1060 slots_offset: usize,
1061 lane_stride: usize,
1062}
1063
1064impl std::fmt::Debug for ShmRingView {
1065 fn fmt(
1066 &self,
1067 formatter: &mut std::fmt::Formatter<'_>,
1068 ) -> std::fmt::Result {
1069 formatter
1070 .debug_struct("ShmRingView")
1071 .field("metadata", &self.metadata)
1072 .finish()
1073 }
1074}
1075
1076impl ShmRingView {
1077 pub fn attach_existing_with_policy(
1079 fleet_name: &str,
1080 kind: u8,
1081 policy: shm::ShmAccessPolicy,
1082 ) -> std::io::Result<Self> {
1083 Self::attach_existing_for_uid_with_policy(
1084 fleet_name,
1085 kind,
1086 unsafe { libc::geteuid() },
1087 policy,
1088 )
1089 }
1090
1091 pub fn attach_existing(
1093 fleet_name: &str,
1094 kind: u8,
1095 ) -> std::io::Result<Self> {
1096 let uid = unsafe { libc::geteuid() };
1098 Self::attach_existing_for_uid(fleet_name, kind, uid)
1099 }
1100
1101 pub fn attach_existing_for_uid(
1106 fleet_name: &str,
1107 kind: u8,
1108 uid: u32,
1109 ) -> std::io::Result<Self> {
1110 Self::attach_existing_for_uid_with_policy(
1111 fleet_name,
1112 kind,
1113 uid,
1114 shm::ShmAccessPolicy::default(),
1115 )
1116 }
1117
1118 pub fn attach_existing_for_uid_with_policy(
1121 fleet_name: &str,
1122 kind: u8,
1123 uid: u32,
1124 policy: shm::ShmAccessPolicy,
1125 ) -> std::io::Result<Self> {
1126 let name = shm::ring_segment_name_for_uid(fleet_name, kind, uid);
1127 let region = ShmRegion::open_existing_read_only(
1128 &name,
1129 HEADER_SIZE,
1130 uid,
1131 policy,
1132 )?;
1133
1134 let header = unsafe { &*(region.as_ptr() as *const ShmRingHeader) };
1137 if header.magic != MAGIC {
1138 return Err(std::io::Error::new(
1139 std::io::ErrorKind::InvalidData,
1140 format!(
1141 "SHM segment {} has wrong magic 0x{:08X} (expected 0x{:08X})",
1142 name, header.magic, MAGIC
1143 ),
1144 ));
1145 }
1146 if header.version != VERSION {
1147 return Err(std::io::Error::new(
1148 std::io::ErrorKind::InvalidData,
1149 format!(
1150 "SHM segment {} version {} != local {}",
1151 name, header.version, VERSION
1152 ),
1153 ));
1154 }
1155 if header.kind != kind {
1156 return Err(std::io::Error::new(
1157 std::io::ErrorKind::InvalidData,
1158 format!(
1159 "SHM segment {} kind {} != requested {}",
1160 name, header.kind, kind
1161 ),
1162 ));
1163 }
1164
1165 let topology = match header.topology {
1166 value if value == RingTopology::Shared as u8 => {
1167 RingTopology::Shared
1168 }
1169 value if value == RingTopology::PerNode as u8 => {
1170 RingTopology::PerNode
1171 }
1172 value if value == RingTopology::SharedOrdered as u8 => {
1173 RingTopology::SharedOrdered
1174 }
1175 value => {
1176 return Err(std::io::Error::new(
1177 std::io::ErrorKind::InvalidData,
1178 format!("SHM segment {name} has unknown topology {value}"),
1179 ));
1180 }
1181 };
1182 let capacity = usize::try_from(header.capacity).map_err(|_| {
1183 std::io::Error::new(
1184 std::io::ErrorKind::InvalidData,
1185 format!(
1186 "SHM segment {name} capacity {} does not fit this platform",
1187 header.capacity
1188 ),
1189 )
1190 })?;
1191 let lane_count = usize::from(header.lane_count);
1192 if lane_count == 0 {
1193 return Err(std::io::Error::new(
1194 std::io::ErrorKind::InvalidData,
1195 format!("SHM segment {name} has zero lanes"),
1196 ));
1197 }
1198 if topology != RingTopology::PerNode && lane_count != 1 {
1199 return Err(std::io::Error::new(
1200 std::io::ErrorKind::InvalidData,
1201 format!(
1202 "SHM segment {name} topology {topology:?} requires one lane, found {lane_count}"
1203 ),
1204 ));
1205 }
1206 let spec = RingSpec {
1207 capacity,
1208 payload_capacity: header.payload_capacity as usize,
1209 topology,
1210 };
1211 let fleet_capacity = if topology == RingTopology::PerNode {
1212 header.lane_count
1213 } else {
1214 1
1215 };
1216 let (slot_stride, slots_offset, expected_size) =
1217 checked_layout(spec, fleet_capacity).map_err(|error| {
1218 std::io::Error::new(
1219 std::io::ErrorKind::InvalidData,
1220 format!("SHM segment {name} has invalid geometry: {error}"),
1221 )
1222 })?;
1223 if header.slot_stride as usize != slot_stride {
1224 return Err(std::io::Error::new(
1225 std::io::ErrorKind::InvalidData,
1226 format!(
1227 "SHM segment {} slot stride {} != geometry {}",
1228 name, header.slot_stride, slot_stride
1229 ),
1230 ));
1231 }
1232 if region.len() < expected_size {
1233 return Err(std::io::Error::new(
1234 std::io::ErrorKind::InvalidData,
1235 format!(
1236 "SHM segment {} size {} is smaller than persisted geometry {}",
1237 name,
1238 region.len(),
1239 expected_size
1240 ),
1241 ));
1242 }
1243 let lane_stride =
1244 capacity.checked_mul(slot_stride).ok_or_else(|| {
1245 invalid_input(format!(
1246 "SHM segment {name} lane stride overflow"
1247 ))
1248 })?;
1249
1250 Ok(Self {
1251 metadata: ShmRingMetadata {
1252 name,
1253 segment_size: region.len(),
1254 format_version: header.version,
1255 kind,
1256 spec,
1257 lane_count,
1258 },
1259 region,
1260 slot_stride,
1261 slots_offset,
1262 lane_stride,
1263 })
1264 }
1265
1266 pub fn attach_typed<T: crate::OrbitTyped>(
1268 fleet_name: &str
1269 ) -> std::io::Result<Self> {
1270 let view = Self::attach_existing(fleet_name, T::KIND)?;
1271 if view.metadata.spec != T::RING_SPEC {
1272 return Err(std::io::Error::new(
1273 std::io::ErrorKind::InvalidData,
1274 format!(
1275 "OrbitTyped KIND {} declares {:?}; existing spec is {:?}",
1276 T::KIND,
1277 T::RING_SPEC,
1278 view.metadata.spec
1279 ),
1280 ));
1281 }
1282 Ok(view)
1283 }
1284
1285 pub fn metadata(&self) -> &ShmRingMetadata {
1286 &self.metadata
1287 }
1288
1289 pub fn current_version(&self) -> u64 {
1291 self.header().version_counter.load(Ordering::Acquire)
1292 }
1293
1294 pub fn notification_generation(&self) -> u32 {
1296 self.header().notification_generation.load(Ordering::Acquire)
1297 }
1298
1299 pub fn lane(
1301 &self,
1302 lane: usize,
1303 ) -> std::io::Result<ShmRingLaneView<'_>> {
1304 if lane >= self.metadata.lane_count {
1305 return Err(std::io::Error::new(
1306 std::io::ErrorKind::InvalidInput,
1307 format!(
1308 "lane {lane} is outside SHM ring lane count {}",
1309 self.metadata.lane_count
1310 ),
1311 ));
1312 }
1313 Ok(ShmRingLaneView { ring: self, lane })
1314 }
1315
1316 fn header(&self) -> &ShmRingHeader {
1317 unsafe { &*(self.region.as_ptr() as *const ShmRingHeader) }
1319 }
1320
1321 fn lane_header(
1322 &self,
1323 lane: usize,
1324 ) -> &ShmLaneHeader {
1325 debug_assert!(lane < self.metadata.lane_count);
1326 unsafe {
1328 &*(self.region.as_ptr().add(HEADER_SIZE + lane * LANE_HEADER_SIZE)
1329 as *const ShmLaneHeader)
1330 }
1331 }
1332
1333 fn slot_ptr(
1334 &self,
1335 lane: usize,
1336 counter: u64,
1337 ) -> *mut ShmSlotHeader {
1338 debug_assert!(lane < self.metadata.lane_count);
1339 let slot = (counter as usize) & (self.metadata.spec.capacity - 1);
1340 unsafe {
1343 self.region
1344 .as_ptr()
1345 .add(
1346 self.slots_offset
1347 + lane * self.lane_stride
1348 + slot * self.slot_stride,
1349 )
1350 .cast::<ShmSlotHeader>()
1351 }
1352 }
1353
1354 fn read_lane_state_at(
1355 &self,
1356 lane: usize,
1357 counter: u64,
1358 ) -> crate::ring::cursor::RingRead {
1359 use crate::ring::cursor::RingRead;
1360
1361 let slot_ptr = self.slot_ptr(lane, counter);
1362 let Some(expected_committed) =
1363 counter.checked_mul(2).and_then(|value| value.checked_add(2))
1364 else {
1365 return RingRead::Unavailable;
1366 };
1367
1368 for _ in 0..3 {
1369 let sequence = unsafe { &*slot_ptr }.seq.load(Ordering::Acquire);
1371 if sequence < expected_committed {
1372 return if self.metadata.spec.topology == RingTopology::Shared {
1373 RingRead::Pending
1374 } else {
1375 RingRead::Unavailable
1376 };
1377 }
1378 if sequence > expected_committed {
1379 return RingRead::Unavailable;
1380 }
1381 if let Some(frame) = unsafe {
1383 read_committed_frame(
1384 slot_ptr,
1385 self.metadata.spec.payload_capacity,
1386 )
1387 } {
1388 let lane_matches = self.metadata.spec.topology
1389 != RingTopology::PerNode
1390 || usize::from(frame.id.node()) == lane;
1391 return if frame.id.kind() == self.metadata.kind
1392 && frame.id.counter() == counter
1393 && lane_matches
1394 {
1395 RingRead::Ready(frame)
1396 } else {
1397 RingRead::Unavailable
1398 };
1399 }
1400 }
1401 RingRead::Unavailable
1402 }
1403}
1404
1405#[derive(Clone, Copy, Debug)]
1407pub struct ShmRingLaneView<'a> {
1408 ring: &'a ShmRingView,
1409 lane: usize,
1410}
1411
1412impl ShmRingLaneView<'_> {
1413 pub fn index(self) -> usize {
1414 self.lane
1415 }
1416
1417 pub fn head(self) -> u64 {
1419 self.ring.lane_header(self.lane).write_pos.load(Ordering::Acquire)
1420 }
1421
1422 pub fn retained_range(self) -> std::ops::Range<u64> {
1424 let head = self.head();
1425 head.saturating_sub(self.ring.metadata.spec.capacity as u64)..head
1426 }
1427
1428 pub fn read_at(
1430 self,
1431 counter: u64,
1432 ) -> Option<Frame> {
1433 match self.ring.read_lane_state_at(self.lane, counter) {
1434 crate::ring::cursor::RingRead::Ready(frame) => Some(frame),
1435 crate::ring::cursor::RingRead::Pending
1436 | crate::ring::cursor::RingRead::Unavailable => None,
1437 }
1438 }
1439
1440 pub fn read_head(self) -> Option<Frame> {
1442 let head = self.head();
1443 head.checked_sub(1).and_then(|counter| self.read_at(counter))
1444 }
1445}
1446
1447impl crate::ring::cursor::RingFrameSource for ShmRingLaneView<'_> {
1448 fn kind(&self) -> u8 {
1449 self.ring.metadata.kind
1450 }
1451
1452 fn head(&self) -> u64 {
1453 (*self).head()
1454 }
1455
1456 fn capacity(&self) -> usize {
1457 self.ring.metadata.spec.capacity
1458 }
1459
1460 fn read_at(
1461 &self,
1462 counter: u64,
1463 ) -> Option<Frame> {
1464 (*self).read_at(counter)
1465 }
1466
1467 fn read_state_at(
1468 &self,
1469 counter: u64,
1470 ) -> crate::ring::cursor::RingRead {
1471 self.ring.read_lane_state_at(self.lane, counter)
1472 }
1473}
1474
1475pub struct ShmRingRegistry {
1483 fleet_name: String,
1484 fleet_capacity: u16,
1485 policies: std::collections::BTreeMap<u8, shm::ShmAccessPolicy>,
1486 rings: crate::ring::ByKind<std::sync::Arc<ShmRing>>,
1487}
1488
1489impl ShmRingRegistry {
1490 pub fn new(
1491 fleet_name: impl Into<String>,
1492 fleet_capacity: u16,
1493 ) -> Self {
1494 Self::with_policies(fleet_name, fleet_capacity, [])
1495 }
1496
1497 pub fn with_policies(
1500 fleet_name: impl Into<String>,
1501 fleet_capacity: u16,
1502 policies: impl IntoIterator<Item = (u8, shm::ShmAccessPolicy)>,
1503 ) -> Self {
1504 Self {
1505 fleet_name: fleet_name.into(),
1506 fleet_capacity,
1507 policies: policies.into_iter().collect(),
1508 rings: crate::ring::ByKind::new(),
1509 }
1510 }
1511
1512 pub fn get_or_create_for<T: crate::OrbitTyped>(
1516 &self
1517 ) -> std::io::Result<std::sync::Arc<ShmRing>> {
1518 self.ring_for::<T>().cloned()
1519 }
1520
1521 pub(crate) fn ring_for<T: crate::OrbitTyped>(
1525 &self
1526 ) -> std::io::Result<&std::sync::Arc<ShmRing>> {
1527 let ring = match self.rings.get(T::KIND) {
1528 Some(ring) => ring,
1529 None => {
1530 let opened = ShmRing::open_or_create_for_fleet_with_policy(
1531 &self.fleet_name,
1532 T::KIND,
1533 T::RING_SPEC,
1534 self.fleet_capacity,
1535 self.policies.get(&T::KIND).copied().unwrap_or_default(),
1536 )?;
1537 self.rings.get_or_insert(T::KIND, std::sync::Arc::new(opened))
1539 }
1540 };
1541 if ring.spec() != T::RING_SPEC {
1542 return Err(std::io::Error::new(
1543 std::io::ErrorKind::InvalidData,
1544 format!(
1545 "OrbitTyped KIND {} was reused with ring spec {:?}; existing spec is {:?}",
1546 T::KIND,
1547 T::RING_SPEC,
1548 ring.spec()
1549 ),
1550 ));
1551 }
1552 Ok(ring)
1553 }
1554
1555 pub fn lookup(
1558 &self,
1559 kind: u8,
1560 ) -> Option<std::sync::Arc<ShmRing>> {
1561 self.rings.get(kind).cloned()
1562 }
1563}
1564
1565unsafe fn read_committed_frame(
1573 slot_ptr: *mut ShmSlotHeader,
1574 payload_capacity: usize,
1575) -> Option<Frame> {
1576 let slot = unsafe { &*slot_ptr };
1577 let seq_pre = slot.seq.load(Ordering::Acquire);
1578 if seq_pre == 0 {
1579 return None;
1581 }
1582 if seq_pre & 1 == 1 {
1583 return None;
1585 }
1586
1587 let id = NetId64::from_raw(slot.id.load(Ordering::Relaxed));
1591 let kind = slot.kind.load(Ordering::Relaxed);
1592 let ver = slot.ver.load(Ordering::Relaxed);
1593 let payload_len = slot.payload_len.load(Ordering::Relaxed) as usize;
1594 if payload_len > payload_capacity {
1595 return None;
1597 }
1598 let payload_src = unsafe { ShmRing::payload_ptr(slot_ptr) };
1603 let mut payload_buf = Vec::with_capacity(payload_len);
1604 for index in 0..payload_len {
1605 payload_buf
1606 .push(unsafe { (*payload_src.add(index)).load(Ordering::Relaxed) });
1607 }
1608
1609 fence(Ordering::Acquire);
1617 let seq_post = slot.seq.load(Ordering::Relaxed);
1618 if seq_pre != seq_post {
1619 return None;
1621 }
1622
1623 Some(Frame { id, kind, ver, payload: Bytes::from(payload_buf) })
1624}
1625
1626#[cfg(test)]
1627mod tests {
1628 use std::sync::atomic::{AtomicU64, Ordering};
1629
1630 use nix::sys::wait::{WaitStatus, waitpid};
1631 use nix::unistd::{ForkResult, fork};
1632
1633 use super::*;
1634 use crate::ring::cursor::{RingCursor, poll_ring};
1635
1636 #[test]
1637 fn cursor_retries_a_claimed_slot_after_it_commits() {
1638 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1639
1640 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1641 let fleet_name = format!("p{:x}{test_id:x}", std::process::id());
1642 let ring =
1643 ShmRing::open_or_create(&fleet_name, 199, RingSpec::new(4, 16))
1644 .expect("create test ring");
1645 ring.reset();
1646 let slot_ptr = ring.slot_ptr(0, 0);
1647
1648 ring.lane_header(0).write_pos.store(1, Ordering::Release);
1649 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1650
1651 let mut cursor = RingCursor::from_start();
1652 let pending = poll_ring(&ring, &mut cursor);
1653 assert!(pending.is_empty());
1654 assert_eq!(cursor.next_counter(), 0);
1655
1656 let payload = b"ready";
1657 unsafe {
1658 let slot = &*slot_ptr;
1659 slot.id.store(
1660 NetId64::make(199, NodeId::ZERO.get(), 0).raw(),
1661 Ordering::Relaxed,
1662 );
1663 slot.ver.store(7, Ordering::Relaxed);
1664 slot.payload_len.store(payload.len() as u32, Ordering::Relaxed);
1665 slot.kind.store(1, Ordering::Relaxed);
1666 let bytes = ShmRing::payload_ptr(slot_ptr);
1667 for (index, byte) in payload.iter().enumerate() {
1668 (*bytes.add(index)).store(*byte, Ordering::Relaxed);
1669 }
1670 }
1671 unsafe { &*slot_ptr }.seq.store(2, Ordering::Release);
1672
1673 let committed = poll_ring(&ring, &mut cursor);
1674 assert_eq!(committed.frames.len(), 1);
1675 assert_eq!(&committed.frames[0].payload[..], payload);
1676 assert_eq!(cursor.next_counter(), 1);
1677
1678 ring.unlink().expect("unlink test ring");
1679 }
1680
1681 #[test]
1682 fn per_node_head_ignores_a_writer_that_dies_before_commit() {
1683 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1684
1685 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1686 let fleet_name = format!("d{:x}{test_id:x}", std::process::id());
1687 let spec = RingSpec::per_node(4, 16);
1688 let abandoned =
1689 ShmRing::open_or_create_for_fleet(&fleet_name, 198, spec, 2)
1690 .expect("create per-node test ring");
1691 abandoned.reset();
1692
1693 let slot_ptr = abandoned.slot_ptr(1, 0);
1694 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1695 assert_eq!(abandoned.lane_head(NodeId::new(1)), 0);
1696
1697 let replacement =
1698 ShmRing::open_or_create_for_fleet(&fleet_name, 198, spec, 2)
1699 .expect("replacement attaches");
1700 let id = replacement
1701 .write(NodeId::new(1), 1, 9, Bytes::from_static(b"recovered"))
1702 .expect("replacement commits");
1703
1704 assert_eq!(id.counter(), 0);
1705 assert_eq!(replacement.lane_head(NodeId::new(1)), 1);
1706 assert_eq!(
1707 &replacement.read(id).expect("frame visible").payload[..],
1708 b"recovered"
1709 );
1710
1711 replacement.unlink().expect("unlink test ring");
1712 }
1713
1714 #[test]
1715 fn per_node_lane_serializes_concurrent_local_publishers() {
1716 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1717
1718 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1719 let fleet_name = format!("c{:x}{test_id:x}", std::process::id());
1720 let ring = std::sync::Arc::new(
1721 ShmRing::open_or_create_for_fleet(
1722 &fleet_name,
1723 197,
1724 RingSpec::per_node(512, 0),
1725 2,
1726 )
1727 .expect("create concurrent writer ring"),
1728 );
1729 ring.reset();
1730
1731 let mut writers = Vec::new();
1732 for _ in 0..4 {
1733 let ring = ring.clone();
1734 writers.push(std::thread::spawn(move || {
1735 (0..64)
1736 .map(|_| {
1737 ring.write(NodeId::new(1), 1, 0, Bytes::new())
1738 .expect("publish")
1739 .counter()
1740 })
1741 .collect::<Vec<_>>()
1742 }));
1743 }
1744
1745 let mut counters = writers
1746 .into_iter()
1747 .flat_map(|writer| writer.join().expect("writer joins"))
1748 .collect::<Vec<_>>();
1749 counters.sort_unstable();
1750 assert_eq!(counters, (0..256).collect::<Vec<_>>());
1751 assert_eq!(ring.lane_head(NodeId::new(1)), 256);
1752
1753 ring.unlink().expect("unlink test ring");
1754 }
1755
1756 #[test]
1757 fn shared_ordered_recovers_after_a_locked_writer_process_dies() {
1758 static TEST_ID: AtomicU64 = AtomicU64::new(0);
1759
1760 let test_id = TEST_ID.fetch_add(1, Ordering::Relaxed);
1761 let fleet_name = format!("o{:x}{test_id:x}", std::process::id());
1762 let spec = RingSpec::shared_ordered(4, 16);
1763 let ring = ShmRing::open_or_create_for_fleet(&fleet_name, 196, spec, 2)
1764 .expect("create shared-ordered test ring");
1765 ring.reset();
1766 drop(ring.region.lock_exclusive().expect("parent locks ring"));
1769
1770 match unsafe { fork() }.expect("fork test writer") {
1771 ForkResult::Child => {
1772 let _lock =
1776 ring.region.lock_exclusive().expect("child locks ring");
1777 let slot_ptr = ring.slot_ptr(0, 0);
1778 unsafe { &*slot_ptr }.seq.store(1, Ordering::Release);
1779
1780 unsafe { libc::_exit(0) };
1783 }
1784 ForkResult::Parent { child } => {
1785 let status =
1786 waitpid(child, None).expect("wait for abandoned writer");
1787 assert!(matches!(status, WaitStatus::Exited(_, 0)));
1788
1789 let replacement = ShmRing::open_or_create_for_fleet(
1794 &fleet_name,
1795 196,
1796 spec,
1797 2,
1798 )
1799 .expect("replacement attaches");
1800 let recovered_lock = replacement
1801 .region
1802 .try_lock_exclusive()
1803 .expect("kernel released dead writer lock");
1804 drop(recovered_lock);
1805
1806 let id = replacement
1807 .write(
1808 NodeId::new(1),
1809 1,
1810 9,
1811 Bytes::from_static(b"recovered"),
1812 )
1813 .expect("replacement commits counter zero");
1814 assert_eq!(id.counter(), 0);
1815 assert_eq!(replacement.head(), 1);
1816 assert_eq!(
1817 &replacement.read(id).expect("recovered frame").payload[..],
1818 b"recovered"
1819 );
1820
1821 replacement.unlink().expect("unlink shared-ordered test ring");
1822 }
1823 }
1824 }
1825}