1use std::cell::Cell;
41use std::marker::PhantomData;
42use std::path::Path;
43use std::sync::{Arc, OnceLock};
44use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, AtomicUsize, Ordering};
45
46use arc_swap::ArcSwap;
47
48use crate::frame_ring::{FrameClass, LayoutHint};
49use crate::frame_region::FrameRegion;
50use crate::peer_directory::{
51 PeerDirectory, CONSUMER_SLOT_CEILING, OWNER_NONE,
52};
53
54use crate::ordering::{
55 default_stamp_kind, ordering_region_size, stamp_now, OrderingMode,
56 OrderingRegion, StampKind, STAMPED_PAYLOAD_BYTES, STAMP_BYTES,
57};
58use crate::qos_policy::Ordering as QosOrdering;
59use crate::shared_ring::{RingError, SharedRing, PAYLOAD_BYTES};
60use crate::spsc_ring::{SpscRingCore, SPSC_PAYLOAD_BYTES};
61
62pub const DRAINER_GRACE_EPOCHS: u64 = 3;
66
67#[repr(u8)]
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum RingShape {
72 Spsc = 0,
74 Mpsc = 1,
76 Mpmc = 2,
78 Vyukov = 3,
80}
81
82impl RingShape {
83 fn from_u8(tag: u8) -> Self {
84 match tag {
85 0 => Self::Spsc,
86 1 => Self::Mpsc,
87 2 => Self::Mpmc,
88 3 => Self::Vyukov,
89 _ => panic!("AdaptiveRing shape_tag corrupted: {tag}"),
90 }
91 }
92}
93
94const STALE_NONE: u8 = u8::MAX;
110
111pub struct AdaptiveRing {
112 shape_tag: AtomicU8,
114
115 stale_shape_tag: AtomicU8,
124
125 pin_generation: AtomicU64,
128
129 frame_region: OnceLock<Arc<FrameRegion>>,
138
139 spsc: Arc<SpscRingCore>,
141
142 mpsc: Arc<MpscBacking>,
145
146 mpmc: Arc<MpmcBacking>,
149
150 vyukov: Arc<SharedRing>,
152
153 max_producers: usize,
158 max_consumers: usize,
159
160 capacity: usize,
163
164 directory: Arc<PeerDirectory>,
168
169 synced_epoch: AtomicU64,
172
173 grow_lock: parking_lot::Mutex<()>,
175
176 shape_auto: AtomicBool,
182
183 contract: Option<crate::ring_contract::RingContract>,
191
192 ordering: Option<Arc<OrderingState>>,
199
200 backing_id: BackingId,
203
204 header_sidecar: subetha_core::HandshakeHeader,
207 ring_sidecar: Box<subetha_core::ObservationRing>,
208}
209
210unsafe impl Send for AdaptiveRing {}
211unsafe impl Sync for AdaptiveRing {}
212
213impl subetha_sidecar::AdaptiveInstance for AdaptiveRing {
214 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
215 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
216 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
217 Box::new(subetha_sidecar::NoMigrationPolicy)
218 }
219}
220
221enum BackingId {
224 Anon,
225 File { prefix: std::path::PathBuf, created: bool },
226 Shm { prefix: String },
227}
228
229struct OrderingState {
232 region: OrderingRegion,
233 seen: Vec<SeenLine>,
237}
238
239#[repr(align(64))]
240struct SeenLine {
241 stamp: AtomicU64,
242 mode_tag: AtomicU32,
243 lease_gen: AtomicU64,
249}
250
251impl SeenLine {
252 fn new() -> Self {
253 Self {
254 stamp: AtomicU64::new(0),
255 mode_tag: AtomicU32::new(OrderingMode::Unordered as u32),
256 lease_gen: AtomicU64::new(u64::MAX),
257 }
258 }
259}
260
261#[inline]
263fn drainer_token(consumer_id: usize) -> u64 {
264 ((std::process::id() as u64) << 32) | (consumer_id as u64 & 0xFFFF_FFFF)
265}
266
267struct MpscBacking {
268 rings: ArcSwap<Vec<Arc<SpscRingCore>>>,
273 next_drain: AtomicUsize,
274}
275
276struct MpmcBacking {
277 rings: ArcSwap<Vec<Arc<SpscRingCore>>>,
278 consumer_cursors: Vec<PaddedCursor>,
284}
285
286#[repr(align(64))]
291struct PaddedCursor(AtomicUsize, AtomicUsize);
292
293fn consumer_cursor_table() -> Vec<PaddedCursor> {
294 (0..CONSUMER_SLOT_CEILING)
295 .map(|_| PaddedCursor(AtomicUsize::new(0), AtomicUsize::new(0)))
296 .collect()
297}
298
299#[cfg(target_os = "linux")]
308fn hugepage_region(bytes: usize) -> std::io::Result<crate::hugepages::HugepageRegion> {
309 use crate::hugepages::{HugepageRegion, HugepageSize, HUGEPAGE_2MB};
310 let pages = bytes.div_ceil(HUGEPAGE_2MB).max(1);
311 HugepageRegion::allocate(pages, HugepageSize::Mb2)
312}
313
314#[cfg(windows)]
315fn hugepage_region(bytes: usize) -> std::io::Result<crate::large_pages::LargePageRegion> {
316 use crate::large_pages::{enable_lock_memory_privilege, LargePageRegion};
317 enable_lock_memory_privilege()?;
320 LargePageRegion::allocate(bytes)
321}
322
323#[cfg(any(target_os = "freebsd", target_os = "macos"))]
324fn hugepage_region(bytes: usize) -> std::io::Result<crate::super_pages::SuperPageRegion> {
325 crate::super_pages::SuperPageRegion::allocate(bytes)
332}
333
334impl AdaptiveRing {
335 pub fn create_anon(
341 max_producers: usize,
342 max_consumers: usize,
343 capacity: usize,
344 ) -> Result<Self, RingError> {
345 assert!(max_producers >= 1, "max_producers must be >= 1");
346 assert!(max_consumers >= 1, "max_consumers must be >= 1");
347
348 let spsc = Arc::new(SpscRingCore::create_anon(capacity)?);
349
350 let mpsc_rings: Vec<Arc<SpscRingCore>> = (0..max_producers)
351 .map(|_| SpscRingCore::create_anon(capacity).map(Arc::new))
352 .collect::<Result<Vec<_>, _>>()?;
353 let mpsc = Arc::new(MpscBacking {
354 rings: ArcSwap::from_pointee(mpsc_rings),
355 next_drain: AtomicUsize::new(0),
356 });
357
358 let mpmc_rings: Vec<Arc<SpscRingCore>> = (0..max_producers)
359 .map(|_| SpscRingCore::create_anon(capacity).map(Arc::new))
360 .collect::<Result<Vec<_>, _>>()?;
361 let mpmc = Arc::new(MpmcBacking {
362 rings: ArcSwap::from_pointee(mpmc_rings),
363 consumer_cursors: consumer_cursor_table(),
364 });
365
366 let vyukov = Arc::new(SharedRing::create_anon(capacity)?);
367
368 let directory = Arc::new(PeerDirectory::create_anon()?);
369 directory.publish_rings(max_producers);
370
371 Ok(Self {
372 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
373 stale_shape_tag: AtomicU8::new(STALE_NONE),
374 pin_generation: AtomicU64::new(0),
375 frame_region: OnceLock::new(),
376 spsc,
377 mpsc,
378 mpmc,
379 vyukov,
380 max_producers,
381 max_consumers,
382 capacity,
383 directory,
384 synced_epoch: AtomicU64::new(u64::MAX),
385 grow_lock: parking_lot::Mutex::new(()),
386 contract: None,
387 shape_auto: AtomicBool::new(true),
388 ordering: None,
389 backing_id: BackingId::Anon,
390 header_sidecar: subetha_core::HandshakeHeader::new(),
391 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
392 })
393 }
394
395 #[cfg(any(target_os = "linux", windows, target_os = "freebsd", target_os = "macos"))]
412 pub fn create_hugepage(
413 max_producers: usize,
414 max_consumers: usize,
415 capacity: usize,
416 ) -> Result<Self, RingError> {
417 assert!(max_producers >= 1, "max_producers must be >= 1");
418 assert!(max_consumers >= 1, "max_consumers must be >= 1");
419
420 let spsc_bytes = crate::spsc_ring::spsc_ring_file_size(capacity);
421 let vyukov_bytes = crate::shared_ring::ring_file_size(capacity);
422
423 let spsc = Arc::new(SpscRingCore::create_in_region(
424 hugepage_region(spsc_bytes)?, capacity)?);
425
426 let mut mpsc_rings = Vec::with_capacity(max_producers);
427 for _ in 0..max_producers {
428 mpsc_rings.push(Arc::new(SpscRingCore::create_in_region(
429 hugepage_region(spsc_bytes)?, capacity)?));
430 }
431 let mpsc = Arc::new(MpscBacking {
432 rings: ArcSwap::from_pointee(mpsc_rings),
433 next_drain: AtomicUsize::new(0),
434 });
435
436 let mut mpmc_rings = Vec::with_capacity(max_producers);
437 for _ in 0..max_producers {
438 mpmc_rings.push(Arc::new(SpscRingCore::create_in_region(
439 hugepage_region(spsc_bytes)?, capacity)?));
440 }
441 let mpmc = Arc::new(MpmcBacking {
442 rings: ArcSwap::from_pointee(mpmc_rings),
443 consumer_cursors: consumer_cursor_table(),
444 });
445
446 let vyukov = Arc::new(SharedRing::create_in_region(
447 hugepage_region(vyukov_bytes)?, capacity)?);
448
449 let directory = Arc::new(PeerDirectory::create_anon()?);
450 directory.publish_rings(max_producers);
451
452 Ok(Self {
453 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
454 stale_shape_tag: AtomicU8::new(STALE_NONE),
455 pin_generation: AtomicU64::new(0),
456 frame_region: OnceLock::new(),
457 spsc,
458 mpsc,
459 mpmc,
460 vyukov,
461 max_producers,
462 max_consumers,
463 capacity,
464 directory,
465 synced_epoch: AtomicU64::new(u64::MAX),
466 grow_lock: parking_lot::Mutex::new(()),
467 contract: None,
468 shape_auto: AtomicBool::new(true),
469 ordering: None,
470 backing_id: BackingId::Anon,
475 header_sidecar: subetha_core::HandshakeHeader::new(),
476 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
477 })
478 }
479
480 pub fn create(
485 path_prefix: impl AsRef<Path>,
486 max_producers: usize,
487 max_consumers: usize,
488 capacity: usize,
489 ) -> Result<Self, RingError> {
490 assert!(max_producers >= 1 && max_consumers >= 1);
491 let base = path_prefix.as_ref();
492
493 let spsc_path = with_suffix(base, ".spsc.bin");
494 let spsc = Arc::new(SpscRingCore::create(&spsc_path, capacity)?);
495
496 let mut mpsc_rings = Vec::with_capacity(max_producers);
497 for i in 0..max_producers {
498 let p = with_suffix(base, &format!(".mpsc.{i}.bin"));
499 mpsc_rings.push(Arc::new(SpscRingCore::create(&p, capacity)?));
500 }
501 let mpsc = Arc::new(MpscBacking {
502 rings: ArcSwap::from_pointee(mpsc_rings),
503 next_drain: AtomicUsize::new(0),
504 });
505
506 let mut mpmc_rings = Vec::with_capacity(max_producers);
507 for i in 0..max_producers {
508 let p = with_suffix(base, &format!(".mpmc.{i}.bin"));
509 mpmc_rings.push(Arc::new(SpscRingCore::create(&p, capacity)?));
510 }
511 let mpmc = Arc::new(MpmcBacking {
512 rings: ArcSwap::from_pointee(mpmc_rings),
513 consumer_cursors: consumer_cursor_table(),
514 });
515
516 let vyukov_path = with_suffix(base, ".vyukov.bin");
517 let vyukov = Arc::new(SharedRing::create(&vyukov_path, capacity)?);
518
519 let directory = Arc::new(
520 PeerDirectory::create(with_suffix(base, ".peers.bin"))?,
521 );
522 directory.publish_rings(max_producers);
523
524 Ok(Self {
525 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
526 stale_shape_tag: AtomicU8::new(STALE_NONE),
527 pin_generation: AtomicU64::new(0),
528 frame_region: OnceLock::new(),
529 spsc,
530 mpsc,
531 mpmc,
532 vyukov,
533 max_producers,
534 max_consumers,
535 capacity,
536 directory,
537 synced_epoch: AtomicU64::new(u64::MAX),
538 grow_lock: parking_lot::Mutex::new(()),
539 contract: None,
540 shape_auto: AtomicBool::new(true),
541 ordering: None,
542 backing_id: BackingId::File {
543 prefix: base.to_path_buf(),
544 created: true,
545 },
546 header_sidecar: subetha_core::HandshakeHeader::new(),
547 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
548 })
549 }
550
551 pub fn open(
563 path_prefix: impl AsRef<Path>,
564 max_producers: usize,
565 max_consumers: usize,
566 expected_capacity: usize,
567 ) -> Result<Self, RingError> {
568 assert!(max_producers >= 1 && max_consumers >= 1);
569 let base = path_prefix.as_ref();
570
571 let directory = Arc::new(
576 PeerDirectory::open(with_suffix(base, ".peers.bin"))?,
577 );
578 let n_rings = directory.published().max(1);
579
580 let spsc_path = with_suffix(base, ".spsc.bin");
581 let spsc = Arc::new(SpscRingCore::open(&spsc_path, expected_capacity)?);
582
583 let mut mpsc_rings = Vec::with_capacity(n_rings);
584 for i in 0..n_rings {
585 let p = with_suffix(base, &format!(".mpsc.{i}.bin"));
586 mpsc_rings.push(Arc::new(SpscRingCore::open(&p, expected_capacity)?));
587 }
588 let mpsc = Arc::new(MpscBacking {
589 rings: ArcSwap::from_pointee(mpsc_rings),
590 next_drain: AtomicUsize::new(0),
591 });
592
593 let mut mpmc_rings = Vec::with_capacity(n_rings);
594 for i in 0..n_rings {
595 let p = with_suffix(base, &format!(".mpmc.{i}.bin"));
596 mpmc_rings.push(Arc::new(SpscRingCore::open(&p, expected_capacity)?));
597 }
598 let mpmc = Arc::new(MpmcBacking {
599 rings: ArcSwap::from_pointee(mpmc_rings),
600 consumer_cursors: consumer_cursor_table(),
601 });
602
603 let vyukov_path = with_suffix(base, ".vyukov.bin");
604 let vyukov = Arc::new(SharedRing::open(&vyukov_path, expected_capacity)?);
605
606 Ok(Self {
607 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
608 stale_shape_tag: AtomicU8::new(STALE_NONE),
609 pin_generation: AtomicU64::new(0),
610 frame_region: OnceLock::new(),
611 spsc,
612 mpsc,
613 mpmc,
614 vyukov,
615 max_producers,
616 max_consumers,
617 capacity: expected_capacity,
618 directory,
619 synced_epoch: AtomicU64::new(u64::MAX),
620 grow_lock: parking_lot::Mutex::new(()),
621 contract: None,
622 shape_auto: AtomicBool::new(true),
623 ordering: None,
624 backing_id: BackingId::File {
625 prefix: base.to_path_buf(),
626 created: false,
627 },
628 header_sidecar: subetha_core::HandshakeHeader::new(),
629 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
630 })
631 }
632
633 pub fn create_shmfs(
642 name_prefix: &str,
643 max_producers: usize,
644 max_consumers: usize,
645 capacity: usize,
646 ) -> Result<Self, RingError> {
647 assert!(max_producers >= 1, "max_producers must be >= 1");
648 assert!(max_consumers >= 1, "max_consumers must be >= 1");
649
650 let spsc_size = crate::spsc_ring::spsc_ring_file_size(capacity);
651 let vyukov_size = crate::shared_ring::ring_file_size(capacity);
652
653 let spsc_shm = crate::shm_file::ShmFile::create_or_open_named(
655 &format!("{name_prefix}_spsc"), spsc_size,
656 ).map_err(|_| RingError::PayloadTooLarge)?;
657 let spsc = Arc::new(SpscRingCore::create_from_shm(spsc_shm, capacity)?);
658
659 let mut mpsc_rings = Vec::with_capacity(max_producers);
661 for i in 0..max_producers {
662 let shm = crate::shm_file::ShmFile::create_or_open_named(
663 &format!("{name_prefix}_mpsc_{i}"), spsc_size,
664 ).map_err(|_| RingError::PayloadTooLarge)?;
665 mpsc_rings.push(Arc::new(SpscRingCore::create_from_shm(shm, capacity)?));
666 }
667 let mpsc = Arc::new(MpscBacking {
668 rings: ArcSwap::from_pointee(mpsc_rings),
669 next_drain: AtomicUsize::new(0),
670 });
671
672 let mut mpmc_rings = Vec::with_capacity(max_producers);
674 for i in 0..max_producers {
675 let shm = crate::shm_file::ShmFile::create_or_open_named(
676 &format!("{name_prefix}_mpmc_{i}"), spsc_size,
677 ).map_err(|_| RingError::PayloadTooLarge)?;
678 mpmc_rings.push(Arc::new(SpscRingCore::create_from_shm(shm, capacity)?));
679 }
680 let mpmc = Arc::new(MpmcBacking {
681 rings: ArcSwap::from_pointee(mpmc_rings),
682 consumer_cursors: consumer_cursor_table(),
683 });
684
685 let vyukov_shm = crate::shm_file::ShmFile::create_or_open_named(
687 &format!("{name_prefix}_vyukov"), vyukov_size,
688 ).map_err(|_| RingError::PayloadTooLarge)?;
689 let vyukov = Arc::new(SharedRing::create_from_shm(vyukov_shm, capacity)?);
690
691 let directory = Arc::new(PeerDirectory::create_or_open_shm(
692 &format!("{name_prefix}_peers"),
693 )?);
694 directory.publish_rings(max_producers);
695
696 Ok(Self {
697 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
698 stale_shape_tag: AtomicU8::new(STALE_NONE),
699 pin_generation: AtomicU64::new(0),
700 frame_region: OnceLock::new(),
701 spsc, mpsc, mpmc, vyukov,
702 max_producers, max_consumers,
703 capacity,
704 directory,
705 synced_epoch: AtomicU64::new(u64::MAX),
706 grow_lock: parking_lot::Mutex::new(()),
707 contract: None,
708 shape_auto: AtomicBool::new(true),
709 ordering: None,
710 backing_id: BackingId::Shm { prefix: name_prefix.to_owned() },
711 header_sidecar: subetha_core::HandshakeHeader::new(),
712 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
713 })
714 }
715
716 pub fn open_shmfs(
736 name_prefix: &str,
737 max_producers: usize,
738 max_consumers: usize,
739 expected_capacity: usize,
740 ) -> Result<Self, RingError> {
741 assert!(max_producers >= 1, "max_producers must be >= 1");
742 assert!(max_consumers >= 1, "max_consumers must be >= 1");
743
744 let spsc_size = crate::spsc_ring::spsc_ring_file_size(expected_capacity);
745 let vyukov_size = crate::shared_ring::ring_file_size(expected_capacity);
746
747 let directory = Arc::new(PeerDirectory::create_or_open_shm(
752 &format!("{name_prefix}_peers"),
753 )?);
754 let n_rings = directory.published().max(1);
755
756 let spsc_shm = crate::shm_file::ShmFile::create_or_open_named(
758 &format!("{name_prefix}_spsc"), spsc_size,
759 ).map_err(|_| RingError::PayloadTooLarge)?;
760 let spsc = Arc::new(SpscRingCore::open_from_shm(spsc_shm, expected_capacity)?);
761
762 let mut mpsc_rings = Vec::with_capacity(n_rings);
764 for i in 0..n_rings {
765 let shm = crate::shm_file::ShmFile::create_or_open_named(
766 &format!("{name_prefix}_mpsc_{i}"), spsc_size,
767 ).map_err(|_| RingError::PayloadTooLarge)?;
768 mpsc_rings.push(Arc::new(SpscRingCore::open_from_shm(shm, expected_capacity)?));
769 }
770 let mpsc = Arc::new(MpscBacking {
771 rings: ArcSwap::from_pointee(mpsc_rings),
772 next_drain: AtomicUsize::new(0),
773 });
774
775 let mut mpmc_rings = Vec::with_capacity(n_rings);
777 for i in 0..n_rings {
778 let shm = crate::shm_file::ShmFile::create_or_open_named(
779 &format!("{name_prefix}_mpmc_{i}"), spsc_size,
780 ).map_err(|_| RingError::PayloadTooLarge)?;
781 mpmc_rings.push(Arc::new(SpscRingCore::open_from_shm(shm, expected_capacity)?));
782 }
783 let mpmc = Arc::new(MpmcBacking {
784 rings: ArcSwap::from_pointee(mpmc_rings),
785 consumer_cursors: consumer_cursor_table(),
786 });
787
788 let vyukov_shm = crate::shm_file::ShmFile::create_or_open_named(
790 &format!("{name_prefix}_vyukov"), vyukov_size,
791 ).map_err(|_| RingError::PayloadTooLarge)?;
792 let vyukov = Arc::new(SharedRing::open_from_shm(vyukov_shm, expected_capacity)?);
793
794 Ok(Self {
795 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
796 stale_shape_tag: AtomicU8::new(STALE_NONE),
797 pin_generation: AtomicU64::new(0),
798 frame_region: OnceLock::new(),
799 spsc, mpsc, mpmc, vyukov,
800 max_producers, max_consumers,
801 capacity: expected_capacity,
802 directory,
803 synced_epoch: AtomicU64::new(u64::MAX),
804 grow_lock: parking_lot::Mutex::new(()),
805 contract: None,
806 shape_auto: AtomicBool::new(true),
807 ordering: None,
808 backing_id: BackingId::Shm { prefix: name_prefix.to_owned() },
809 header_sidecar: subetha_core::HandshakeHeader::new(),
810 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
811 })
812 }
813
814 pub fn with_ordering_stamps(self) -> Result<Self, RingError> {
838 self.with_ordering_stamps_impl(None)
839 }
840
841 pub fn with_ordering_stamps_kind(self, kind: StampKind) -> Result<Self, RingError> {
848 self.with_ordering_stamps_impl(Some(kind))
849 }
850
851 fn with_ordering_stamps_impl(
852 mut self,
853 kind: Option<StampKind>,
854 ) -> Result<Self, RingError> {
855 if self.ordering.is_some() {
856 return Ok(self);
857 }
858 if self.current_shape() == RingShape::Vyukov {
859 return Err(RingError::LayoutMismatch);
860 }
861 let lines = crate::peer_directory::PRODUCER_SLOT_CEILING;
868 let region = match &self.backing_id {
869 BackingId::Anon => OrderingRegion::create_anon(
870 lines,
871 kind.unwrap_or_else(default_stamp_kind),
872 )?,
873 BackingId::File { prefix, created } => {
874 let path = with_suffix(prefix, ".ordering.bin");
875 if *created {
876 OrderingRegion::create(
877 &path,
878 lines,
879 kind.unwrap_or_else(default_stamp_kind),
880 )?
881 } else {
882 let region = OrderingRegion::open(&path, lines)?;
883 if let Some(k) = kind
884 && region.stamp_kind() != k
885 {
886 return Err(RingError::LayoutMismatch);
887 }
888 region
889 }
890 }
891 BackingId::Shm { prefix } => {
892 let size = ordering_region_size(lines);
893 let shm = crate::shm_file::ShmFile::create_or_open_named(
894 &format!("{prefix}_ordering"),
895 size,
896 ).map_err(|e| RingError::IoError(e.kind()))?;
897 OrderingRegion::create_shm(
898 shm,
899 lines,
900 kind.unwrap_or_else(default_stamp_kind),
901 )?
902 }
903 };
904 let seen = (0..CONSUMER_SLOT_CEILING).map(|_| SeenLine::new()).collect();
905 self.ordering = Some(Arc::new(OrderingState { region, seen }));
906 Ok(self)
907 }
908
909 pub fn current_shape(&self) -> RingShape {
911 RingShape::from_u8(self.shape_tag.load(Ordering::Acquire))
912 }
913
914 pub fn peek_spsc_slot(&self) -> Option<PeekedSpscSlot<'_>> {
925 if self.current_shape() != RingShape::Spsc {
926 return None;
927 }
928 self.spsc.peek_slot().map(|inner| PeekedSpscSlot { inner })
929 }
930
931 pub fn is_empty(&self) -> bool {
950 if let Some(stale) = self.stale_shape()
951 && !self.backing_is_empty(stale)
952 {
953 return false;
954 }
955 self.backing_is_empty(self.current_shape())
956 }
957
958 pub fn approx_len(&self) -> usize {
963 let stale_len = match self.stale_shape() {
964 Some(stale) if stale != self.current_shape() => {
965 self.backing_approx_len(stale)
966 }
967 _ => 0,
968 };
969 stale_len + self.backing_approx_len(self.current_shape())
970 }
971
972 fn backing_approx_len(&self, shape: RingShape) -> usize {
973 match shape {
974 RingShape::Spsc => self.spsc.approx_len(),
975 RingShape::Mpsc => self.mpsc.rings.load().iter().map(|r| r.approx_len()).sum(),
976 RingShape::Mpmc => self.mpmc.rings.load().iter().map(|r| r.approx_len()).sum(),
977 RingShape::Vyukov => self.vyukov.approx_len(),
978 }
979 }
980
981 pub fn sub_ring_capacity(&self) -> usize {
986 match self.current_shape() {
987 RingShape::Spsc => self.spsc.capacity(),
988 RingShape::Mpsc => self.mpsc.rings.load().first().map(|r| r.capacity()).unwrap_or(0),
989 RingShape::Mpmc => self.mpmc.rings.load().first().map(|r| r.capacity()).unwrap_or(0),
990 RingShape::Vyukov => self.vyukov.capacity(),
991 }
992 }
993
994 pub fn total_slot_capacity(&self) -> usize {
999 match self.current_shape() {
1000 RingShape::Spsc => self.spsc.capacity(),
1001 RingShape::Mpsc => self.mpsc.rings.load().iter().map(|r| r.capacity()).sum(),
1002 RingShape::Mpmc => self.mpmc.rings.load().iter().map(|r| r.capacity()).sum(),
1003 RingShape::Vyukov => self.vyukov.capacity(),
1004 }
1005 }
1006
1007 pub fn pin_generation(&self) -> u64 {
1010 self.pin_generation.load(Ordering::Acquire)
1011 }
1012
1013 pub fn max_producers(&self) -> usize { self.max_producers }
1017
1018 pub fn max_consumers(&self) -> usize { self.max_consumers }
1021
1022 pub fn published_producers(&self) -> usize {
1025 self.directory.published()
1026 }
1027
1028 pub fn contract(&self) -> crate::ring_contract::RingContract {
1033 self.contract.unwrap_or_else(crate::ring_contract::RingContract::unbounded)
1034 }
1035
1036 pub fn with_contract(mut self, contract: crate::ring_contract::RingContract) -> Self {
1042 self.contract = Some(contract);
1043 self
1044 }
1045
1046 pub fn contract_filtered_shape(&self, target: RingShape) -> RingShape {
1058 if self.contract().permits_shape(target) {
1059 return target;
1060 }
1061 if self.ordering.is_none() {
1062 RingShape::Vyukov
1063 } else {
1064 target
1065 }
1066 }
1067
1068 fn reshape_for_counts(&self) -> bool {
1080 if !self.shape_auto.load(Ordering::Relaxed)
1081 || self.current_shape() == RingShape::Vyukov
1082 {
1083 return true;
1084 }
1085 let p = self.directory.active_producers();
1086 let c = self.directory.active_consumers();
1087 if let Some(target) = DefaultRingShapePolicy::target_shape(p, c)
1088 && target != self.current_shape()
1089 {
1090 return self.morph_shape(self.contract_filtered_shape(target)).is_ok();
1091 }
1092 true
1093 }
1094
1095 pub fn pin_shape(&self) {
1100 self.shape_auto.store(false, Ordering::Relaxed);
1101 }
1102
1103 pub fn resume_auto_shape(&self) {
1109 self.shape_auto.store(true, Ordering::Relaxed);
1110 let p = self.directory.active_producers();
1111 let c = self.directory.active_consumers();
1112 if let Some(target) = DefaultRingShapePolicy::target_shape(p, c) {
1113 self.morph_shape(self.contract_filtered_shape(target)).ok();
1114 }
1115 }
1116
1117 pub fn shape_is_auto(&self) -> bool {
1121 self.shape_auto.load(Ordering::Relaxed)
1122 }
1123
1124 #[inline]
1129 fn ensure_synced(&self) {
1130 let e = self.directory.epoch();
1131 if e != self.synced_epoch.load(Ordering::Relaxed) {
1132 self.sync_topology(e);
1133 }
1134 }
1135
1136 #[cold]
1137 fn sync_topology(&self, epoch: u64) {
1138 self.directory.reap_dead_peers();
1139 let arrays_ok = self.refresh_local_arrays().is_ok();
1140 let shape_ok = self.reshape_for_counts();
1141 if arrays_ok && shape_ok {
1142 self.synced_epoch.store(epoch, Ordering::Relaxed);
1146 }
1147 }
1148
1149 fn refresh_local_arrays(&self) -> Result<(), RingError> {
1154 let published = self.directory.published();
1155 if self.mpsc.rings.load().len() >= published {
1156 return Ok(());
1157 }
1158 let _guard = self.grow_lock.lock();
1159 let cur_mpsc = self.mpsc.rings.load_full();
1160 let cur_mpmc = self.mpmc.rings.load_full();
1161 if cur_mpsc.len() >= published {
1162 return Ok(());
1163 }
1164 let mut mpsc_new = (*cur_mpsc).clone();
1165 let mut mpmc_new = (*cur_mpmc).clone();
1166 for i in cur_mpsc.len()..published {
1167 let (a, b) = self.open_ring_backing(i)?;
1168 mpsc_new.push(a);
1169 mpmc_new.push(b);
1170 }
1171 self.mpsc.rings.store(Arc::new(mpsc_new));
1172 self.mpmc.rings.store(Arc::new(mpmc_new));
1173 self.pin_generation.fetch_add(1, Ordering::AcqRel);
1174 Ok(())
1175 }
1176
1177 fn open_ring_backing(
1181 &self,
1182 i: usize,
1183 ) -> Result<(Arc<SpscRingCore>, Arc<SpscRingCore>), RingError> {
1184 match &self.backing_id {
1185 BackingId::File { prefix, .. } => {
1186 let a = SpscRingCore::open(
1187 with_suffix(prefix, &format!(".mpsc.{i}.bin")), self.capacity)?;
1188 let b = SpscRingCore::open(
1189 with_suffix(prefix, &format!(".mpmc.{i}.bin")), self.capacity)?;
1190 Ok((Arc::new(a), Arc::new(b)))
1191 }
1192 BackingId::Shm { prefix } => {
1193 let size = crate::spsc_ring::spsc_ring_file_size(self.capacity);
1194 let shm_a = crate::shm_file::ShmFile::create_or_open_named(
1195 &format!("{prefix}_mpsc_{i}"), size,
1196 ).map_err(|e| RingError::IoError(e.kind()))?;
1197 let shm_b = crate::shm_file::ShmFile::create_or_open_named(
1198 &format!("{prefix}_mpmc_{i}"), size,
1199 ).map_err(|e| RingError::IoError(e.kind()))?;
1200 let a = SpscRingCore::create_from_shm(shm_a, self.capacity)?;
1201 let b = SpscRingCore::create_from_shm(shm_b, self.capacity)?;
1202 Ok((Arc::new(a), Arc::new(b)))
1203 }
1204 BackingId::Anon => Err(RingError::LayoutMismatch),
1207 }
1208 }
1209
1210 fn create_ring_backing(
1214 &self,
1215 i: usize,
1216 ) -> Result<(Arc<SpscRingCore>, Arc<SpscRingCore>), RingError> {
1217 match &self.backing_id {
1218 BackingId::Anon => {
1219 let a = SpscRingCore::create_anon(self.capacity)?;
1220 let b = SpscRingCore::create_anon(self.capacity)?;
1221 Ok((Arc::new(a), Arc::new(b)))
1222 }
1223 BackingId::File { prefix, .. } => {
1224 let a = SpscRingCore::create(
1225 with_suffix(prefix, &format!(".mpsc.{i}.bin")), self.capacity)?;
1226 let b = SpscRingCore::create(
1227 with_suffix(prefix, &format!(".mpmc.{i}.bin")), self.capacity)?;
1228 Ok((Arc::new(a), Arc::new(b)))
1229 }
1230 BackingId::Shm { prefix } => {
1231 let size = crate::spsc_ring::spsc_ring_file_size(self.capacity);
1232 let shm_a = crate::shm_file::ShmFile::create_or_open_named(
1233 &format!("{prefix}_mpsc_{i}"), size,
1234 ).map_err(|e| RingError::IoError(e.kind()))?;
1235 let shm_b = crate::shm_file::ShmFile::create_or_open_named(
1236 &format!("{prefix}_mpmc_{i}"), size,
1237 ).map_err(|e| RingError::IoError(e.kind()))?;
1238 let a = SpscRingCore::create_from_shm(shm_a, self.capacity)?;
1239 let b = SpscRingCore::create_from_shm(shm_b, self.capacity)?;
1240 Ok((Arc::new(a), Arc::new(b)))
1241 }
1242 }
1243 }
1244
1245 fn grow_rings_to(&self, want: usize) -> Result<(), RingError> {
1250 let _guard = self.grow_lock.lock();
1251 let published = self.directory.published();
1252 let cur_mpsc = self.mpsc.rings.load_full();
1253 let cur_mpmc = self.mpmc.rings.load_full();
1254 let mut mpsc_new = (*cur_mpsc).clone();
1255 let mut mpmc_new = (*cur_mpmc).clone();
1256 for i in cur_mpsc.len()..published {
1259 let (a, b) = self.open_ring_backing(i)?;
1260 mpsc_new.push(a);
1261 mpmc_new.push(b);
1262 }
1263 for i in published..want {
1264 let (a, b) = self.create_ring_backing(i)?;
1265 mpsc_new.push(a);
1266 mpmc_new.push(b);
1267 }
1268 if mpsc_new.len() > cur_mpsc.len() {
1269 self.mpsc.rings.store(Arc::new(mpsc_new));
1270 self.mpmc.rings.store(Arc::new(mpmc_new));
1271 self.pin_generation.fetch_add(1, Ordering::AcqRel);
1272 }
1273 if want > published {
1274 self.directory.publish_rings(want);
1275 }
1276 Ok(())
1277 }
1278
1279 pub fn register_producer(&self) -> Result<usize, AdaptiveError> {
1290 let slot = self.directory.claim_producer_slot()
1291 .ok_or(AdaptiveError::TooManyProducers)?;
1292 if let Some(g) = self.contract
1293 && !g.permits_producer(self.directory.active_producers() - 1)
1294 {
1295 self.directory.release_producer_slot(slot);
1296 return Err(AdaptiveError::TooManyProducers);
1297 }
1298 if slot >= self.directory.published()
1299 && self.grow_rings_to(slot + 1).is_err()
1300 {
1301 self.directory.release_producer_slot(slot);
1302 return Err(AdaptiveError::GrowthFailed);
1303 }
1304 self.ensure_synced();
1305 self.reshape_for_counts();
1306 Ok(slot)
1307 }
1308
1309 pub fn unregister_producer(&self, producer_id: usize) {
1314 self.directory.release_producer_slot(producer_id);
1315 self.reshape_for_counts();
1316 }
1317
1318 pub fn register_consumer(&self) -> Result<usize, AdaptiveError> {
1325 let slot = self.directory.claim_consumer_slot()
1326 .ok_or(AdaptiveError::TooManyConsumers)?;
1327 if let Some(g) = self.contract
1328 && !g.permits_consumer(self.directory.active_consumers() - 1)
1329 {
1330 self.directory.release_consumer_slot(slot);
1331 return Err(AdaptiveError::TooManyConsumers);
1332 }
1333 self.rebalance_ownership();
1334 self.ensure_synced();
1335 self.reshape_for_counts();
1336 Ok(slot)
1337 }
1338
1339 pub fn unregister_consumer(&self, consumer_id: usize) {
1344 let me = consumer_id as u16;
1345 let remaining: Vec<u16> = self.directory.claimed_consumer_slots()
1346 .into_iter()
1347 .filter(|s| *s != me)
1348 .collect();
1349 let n = self.directory.published();
1350 for r in 0..n {
1351 let (owner, _) = self.directory.ring_owner(r);
1352 if owner == me {
1353 match remaining.get(r % remaining.len().max(1)) {
1354 Some(to) => self.directory.transfer_ring(r, me, *to),
1355 None => self.directory.transfer_ring(r, me, OWNER_NONE),
1356 }
1357 }
1358 }
1359 self.directory.release_consumer_slot(consumer_id);
1360 self.reshape_for_counts();
1361 }
1362
1363 fn rebalance_ownership(&self) {
1369 let slots = self.directory.claimed_consumer_slots();
1370 if slots.is_empty() {
1371 return;
1372 }
1373 let n = self.directory.published();
1374 for r in 0..n {
1375 let desired = slots[r % slots.len()];
1376 let (owner, pending) = self.directory.ring_owner(r);
1377 if owner == desired {
1378 continue;
1379 }
1380 if owner == OWNER_NONE {
1381 self.directory.try_claim_ring(r, desired);
1382 } else if pending != desired {
1383 self.directory.request_handoff(r, desired);
1384 }
1385 }
1386 }
1387
1388 pub fn active_producers(&self) -> usize {
1390 self.directory.active_producers()
1391 }
1392
1393 pub fn active_consumers(&self) -> usize {
1395 self.directory.active_consumers()
1396 }
1397
1398 pub fn is_stamped(&self) -> bool {
1400 self.ordering.is_some()
1401 }
1402
1403 pub fn stamp_kind(&self) -> Option<StampKind> {
1405 self.ordering.as_ref().map(|o| o.region.stamp_kind())
1406 }
1407
1408 pub fn ordering_mode(&self) -> Option<OrderingMode> {
1413 self.ordering.as_ref().map(|o| o.region.mode())
1414 }
1415
1416 pub fn set_ordering_mode(&self, mode: OrderingMode) -> Result<(), RingError> {
1422 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1423 ord.region.set_mode(mode);
1424 Ok(())
1425 }
1426
1427 pub fn inversions(&self) -> u64 {
1430 self.ordering.as_ref().map(|o| o.region.inversions()).unwrap_or(0)
1431 }
1432
1433 pub fn refresh_watermark(&self, producer_id: usize) -> Result<(), RingError> {
1436 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1437 if producer_id >= ord.region.max_producers() {
1438 return Err(RingError::PayloadTooLarge);
1439 }
1440 ord.region.refresh_watermark(producer_id);
1441 Ok(())
1442 }
1443
1444 pub fn retire_producer(&self, producer_id: usize) -> Result<(), RingError> {
1449 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1450 if producer_id >= ord.region.max_producers() {
1451 return Err(RingError::PayloadTooLarge);
1452 }
1453 ord.region.retire_producer(producer_id);
1454 Ok(())
1455 }
1456
1457 pub fn release_drainer(&self, consumer_id: usize) -> Result<bool, RingError> {
1461 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1462 Ok(ord.region.release_drainer(drainer_token(consumer_id)))
1463 }
1464
1465 pub fn tick_drainer_epoch(&self) -> Result<u64, RingError> {
1470 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1471 Ok(ord.region.tick_drainer_epoch())
1472 }
1473
1474 pub fn ordering_region(&self) -> Option<&OrderingRegion> {
1479 self.ordering.as_ref().map(|o| &o.region)
1480 }
1481
1482 pub fn try_send(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
1494 self.ensure_synced();
1495 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1496 if let Some(ord) = &self.ordering {
1497 return self.stamped_send_inner(ord, shape, producer_id, payload);
1498 }
1499 match shape {
1500 RingShape::Spsc => self.spsc.try_push(payload),
1501 RingShape::Mpsc => {
1502 let rings = self.mpsc.rings.load();
1503 let ring = rings.get(producer_id)
1504 .ok_or(RingError::PayloadTooLarge)?; ring.try_push(payload)
1506 }
1507 RingShape::Mpmc => {
1508 let rings = self.mpmc.rings.load();
1509 let ring = rings.get(producer_id)
1510 .ok_or(RingError::PayloadTooLarge)?;
1511 ring.try_push(payload)
1512 }
1513 RingShape::Vyukov => self.vyukov.try_push(payload),
1514 }
1515 }
1516
1517 pub fn try_recv(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
1527 self.ensure_synced();
1528 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1529 if let Some(ord) = &self.ordering {
1530 return self.ordered_recv_inner(ord, shape, consumer_id, out)
1531 .map(|(n, _stamp)| n);
1532 }
1533 if let Some(stale) = self.stale_shape()
1537 && stale != shape
1538 && Self::may_walk_stale(stale, consumer_id)
1539 && let Ok(n) = self.shape_pop(stale, consumer_id, out)
1540 {
1541 return Ok(n);
1542 }
1543 self.shape_pop(shape, consumer_id, out)
1544 }
1545
1546 pub const FRAME_INLINE_BUDGET: usize = PAYLOAD_BYTES - 5;
1552
1553 pub const FRAME_DEFAULT_BLOCK_SIZE: usize = 8192;
1558
1559 pub fn with_frames(self, block_size: usize, block_count: usize) -> Self {
1565 self.frame_region
1566 .get_or_init(|| self.build_frame_region(block_size, block_count));
1567 self
1568 }
1569
1570 fn frame_region(&self) -> &FrameRegion {
1571 self.frame_region.get_or_init(|| {
1572 let blocks = self.spsc.capacity().max(16);
1573 self.build_frame_region(Self::FRAME_DEFAULT_BLOCK_SIZE, blocks)
1574 })
1575 }
1576
1577 fn build_frame_region(&self, block_size: usize, block_count: usize) -> Arc<FrameRegion> {
1594 let region = match &self.backing_id {
1595 BackingId::Anon => FrameRegion::create_anon(block_size, block_count),
1596 BackingId::Shm { prefix } => FrameRegion::create_or_open_shm(
1597 &format!("{prefix}_frames"),
1598 block_size,
1599 block_count,
1600 ),
1601 BackingId::File { prefix, .. } => FrameRegion::create_or_open_file(
1602 with_suffix(prefix, ".frames.bin"),
1603 block_size,
1604 block_count,
1605 ),
1606 };
1607 Arc::new(region.expect("frame region create-or-open"))
1608 }
1609
1610 pub fn send_frame(&self, producer_id: usize, payload: &[u8])
1624 -> Result<FrameClass, RingError>
1625 {
1626 self.send_frame_as(producer_id, payload, LayoutHint::Auto)
1627 }
1628
1629 pub fn send_frame_as(&self, producer_id: usize, payload: &[u8], hint: LayoutHint)
1632 -> Result<FrameClass, RingError>
1633 {
1634 if self.ordering.is_some() {
1635 return Err(RingError::LayoutMismatch);
1636 }
1637 let inline = match hint {
1638 LayoutHint::ForceInline => {
1639 if payload.len() > Self::FRAME_INLINE_BUDGET {
1640 return Err(RingError::PayloadTooLarge);
1641 }
1642 true
1643 }
1644 LayoutHint::ForceOffset => false,
1645 LayoutHint::Auto => payload.len() <= Self::FRAME_INLINE_BUDGET,
1646 };
1647 let len = payload.len() as u32;
1648 if inline {
1649 let mut buf = [0u8; PAYLOAD_BYTES];
1652 buf[0] = FrameClass::Inline as u8;
1653 buf[1..5].copy_from_slice(&len.to_le_bytes());
1654 buf[5..5 + payload.len()].copy_from_slice(payload);
1655 self.try_send(producer_id, &buf[..5 + payload.len()])?;
1656 Ok(FrameClass::Inline)
1657 } else {
1658 let region = self.frame_region();
1659 if payload.len() > region.block_size() {
1660 return Err(RingError::PayloadTooLarge);
1661 }
1662 let idx = region.alloc().ok_or(RingError::Full)?;
1663 region.write_block(idx, payload);
1664 let mut buf = [0u8; 9];
1666 buf[0] = FrameClass::Offset as u8;
1667 buf[1..5].copy_from_slice(&len.to_le_bytes());
1668 buf[5..9].copy_from_slice(&idx.to_le_bytes());
1669 match self.try_send(producer_id, &buf) {
1670 Ok(()) => Ok(FrameClass::Offset),
1671 Err(e) => {
1672 region.free(idx);
1675 Err(e)
1676 }
1677 }
1678 }
1679 }
1680
1681 pub fn recv_frame(&self, consumer_id: usize, out: &mut Vec<u8>)
1689 -> Result<FrameClass, RingError>
1690 {
1691 if self.ordering.is_some() {
1692 return Err(RingError::LayoutMismatch);
1693 }
1694 let mut slot = [0u8; SPSC_PAYLOAD_BYTES];
1696 self.try_recv(consumer_id, &mut slot)?;
1697 let len = u32::from_le_bytes([slot[1], slot[2], slot[3], slot[4]]) as usize;
1698 out.clear();
1699 if slot[0] == FrameClass::Inline as u8 {
1700 out.extend_from_slice(&slot[5..5 + len]);
1701 Ok(FrameClass::Inline)
1702 } else {
1703 let idx = u32::from_le_bytes([slot[5], slot[6], slot[7], slot[8]]);
1704 let region = self.frame_region();
1705 region.read_block_into(idx, len, out);
1706 region.free(idx);
1707 Ok(FrameClass::Offset)
1708 }
1709 }
1710
1711 pub fn try_recv_with_stamp(
1717 &self,
1718 consumer_id: usize,
1719 out: &mut [u8],
1720 ) -> Result<(usize, u64), RingError> {
1721 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1722 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1723 self.ordered_recv_inner(ord, shape, consumer_id, out)
1724 }
1725
1726 fn stamped_send_inner(
1733 &self,
1734 ord: &OrderingState,
1735 shape: RingShape,
1736 producer_id: usize,
1737 payload: &[u8],
1738 ) -> Result<(), RingError> {
1739 if payload.len() > STAMPED_PAYLOAD_BYTES {
1740 return Err(RingError::PayloadTooLarge);
1741 }
1742 if producer_id >= ord.region.max_producers() {
1743 return Err(RingError::PayloadTooLarge);
1744 }
1745 let mpsc_guard;
1746 let mpmc_guard;
1747 let ring: &SpscRingCore = match shape {
1748 RingShape::Spsc => &self.spsc,
1749 RingShape::Mpsc => {
1750 mpsc_guard = self.mpsc.rings.load();
1751 mpsc_guard.get(producer_id).ok_or(RingError::PayloadTooLarge)?
1752 }
1753 RingShape::Mpmc => {
1754 mpmc_guard = self.mpmc.rings.load();
1755 mpmc_guard.get(producer_id).ok_or(RingError::PayloadTooLarge)?
1756 }
1757 RingShape::Vyukov => return Err(RingError::LayoutMismatch),
1762 };
1763 let stamp = ord.region.next_stamp(producer_id);
1764 let mut buf = [0u8; SPSC_PAYLOAD_BYTES];
1765 buf[..STAMP_BYTES].copy_from_slice(&stamp.to_le_bytes());
1766 buf[STAMP_BYTES..STAMP_BYTES + payload.len()].copy_from_slice(payload);
1767 let result = ring.try_push(&buf[..STAMP_BYTES + payload.len()]);
1768 ord.region.publish_watermark(producer_id, stamp);
1769 result
1770 }
1771
1772 fn ordered_recv_inner(
1776 &self,
1777 ord: &OrderingState,
1778 shape: RingShape,
1779 consumer_id: usize,
1780 out: &mut [u8],
1781 ) -> Result<(usize, u64), RingError> {
1782 if consumer_id >= CONSUMER_SLOT_CEILING {
1783 return Err(RingError::PayloadTooLarge);
1784 }
1785 if out.len() < STAMPED_PAYLOAD_BYTES {
1786 return Err(RingError::PayloadTooLarge);
1787 }
1788 let mode = ord.region.mode();
1789 match mode {
1790 OrderingMode::Unordered => {
1791 let mut buf = [0u8; SPSC_PAYLOAD_BYTES];
1792 let popped = self.stale_shape()
1796 .filter(|stale| *stale != shape)
1797 .filter(|stale| Self::may_walk_stale(*stale, consumer_id))
1798 .and_then(|stale| {
1799 self.shape_pop(stale, consumer_id, &mut buf).ok()
1800 });
1801 if popped.is_none() {
1802 match shape {
1803 RingShape::Spsc => self.spsc.try_pop(&mut buf),
1804 RingShape::Mpsc => self.mpsc_pop(&mut buf),
1805 RingShape::Mpmc => self.mpmc_pop(consumer_id, &mut buf),
1806 RingShape::Vyukov => Err(RingError::LayoutMismatch),
1807 }?;
1808 }
1809 let stamp = u64::from_le_bytes(
1810 buf[..STAMP_BYTES].try_into().unwrap(),
1811 );
1812 self.note_stamp(ord, consumer_id, mode, stamp);
1813 out[..STAMPED_PAYLOAD_BYTES]
1814 .copy_from_slice(&buf[STAMP_BYTES..]);
1815 Ok((STAMPED_PAYLOAD_BYTES, stamp))
1816 }
1817 OrderingMode::MergeByStamp | OrderingMode::MergeStrict => {
1818 let seen = &ord.seen[consumer_id];
1829 let lease_gen_now = ord.region.lease_generation();
1830 if seen.lease_gen.load(Ordering::Relaxed) != lease_gen_now {
1831 if !ord.region.try_acquire_drainer(
1832 drainer_token(consumer_id),
1833 DRAINER_GRACE_EPOCHS,
1834 ) {
1835 return Err(RingError::NotDrainer);
1836 }
1837 seen.lease_gen.store(lease_gen_now, Ordering::Relaxed);
1838 }
1839 if let Some(stale) = self.stale_shape()
1845 && stale != shape
1846 {
1847 match self.merge_pop(ord, stale, consumer_id, mode, out) {
1848 Ok(result) => return Ok(result),
1849 Err(RingError::Empty) => {}
1850 Err(e) => return Err(e),
1851 }
1852 }
1853 self.merge_pop(ord, shape, consumer_id, mode, out)
1854 }
1855 }
1856 }
1857
1858
1859 fn merge_pop(
1890 &self,
1891 ord: &OrderingState,
1892 shape: RingShape,
1893 consumer_id: usize,
1894 mode: OrderingMode,
1895 out: &mut [u8],
1896 ) -> Result<(usize, u64), RingError> {
1897 let mpsc_guard;
1900 let mpmc_guard;
1901 let rings: &[Arc<SpscRingCore>] = match shape {
1902 RingShape::Spsc => std::slice::from_ref(&self.spsc),
1903 RingShape::Mpsc => {
1904 mpsc_guard = self.mpsc.rings.load();
1905 mpsc_guard.as_slice()
1906 }
1907 RingShape::Mpmc => {
1908 mpmc_guard = self.mpmc.rings.load();
1909 mpmc_guard.as_slice()
1910 }
1911 RingShape::Vyukov => &[],
1912 };
1913 if rings.is_empty() {
1914 return Err(RingError::LayoutMismatch);
1915 }
1916 let gate_lines = self.directory.published()
1921 .min(ord.region.max_producers());
1922 let kind = ord.region.stamp_kind();
1923 loop {
1924 let mut best: Option<(usize, u64)> = None;
1928 let mut any_empty = false;
1929 for (i, ring) in rings.iter().enumerate() {
1930 match ring.peek_slot() {
1931 Some(peek) => {
1932 let s = u64::from_le_bytes(
1933 peek[..STAMP_BYTES].try_into().unwrap(),
1934 );
1935 if best.is_none_or(|(_, bs)| s < bs) {
1936 best = Some((i, s));
1937 }
1938 }
1939 None => any_empty = true,
1940 }
1941 }
1942 let Some((idx, stamp)) = best else {
1943 return Err(RingError::Empty);
1944 };
1945
1946 for line in 0..gate_lines {
1947 if ord.region.in_flight_below(line, stamp) {
1948 return Err(RingError::Empty);
1949 }
1950 }
1951 if mode == OrderingMode::MergeStrict {
1952 for line in 0..gate_lines {
1953 if ord.region.issued(line) != 0
1958 && rings[line.min(rings.len() - 1)].approx_len() == 0
1959 && ord.region.watermark(line) < stamp
1960 {
1961 return Err(RingError::Empty);
1962 }
1963 }
1964 }
1965 if any_empty
1966 && let Some(guard) = kind.freshness_guard()
1967 && stamp_now(kind).wrapping_sub(stamp) < guard
1968 {
1969 std::hint::spin_loop();
1970 continue;
1971 }
1972
1973 let peek = rings[idx].peek_slot().expect(
1974 "single drainer holds the lease; a peeked head cannot vanish",
1975 );
1976 let confirmed_stamp = u64::from_le_bytes(
1977 peek[..STAMP_BYTES].try_into().unwrap(),
1978 );
1979 out[..STAMPED_PAYLOAD_BYTES].copy_from_slice(&peek[STAMP_BYTES..]);
1980 peek.confirm();
1981 self.note_stamp(ord, consumer_id, mode, confirmed_stamp);
1982 return Ok((STAMPED_PAYLOAD_BYTES, confirmed_stamp));
1983 }
1984 }
1985
1986 fn note_stamp(
1993 &self,
1994 ord: &OrderingState,
1995 consumer_id: usize,
1996 mode: OrderingMode,
1997 stamp: u64,
1998 ) {
1999 let line = &ord.seen[consumer_id];
2000 if line.mode_tag.swap(mode as u32, Ordering::Relaxed) != mode as u32 {
2001 line.stamp.store(0, Ordering::Relaxed);
2002 }
2003 let last = line.stamp.load(Ordering::Relaxed);
2004 if stamp < last {
2005 ord.region.record_inversion();
2006 self.ring_sidecar
2007 .push_op(crate::sidecar_ops::ordering::OP_ORDER_INVERSION, 0);
2008 }
2009 line.stamp.store(stamp, Ordering::Relaxed);
2010 }
2011
2012 fn mpsc_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2013 let rings = self.mpsc.rings.load();
2014 self.mpsc_pop_in(&rings, out)
2015 }
2016
2017 fn mpsc_pop_in(
2018 &self,
2019 rings: &[Arc<SpscRingCore>],
2020 out: &mut [u8],
2021 ) -> Result<usize, RingError> {
2022 let n = rings.len();
2023 if n == 0 {
2024 return Err(RingError::Empty);
2025 }
2026 let start = self.mpsc.next_drain.load(Ordering::Relaxed);
2027 for i in 0..n {
2028 let idx = (start + i) % n;
2029 if let Ok(bytes) = rings[idx].try_pop(out) {
2030 self.mpsc.next_drain.store((idx + 1) % n, Ordering::Relaxed);
2031 return Ok(bytes);
2032 }
2033 }
2034 Err(RingError::Empty)
2035 }
2036
2037 fn mpmc_pop(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
2045 let rings = self.mpmc.rings.load();
2046 self.mpmc_pop_in(&rings, consumer_id, out)
2047 }
2048
2049 fn mpmc_pop_in(
2050 &self,
2051 rings: &[Arc<SpscRingCore>],
2052 consumer_id: usize,
2053 out: &mut [u8],
2054 ) -> Result<usize, RingError> {
2055 let cursor_line = self.mpmc.consumer_cursors.get(consumer_id)
2056 .ok_or(RingError::PayloadTooLarge)?;
2057 let me = consumer_id as u16;
2058 let n = rings.len();
2059 if n == 0 {
2060 return Err(RingError::Empty);
2061 }
2062 let start = cursor_line.0.load(Ordering::Relaxed) % n;
2063 let mut stuck: Option<(usize, u16)> = None;
2066 for i in 0..n {
2067 let idx = (start + i) % n;
2068 let (owner, pending) = self.directory.ring_owner(idx);
2069 if owner == me {
2070 if pending != OWNER_NONE
2071 && self.directory.apply_handoff(idx, me).is_some()
2072 {
2073 continue; }
2075 if let Ok(bytes) = rings[idx].try_pop(out) {
2076 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
2077 return Ok(bytes);
2078 }
2079 } else if owner == OWNER_NONE {
2080 if self.directory.try_claim_ring(idx, me)
2081 && let Ok(bytes) = rings[idx].try_pop(out)
2082 {
2083 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
2084 return Ok(bytes);
2085 }
2086 } else if stuck.is_none() && rings[idx].approx_len() > 0 {
2087 stuck = Some((idx, owner));
2088 }
2089 }
2090 if let Some((idx, owner)) = stuck {
2093 let probes = cursor_line.1.fetch_add(1, Ordering::Relaxed);
2094 if probes % 1024 == 1023
2095 && self.directory.try_takeover(idx, owner, me)
2096 && let Ok(bytes) = rings[idx].try_pop(out)
2097 {
2098 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
2099 return Ok(bytes);
2100 }
2101 }
2102 Err(RingError::Empty)
2103 }
2104
2105 pub fn pin_current_shape(&self) -> PinnedRing<'_> {
2112 self.ensure_synced();
2113 let captured_gen = self.pin_generation.load(Ordering::Acquire);
2114 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
2115 PinnedRing {
2116 parent: self,
2117 pinned_generation: captured_gen,
2118 shape,
2119 mpsc_rings: self.mpsc.rings.load_full(),
2120 mpmc_rings: self.mpmc.rings.load_full(),
2121 _not_sync: PhantomData,
2122 }
2123 }
2124
2125 pub fn morph_to(&self, new_shape: RingShape) -> Result<(), RingError> {
2152 self.shape_auto.store(false, Ordering::Relaxed);
2153 self.morph_shape(new_shape)
2154 }
2155
2156 pub(crate) fn morph_shape(&self, new_shape: RingShape) -> Result<(), RingError> {
2160 let old_shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
2161 if old_shape == new_shape {
2162 return Ok(());
2163 }
2164
2165 if self.ordering.is_some() && new_shape == RingShape::Vyukov {
2171 return Err(RingError::LayoutMismatch);
2172 }
2173
2174 let prior_stale = self.stale_shape_tag.load(Ordering::Acquire);
2177 if prior_stale != STALE_NONE
2178 && !self.backing_is_empty(RingShape::from_u8(prior_stale))
2179 {
2180 return Err(RingError::StaleBacklog);
2181 }
2182
2183 self.pin_generation.fetch_add(1, Ordering::AcqRel);
2188 self.stale_shape_tag.store(old_shape as u8, Ordering::Release);
2189 self.shape_tag.store(new_shape as u8, Ordering::Release);
2190 Ok(())
2191 }
2192
2193 fn backing_is_empty(&self, shape: RingShape) -> bool {
2195 match shape {
2196 RingShape::Spsc => self.spsc.approx_len() == 0,
2197 RingShape::Mpsc => self.mpsc.rings.load().iter().all(|r| r.approx_len() == 0),
2198 RingShape::Mpmc => self.mpmc.rings.load().iter().all(|r| r.approx_len() == 0),
2199 RingShape::Vyukov => self.vyukov.approx_len() == 0,
2200 }
2201 }
2202
2203 fn stale_shape(&self) -> Option<RingShape> {
2205 let tag = self.stale_shape_tag.load(Ordering::Acquire);
2206 if tag == STALE_NONE {
2207 None
2208 } else {
2209 Some(RingShape::from_u8(tag))
2210 }
2211 }
2212
2213 fn may_walk_stale(shape: RingShape, consumer_id: usize) -> bool {
2218 match shape {
2219 RingShape::Spsc | RingShape::Mpsc => consumer_id == 0,
2220 RingShape::Mpmc | RingShape::Vyukov => true,
2221 }
2222 }
2223
2224 fn shape_pop(
2226 &self,
2227 shape: RingShape,
2228 consumer_id: usize,
2229 out: &mut [u8],
2230 ) -> Result<usize, RingError> {
2231 match shape {
2232 RingShape::Spsc => self.spsc.try_pop(out),
2233 RingShape::Mpsc => self.mpsc_pop(out),
2234 RingShape::Mpmc => self.mpmc_pop(consumer_id, out),
2235 RingShape::Vyukov => self.vyukov.try_pop(out),
2236 }
2237 }
2238}
2239
2240fn with_suffix(base: &std::path::Path, suffix: &str) -> std::path::PathBuf {
2241 let mut s = base.as_os_str().to_owned();
2242 s.push(suffix);
2243 std::path::PathBuf::from(s)
2244}
2245
2246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2248pub enum AdaptiveError {
2249 TooManyProducers,
2254 TooManyConsumers,
2257 GrowthFailed,
2260}
2261
2262pub struct PinnedRing<'a> {
2269 parent: &'a AdaptiveRing,
2270 pinned_generation: u64,
2271 shape: RingShape,
2272 mpsc_rings: Arc<Vec<Arc<SpscRingCore>>>,
2276 mpmc_rings: Arc<Vec<Arc<SpscRingCore>>>,
2277 _not_sync: PhantomData<Cell<()>>,
2278}
2279
2280impl<'a> PinnedRing<'a> {
2281 pub fn shape(&self) -> RingShape { self.shape }
2283
2284 pub fn recv_signal(&self, shape: RingShape) -> &AtomicU64 {
2301 match shape {
2302 RingShape::Spsc => self.parent.spsc.head_signal(),
2303 RingShape::Mpsc => self.mpsc_rings[0].head_signal(),
2304 RingShape::Mpmc => self.mpmc_rings[0].head_signal(),
2305 RingShape::Vyukov => self.parent.vyukov.next_pop_signal(),
2306 }
2307 }
2308
2309 pub fn is_still_valid(&self) -> bool {
2313 self.parent.pin_generation.load(Ordering::Acquire) == self.pinned_generation
2314 }
2315
2316 pub fn spsc_try_push(&self, payload: &[u8]) -> Result<(), RingError> {
2319 self.parent.spsc.try_push(payload)
2320 }
2321
2322 pub fn spsc_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2324 self.parent.spsc.try_pop(out)
2325 }
2326
2327 pub fn mpsc_try_push(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
2329 let ring = self.mpsc_rings.get(producer_id)
2330 .ok_or(RingError::PayloadTooLarge)?;
2331 ring.try_push(payload)
2332 }
2333
2334 pub fn mpsc_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2337 self.parent.mpsc_pop_in(&self.mpsc_rings, out)
2338 }
2339
2340 pub fn mpmc_try_push(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
2342 let ring = self.mpmc_rings.get(producer_id)
2343 .ok_or(RingError::PayloadTooLarge)?;
2344 ring.try_push(payload)
2345 }
2346
2347 pub fn mpmc_try_pop(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
2351 self.parent.mpmc_pop_in(&self.mpmc_rings, consumer_id, out)
2352 }
2353
2354 pub fn vyukov_try_push(&self, payload: &[u8]) -> Result<(), RingError> {
2356 self.parent.vyukov.try_push(payload)
2357 }
2358
2359 pub fn vyukov_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2361 self.parent.vyukov.try_pop(out)
2362 }
2363
2364 pub fn stamped_try_push(
2370 &self,
2371 producer_id: usize,
2372 payload: &[u8],
2373 ) -> Result<(), RingError> {
2374 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2375 self.parent.stamped_send_inner(ord, self.shape, producer_id, payload)
2376 }
2377
2378 pub fn ordered_try_pop(
2386 &self,
2387 consumer_id: usize,
2388 out: &mut [u8],
2389 ) -> Result<usize, RingError> {
2390 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2391 self.parent
2392 .ordered_recv_inner(ord, self.shape, consumer_id, out)
2393 .map(|(n, _stamp)| n)
2394 }
2395
2396 pub fn ordered_try_pop_with_stamp(
2400 &self,
2401 consumer_id: usize,
2402 out: &mut [u8],
2403 ) -> Result<(usize, u64), RingError> {
2404 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2405 self.parent.ordered_recv_inner(ord, self.shape, consumer_id, out)
2406 }
2407}
2408
2409pub struct PeekedSpscSlot<'a> {
2414 inner: crate::spsc_ring::PeekedSlot<'a>,
2415}
2416
2417impl<'a> PeekedSpscSlot<'a> {
2418 pub fn as_slice(&self) -> &[u8] { self.inner.as_slice() }
2419 pub fn len(&self) -> usize { self.inner.len() }
2420 pub fn is_empty(&self) -> bool { self.inner.is_empty() }
2421 pub fn confirm(self) { self.inner.confirm() }
2422}
2423
2424impl<'a> std::ops::Deref for PeekedSpscSlot<'a> {
2425 type Target = [u8];
2426 fn deref(&self) -> &[u8] { &self.inner }
2427}
2428
2429pub const ADAPTIVE_SPSC_PAYLOAD_BYTES: usize = SPSC_PAYLOAD_BYTES;
2432
2433pub const ADAPTIVE_VYUKOV_PAYLOAD_BYTES: usize = PAYLOAD_BYTES;
2436
2437#[derive(Debug, Clone, Copy)]
2445pub struct PolicyObservation {
2446 pub active_producers: usize,
2447 pub active_consumers: usize,
2448 pub current_shape: RingShape,
2449 pub since_last_morph: std::time::Duration,
2450 pub stamped: bool,
2456}
2457
2458pub trait RingShapePolicy: Send + Sync + 'static {
2466 fn decide(&self, observation: &PolicyObservation) -> Option<RingShape>;
2467}
2468
2469pub struct DefaultRingShapePolicy {
2485 pub hysteresis: std::time::Duration,
2486}
2487
2488impl Default for DefaultRingShapePolicy {
2489 fn default() -> Self {
2490 Self { hysteresis: std::time::Duration::from_millis(100) }
2491 }
2492}
2493
2494impl DefaultRingShapePolicy {
2495 pub fn target_shape(producers: usize, consumers: usize) -> Option<RingShape> {
2496 match (producers, consumers) {
2497 (0, _) | (_, 0) => None,
2498 (1, 1) => Some(RingShape::Spsc),
2499 (_, 1) => Some(RingShape::Mpsc),
2500 (_, _) => Some(RingShape::Mpmc),
2501 }
2502 }
2503}
2504
2505impl RingShapePolicy for DefaultRingShapePolicy {
2506 fn decide(&self, obs: &PolicyObservation) -> Option<RingShape> {
2507 if obs.since_last_morph < self.hysteresis {
2508 return None;
2509 }
2510 let target = Self::target_shape(obs.active_producers, obs.active_consumers)?;
2511 if target == obs.current_shape {
2512 None
2513 } else {
2514 Some(target)
2515 }
2516 }
2517}
2518
2519pub struct QosRingShapePolicy {
2532 pub qos: Arc<crate::qos_policy::QosPolicy>,
2533 pub hysteresis: std::time::Duration,
2534}
2535
2536impl QosRingShapePolicy {
2537 pub fn new(qos: Arc<crate::qos_policy::QosPolicy>) -> Self {
2538 Self { qos, hysteresis: std::time::Duration::from_millis(100) }
2539 }
2540}
2541
2542impl RingShapePolicy for QosRingShapePolicy {
2543 fn decide(&self, obs: &PolicyObservation) -> Option<RingShape> {
2544 if obs.since_last_morph < self.hysteresis {
2545 return None;
2546 }
2547 let target = match self.qos.ordering() {
2548 QosOrdering::GlobalFifo if !obs.stamped => Some(RingShape::Vyukov),
2549 _ => DefaultRingShapePolicy::target_shape(
2550 obs.active_producers,
2551 obs.active_consumers,
2552 ),
2553 }?;
2554 if target == obs.current_shape {
2555 None
2556 } else {
2557 Some(target)
2558 }
2559 }
2560}
2561
2562#[derive(Debug, Clone, Copy)]
2565pub struct OrderingPolicyObservation {
2566 pub inversions_per_sec: f64,
2570 pub current_mode: OrderingMode,
2572 pub declared: QosOrdering,
2574 pub active_producers: usize,
2575 pub active_consumers: usize,
2576 pub since_last_change: std::time::Duration,
2578}
2579
2580pub trait OrderingPolicy: Send + Sync + 'static {
2585 fn decide(&self, observation: &OrderingPolicyObservation) -> Option<OrderingMode>;
2586}
2587
2588pub struct DefaultOrderingPolicy {
2603 pub hysteresis: std::time::Duration,
2604 pub auto_order_threshold: Option<f64>,
2605}
2606
2607impl Default for DefaultOrderingPolicy {
2608 fn default() -> Self {
2609 Self {
2610 hysteresis: std::time::Duration::from_millis(100),
2611 auto_order_threshold: None,
2612 }
2613 }
2614}
2615
2616impl OrderingPolicy for DefaultOrderingPolicy {
2617 fn decide(&self, obs: &OrderingPolicyObservation) -> Option<OrderingMode> {
2618 if obs.since_last_change < self.hysteresis {
2619 return None;
2620 }
2621 match obs.declared {
2622 QosOrdering::GlobalFifo => {
2623 if obs.current_mode == OrderingMode::Unordered {
2624 Some(OrderingMode::MergeByStamp)
2625 } else {
2626 None
2627 }
2628 }
2629 QosOrdering::PerProducer => {
2630 match self.auto_order_threshold {
2631 Some(threshold) => {
2632 if obs.current_mode == OrderingMode::Unordered
2633 && obs.inversions_per_sec > threshold
2634 {
2635 Some(OrderingMode::MergeByStamp)
2636 } else {
2637 None
2638 }
2639 }
2640 None => {
2641 if obs.current_mode != OrderingMode::Unordered {
2642 Some(OrderingMode::Unordered)
2643 } else {
2644 None
2645 }
2646 }
2647 }
2648 }
2649 }
2650 }
2651}
2652
2653pub struct AdaptiveRingSidecar {
2661 handle: Option<std::thread::JoinHandle<()>>,
2662 stop: Arc<std::sync::atomic::AtomicBool>,
2663 morphs_triggered: Arc<std::sync::atomic::AtomicU64>,
2664 ordering_flips: Arc<std::sync::atomic::AtomicU64>,
2665}
2666
2667impl AdaptiveRingSidecar {
2668 pub fn spawn<P: RingShapePolicy>(
2671 ring: Arc<AdaptiveRing>,
2672 policy: P,
2673 scan_interval: std::time::Duration,
2674 ) -> Self {
2675 Self::spawn_gated(
2676 ring,
2677 policy,
2678 scan_interval,
2679 crate::policy_gate::GateConfig::default(),
2680 )
2681 }
2682
2683 pub fn spawn_gated<P: RingShapePolicy>(
2687 ring: Arc<AdaptiveRing>,
2688 policy: P,
2689 scan_interval: std::time::Duration,
2690 gate_cfg: crate::policy_gate::GateConfig,
2691 ) -> Self {
2692 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
2693 let morphs_triggered = Arc::new(std::sync::atomic::AtomicU64::new(0));
2694
2695 let stop_c = stop.clone();
2696 let morphs_c = morphs_triggered.clone();
2697 let handle = std::thread::spawn(move || {
2698 let mut last_morph = std::time::Instant::now();
2699 let mut gate = crate::policy_gate::ConfidenceGate::new(gate_cfg);
2700 while !stop_c.load(Ordering::Acquire) {
2701 let obs = PolicyObservation {
2702 active_producers: ring.active_producers(),
2703 active_consumers: ring.active_consumers(),
2704 current_shape: ring.current_shape(),
2705 since_last_morph: last_morph.elapsed(),
2706 stamped: ring.is_stamped(),
2707 };
2708 if let Some(new_shape) = gate
2712 .observe(policy.decide(&obs).map(|s| ring.contract_filtered_shape(s)))
2713 && ring.morph_shape(new_shape).is_ok()
2714 {
2715 last_morph = std::time::Instant::now();
2716 morphs_c.fetch_add(1, Ordering::Relaxed);
2717 }
2718 std::thread::sleep(scan_interval);
2719 }
2720 });
2721
2722 Self {
2723 handle: Some(handle),
2724 stop,
2725 morphs_triggered,
2726 ordering_flips: Arc::new(std::sync::atomic::AtomicU64::new(0)),
2727 }
2728 }
2729
2730 pub fn spawn_with_qos<P: RingShapePolicy, O: OrderingPolicy>(
2759 ring: Arc<AdaptiveRing>,
2760 shape_policy: P,
2761 ordering_policy: O,
2762 qos: Arc<crate::qos_policy::QosPolicy>,
2763 scan_interval: std::time::Duration,
2764 ) -> Self {
2765 Self::spawn_with_qos_core(
2766 ring,
2767 shape_policy,
2768 ordering_policy,
2769 qos,
2770 scan_interval,
2771 crate::policy_gate::GateConfig::default(),
2772 crate::policy_gate::GateConfig::enabled_with_arity(2),
2773 )
2774 }
2775
2776 pub fn spawn_with_qos_gated<P: RingShapePolicy, O: OrderingPolicy>(
2786 ring: Arc<AdaptiveRing>,
2787 shape_policy: P,
2788 ordering_policy: O,
2789 qos: Arc<crate::qos_policy::QosPolicy>,
2790 scan_interval: std::time::Duration,
2791 gate_cfg: crate::policy_gate::GateConfig,
2792 ) -> Self {
2793 Self::spawn_with_qos_core(
2794 ring,
2795 shape_policy,
2796 ordering_policy,
2797 qos,
2798 scan_interval,
2799 gate_cfg,
2800 gate_cfg,
2801 )
2802 }
2803
2804 fn spawn_with_qos_core<P: RingShapePolicy, O: OrderingPolicy>(
2805 ring: Arc<AdaptiveRing>,
2806 shape_policy: P,
2807 ordering_policy: O,
2808 qos: Arc<crate::qos_policy::QosPolicy>,
2809 scan_interval: std::time::Duration,
2810 shape_gate_cfg: crate::policy_gate::GateConfig,
2811 order_gate_cfg: crate::policy_gate::GateConfig,
2812 ) -> Self {
2813 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
2814 let morphs_triggered = Arc::new(std::sync::atomic::AtomicU64::new(0));
2815 let ordering_flips = Arc::new(std::sync::atomic::AtomicU64::new(0));
2816
2817 let stop_c = stop.clone();
2818 let morphs_c = morphs_triggered.clone();
2819 let flips_c = ordering_flips.clone();
2820 let handle = std::thread::spawn(move || {
2821 let mut last_morph = std::time::Instant::now();
2822 let mut last_flip = std::time::Instant::now();
2823 let mut last_inversions = ring.inversions();
2824 let mut last_scan = std::time::Instant::now();
2825 let mut shape_gate = crate::policy_gate::ConfidenceGate::new(shape_gate_cfg);
2826 let mut order_gate = crate::policy_gate::ConfidenceGate::new(order_gate_cfg);
2827 let mut last_peers = (0usize, 0usize);
2828 let mut first_scan = true;
2829 while !stop_c.load(Ordering::Acquire) {
2830 let obs = PolicyObservation {
2831 active_producers: ring.active_producers(),
2832 active_consumers: ring.active_consumers(),
2833 current_shape: ring.current_shape(),
2834 since_last_morph: last_morph.elapsed(),
2835 stamped: ring.is_stamped(),
2836 };
2837 let peers = (obs.active_producers, obs.active_consumers);
2838 if !first_scan && peers != last_peers {
2839 shape_gate.shock();
2840 order_gate.shock();
2841 }
2842 last_peers = peers;
2843 first_scan = false;
2844
2845 if let Some(new_shape) = shape_gate
2846 .observe(shape_policy.decide(&obs).map(|s| ring.contract_filtered_shape(s)))
2847 && ring.morph_shape(new_shape).is_ok()
2848 {
2849 last_morph = std::time::Instant::now();
2850 morphs_c.fetch_add(1, Ordering::Relaxed);
2851 }
2852
2853 if let Some(current_mode) = ring.ordering_mode() {
2854 ring.tick_drainer_epoch().ok();
2855
2856 let now_inversions = ring.inversions();
2857 let elapsed = last_scan.elapsed().as_secs_f64().max(1e-9);
2858 let rate = now_inversions
2859 .saturating_sub(last_inversions) as f64 / elapsed;
2860 last_inversions = now_inversions;
2861 last_scan = std::time::Instant::now();
2862
2863 let declared = qos.ordering();
2864 let ord_obs = OrderingPolicyObservation {
2865 inversions_per_sec: rate,
2866 current_mode,
2867 declared,
2868 active_producers: obs.active_producers,
2869 active_consumers: obs.active_consumers,
2870 since_last_change: last_flip.elapsed(),
2871 };
2872 let decision = ordering_policy
2873 .decide(&ord_obs)
2874 .filter(|m| *m != current_mode);
2875
2876 let is_auto_arm = declared == QosOrdering::PerProducer
2885 && decision == Some(OrderingMode::MergeByStamp);
2886 let gated = if is_auto_arm {
2887 order_gate.observe(decision)
2888 } else {
2889 decision
2890 };
2891 if let Some(new_mode) = gated
2892 && ring.set_ordering_mode(new_mode).is_ok()
2893 {
2894 last_flip = std::time::Instant::now();
2895 flips_c.fetch_add(1, Ordering::Relaxed);
2896 }
2897 }
2898 std::thread::sleep(scan_interval);
2899 }
2900 });
2901
2902 Self {
2903 handle: Some(handle),
2904 stop,
2905 morphs_triggered,
2906 ordering_flips,
2907 }
2908 }
2909
2910 pub fn morphs_triggered(&self) -> u64 {
2913 self.morphs_triggered.load(Ordering::Acquire)
2914 }
2915
2916 pub fn ordering_flips(&self) -> u64 {
2919 self.ordering_flips.load(Ordering::Acquire)
2920 }
2921
2922 pub fn shutdown(mut self) {
2924 self.stop.store(true, Ordering::Release);
2925 if let Some(h) = self.handle.take() {
2926 h.join().ok();
2927 }
2928 }
2929}
2930
2931impl Drop for AdaptiveRingSidecar {
2932 fn drop(&mut self) {
2933 self.stop.store(true, Ordering::Release);
2934 if let Some(h) = self.handle.take() {
2935 h.join().ok();
2936 }
2937 }
2938}
2939
2940#[cfg(test)]
2941mod tests {
2942 use super::*;
2943
2944 #[test]
2945 fn create_starts_in_spsc_shape() {
2946 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2947 assert_eq!(ring.current_shape(), RingShape::Spsc);
2948 assert_eq!(ring.pin_generation(), 0);
2949 }
2950
2951 #[test]
2952 fn adaptive_dispatch_round_trip_each_shape() {
2953 for shape in [RingShape::Spsc, RingShape::Mpsc, RingShape::Mpmc, RingShape::Vyukov] {
2954 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2955 ring.shape_tag.store(shape as u8, Ordering::Release);
2956 let payload = [0xCDu8; 56];
2960 ring.try_send(0, &payload).unwrap();
2961 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
2962 let n = ring.try_recv(0, &mut out).unwrap();
2963 assert!(n > 0, "shape {:?} delivered zero bytes", shape);
2964 assert_eq!(&out[..payload.len()], &payload[..]);
2965 }
2966 }
2967
2968 #[test]
2969 fn pin_captures_shape_and_generation() {
2970 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2971 let pinned = ring.pin_current_shape();
2972 assert_eq!(pinned.shape(), RingShape::Spsc);
2973 assert!(pinned.is_still_valid());
2974 let pinned = ring.pin_current_shape();
2976 assert!(pinned.is_still_valid());
2977 }
2978
2979 #[test]
2980 fn morph_invalidates_outstanding_pin() {
2981 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2982 let pinned = ring.pin_current_shape();
2983 assert!(pinned.is_still_valid());
2984
2985 ring.morph_to(RingShape::Mpsc).unwrap();
2986 assert!(!pinned.is_still_valid(),
2987 "pin must invalidate after morph_to");
2988 assert_eq!(ring.current_shape(), RingShape::Mpsc);
2989 }
2990
2991 #[test]
2992 fn morph_to_same_shape_is_no_op() {
2993 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2994 let gen_before = ring.pin_generation();
2995 ring.morph_to(RingShape::Spsc).unwrap();
2996 let gen_after = ring.pin_generation();
2997 assert_eq!(gen_before, gen_after,
2998 "morph_to(same shape) must not bump pin_generation");
2999 }
3000
3001 #[test]
3002 fn morph_preserves_in_flight_items_via_stale_walk() {
3003 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3004
3005 for i in 0..3u32 {
3007 let mut buf = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3008 buf[..4].copy_from_slice(&i.to_le_bytes());
3009 ring.try_send(0, &buf).unwrap();
3010 }
3011
3012 ring.morph_to(RingShape::Mpsc).unwrap();
3015 assert_eq!(ring.current_shape(), RingShape::Mpsc);
3016 assert_eq!(ring.approx_len(), 3,
3017 "the stale backlog must stay visible through approx_len");
3018
3019 let mut buf = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3022 buf[..4].copy_from_slice(&99u32.to_le_bytes());
3023 ring.try_send(0, &buf).unwrap();
3024
3025 let mut seen = Vec::new();
3026 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3027 while ring.try_recv(0, &mut out).is_ok() {
3028 seen.push(u32::from_le_bytes(out[..4].try_into().unwrap()));
3029 }
3030 assert_eq!(seen, vec![0u32, 1, 2, 99],
3031 "stale backlog must drain before post-morph items");
3032 assert!(ring.is_empty());
3033 }
3034
3035 #[test]
3036 fn frame_round_trip_all_shapes() {
3037 use crate::frame_ring::FrameClass;
3038 for shape in [RingShape::Spsc, RingShape::Mpsc, RingShape::Mpmc, RingShape::Vyukov] {
3039 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3040 if shape != RingShape::Spsc {
3041 ring.morph_to(shape).unwrap();
3042 }
3043 let small = b"small inline payload".to_vec();
3044 let large = vec![0xABu8; 4000];
3045 assert_eq!(ring.send_frame(0, &small).unwrap(), FrameClass::Inline,
3046 "{shape:?} small should inline");
3047 assert_eq!(ring.send_frame(0, &large).unwrap(), FrameClass::Offset,
3048 "{shape:?} large should offset");
3049 let mut out = Vec::new();
3050 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Inline);
3051 assert_eq!(out, small, "{shape:?} small round-trip");
3052 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Offset);
3053 assert_eq!(out, large, "{shape:?} large round-trip");
3054 }
3055 }
3056
3057 #[test]
3058 fn frame_survives_morph() {
3059 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3060 ring.send_frame(0, b"pre-morph small").unwrap();
3062 ring.send_frame(0, &vec![1u8; 3000]).unwrap();
3063 ring.morph_to(RingShape::Mpsc).unwrap();
3067 ring.send_frame(0, b"post-morph small").unwrap();
3068 ring.send_frame(0, &vec![2u8; 3000]).unwrap();
3069 let mut out = Vec::new();
3070 ring.recv_frame(0, &mut out).unwrap();
3071 assert_eq!(out, b"pre-morph small");
3072 ring.recv_frame(0, &mut out).unwrap();
3073 assert_eq!(out, vec![1u8; 3000]);
3074 ring.recv_frame(0, &mut out).unwrap();
3075 assert_eq!(out, b"post-morph small");
3076 ring.recv_frame(0, &mut out).unwrap();
3077 assert_eq!(out, vec![2u8; 3000]);
3078 }
3079
3080 #[test]
3081 fn frame_override_and_limits() {
3082 use crate::frame_ring::{FrameClass, LayoutHint};
3083 let ring = AdaptiveRing::create_anon(2, 2, 64).unwrap();
3084 let mut out = Vec::new();
3085 assert_eq!(ring.send_frame_as(0, b"tiny", LayoutHint::ForceOffset).unwrap(),
3087 FrameClass::Offset);
3088 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Offset);
3089 assert_eq!(out, b"tiny");
3090 let big = vec![0u8; AdaptiveRing::FRAME_INLINE_BUDGET + 1];
3092 assert_eq!(ring.send_frame_as(0, &big, LayoutHint::ForceInline).unwrap_err(),
3093 RingError::PayloadTooLarge);
3094 let at = vec![7u8; AdaptiveRing::FRAME_INLINE_BUDGET];
3096 assert_eq!(ring.send_frame(0, &at).unwrap(), FrameClass::Inline);
3097 ring.recv_frame(0, &mut out).unwrap();
3098 assert_eq!(out, at);
3099 }
3100
3101 #[test]
3102 fn frame_rejected_on_stamped_ring() {
3103 let ring = AdaptiveRing::create_anon(2, 2, 64)
3106 .unwrap()
3107 .with_ordering_stamps()
3108 .unwrap();
3109 assert_eq!(ring.send_frame(0, b"x").unwrap_err(), RingError::LayoutMismatch);
3110 let mut out = Vec::new();
3111 assert_eq!(ring.recv_frame(0, &mut out).unwrap_err(), RingError::LayoutMismatch);
3112 }
3113
3114 #[test]
3115 fn frame_vyukov_two_thread_mixed_size() {
3116 use std::sync::Arc;
3117 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtOrd};
3118 use std::thread;
3119
3120 const PER: u32 = 5_000;
3121 const PRODUCERS: u32 = 2;
3122 let total = (PER * PRODUCERS) as usize;
3123
3124 let ring = Arc::new(AdaptiveRing::create_anon(2, 2, 256).unwrap());
3130 ring.morph_to(RingShape::Vyukov).unwrap();
3131 let seen: Arc<Vec<AtomicBool>> =
3134 Arc::new((0..total).map(|_| AtomicBool::new(false)).collect());
3135 let received = Arc::new(AtomicUsize::new(0));
3136
3137 let mut prods = Vec::new();
3138 for p in 0..PRODUCERS {
3139 let ring = ring.clone();
3140 prods.push(thread::spawn(move || {
3141 for i in 0..PER {
3142 let id = p * PER + i;
3143 let len = (id as usize % 200) + 4; let mut payload = vec![0u8; len];
3145 payload[0..4].copy_from_slice(&id.to_le_bytes());
3146 for (k, b) in payload.iter_mut().enumerate().skip(4) {
3147 *b = id.wrapping_add(k as u32) as u8;
3148 }
3149 while ring.send_frame(p as usize, &payload).is_err() {
3150 std::hint::spin_loop();
3151 }
3152 }
3153 }));
3154 }
3155
3156 let mut cons = Vec::new();
3157 for c in 0..2usize {
3158 let ring = ring.clone();
3159 let seen = seen.clone();
3160 let received = received.clone();
3161 cons.push(thread::spawn(move || {
3162 let mut out = Vec::new();
3163 while received.load(AtOrd::Acquire) < total {
3164 if ring.recv_frame(c, &mut out).is_ok() {
3165 let id = u32::from_le_bytes(out[0..4].try_into().unwrap());
3166 let len = (id as usize % 200) + 4;
3167 assert_eq!(out.len(), len, "id {id} length");
3168 for (k, b) in out.iter().enumerate().skip(4) {
3169 assert_eq!(*b, id.wrapping_add(k as u32) as u8,
3170 "id {id} byte {k}");
3171 }
3172 let already = seen[id as usize].swap(true, AtOrd::AcqRel);
3173 assert!(!already, "id {id} delivered twice");
3174 received.fetch_add(1, AtOrd::AcqRel);
3175 } else {
3176 std::hint::spin_loop();
3177 }
3178 }
3179 }));
3180 }
3181
3182 for p in prods { p.join().unwrap(); }
3183 for c in cons { c.join().unwrap(); }
3184 assert_eq!(received.load(AtOrd::Acquire), total);
3185 assert!(seen.iter().all(|b| b.load(AtOrd::Acquire)),
3186 "every id delivered exactly once");
3187 }
3188
3189 #[test]
3190 fn second_morph_blocked_until_stale_backlog_drains() {
3191 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3192 ring.try_send(0, &[7u8; 8]).unwrap();
3193 ring.morph_to(RingShape::Mpsc).unwrap();
3194
3195 assert_eq!(ring.morph_to(RingShape::Mpmc).unwrap_err(),
3197 RingError::StaleBacklog);
3198
3199 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3200 ring.try_recv(0, &mut out).unwrap();
3201 ring.morph_to(RingShape::Mpmc).unwrap();
3203 assert_eq!(ring.current_shape(), RingShape::Mpmc);
3204 }
3205
3206 #[test]
3207 fn register_producer_grows_past_hint_and_recycles_slots() {
3208 let ring = AdaptiveRing::create_anon(3, 1, 64).unwrap();
3209 let id0 = ring.register_producer().unwrap();
3210 let id1 = ring.register_producer().unwrap();
3211 let id2 = ring.register_producer().unwrap();
3212 assert_eq!((id0, id1, id2), (0, 1, 2));
3213
3214 let id3 = ring.register_producer().unwrap();
3217 assert_eq!(id3, 3);
3218 assert_eq!(ring.published_producers(), 4);
3219 ring.try_send(id3, &7u64.to_le_bytes()).unwrap();
3220 let mut out = [0u8; 64];
3221 let _c = ring.register_consumer().unwrap();
3225 let n = ring.try_recv(0, &mut out).unwrap();
3226 assert!(n >= 8, "popped record too short: {n}");
3227 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 7);
3228
3229 ring.unregister_producer(id1);
3232 assert_eq!(ring.register_producer().unwrap(), 1);
3233
3234 let pinned = AdaptiveRing::create_anon(2, 1, 64)
3236 .unwrap()
3237 .with_contract(crate::ring_contract::RingContract::from_counts(2, 1));
3238 pinned.register_producer().unwrap();
3239 pinned.register_producer().unwrap();
3240 assert_eq!(pinned.register_producer().unwrap_err(),
3241 AdaptiveError::TooManyProducers);
3242 }
3243
3244 #[test]
3245 fn default_policy_target_shape_per_peer_count() {
3246 assert_eq!(DefaultRingShapePolicy::target_shape(0, 1), None);
3248 assert_eq!(DefaultRingShapePolicy::target_shape(1, 0), None);
3249 assert_eq!(DefaultRingShapePolicy::target_shape(0, 0), None);
3250 assert_eq!(DefaultRingShapePolicy::target_shape(1, 1), Some(RingShape::Spsc));
3252 assert_eq!(DefaultRingShapePolicy::target_shape(2, 1), Some(RingShape::Mpsc));
3254 assert_eq!(DefaultRingShapePolicy::target_shape(8, 1), Some(RingShape::Mpsc));
3255 assert_eq!(DefaultRingShapePolicy::target_shape(1, 2), Some(RingShape::Mpmc));
3257 assert_eq!(DefaultRingShapePolicy::target_shape(4, 4), Some(RingShape::Mpmc));
3258 }
3259
3260 #[test]
3261 fn default_policy_returns_none_during_hysteresis() {
3262 let policy = DefaultRingShapePolicy {
3263 hysteresis: std::time::Duration::from_secs(1),
3264 };
3265 let obs = PolicyObservation {
3266 active_producers: 4,
3267 active_consumers: 4,
3268 current_shape: RingShape::Spsc,
3269 since_last_morph: std::time::Duration::from_millis(50),
3270 stamped: false,
3271 };
3272 assert_eq!(policy.decide(&obs), None);
3274 }
3275
3276 #[test]
3277 fn default_policy_returns_target_after_hysteresis() {
3278 let policy = DefaultRingShapePolicy {
3279 hysteresis: std::time::Duration::from_millis(10),
3280 };
3281 let obs = PolicyObservation {
3282 active_producers: 4,
3283 active_consumers: 4,
3284 current_shape: RingShape::Spsc,
3285 since_last_morph: std::time::Duration::from_secs(1),
3286 stamped: false,
3287 };
3288 assert_eq!(policy.decide(&obs), Some(RingShape::Mpmc));
3289 }
3290
3291 #[test]
3292 fn default_policy_returns_none_when_target_equals_current() {
3293 let policy = DefaultRingShapePolicy::default();
3294 let obs = PolicyObservation {
3295 active_producers: 1,
3296 active_consumers: 1,
3297 current_shape: RingShape::Spsc,
3298 since_last_morph: std::time::Duration::from_secs(1),
3299 stamped: false,
3300 };
3301 assert_eq!(policy.decide(&obs), None);
3302 }
3303
3304 #[test]
3305 fn shape_tracks_peer_counts_and_sidecar_stays_idle() {
3306 let ring = Arc::new(AdaptiveRing::create_anon(4, 4, 64).unwrap());
3307 let policy = DefaultRingShapePolicy {
3308 hysteresis: std::time::Duration::from_millis(5),
3309 };
3310 let sidecar = AdaptiveRingSidecar::spawn(
3311 ring.clone(),
3312 policy,
3313 std::time::Duration::from_millis(10),
3314 );
3315
3316 let _p0 = ring.register_producer().unwrap();
3318 let _c0 = ring.register_consumer().unwrap();
3319 assert_eq!(ring.current_shape(), RingShape::Spsc);
3320
3321 let _p1 = ring.register_producer().unwrap();
3324 assert_eq!(ring.current_shape(), RingShape::Mpsc,
3325 "2nd producer registration must morph to MPSC immediately");
3326
3327 let _c1 = ring.register_consumer().unwrap();
3328 assert_eq!(ring.current_shape(), RingShape::Mpmc,
3329 "2nd consumer registration must morph to MPMC immediately");
3330
3331 std::thread::sleep(std::time::Duration::from_millis(60));
3334 assert_eq!(sidecar.morphs_triggered(), 0,
3335 "register-path morphs left the sidecar nothing to do");
3336
3337 ring.unregister_consumer(1);
3340 ring.unregister_producer(1);
3341 assert_eq!(ring.current_shape(), RingShape::Spsc,
3342 "unregister must morph back down automatically");
3343
3344 sidecar.shutdown();
3345 }
3346
3347 #[test]
3348 fn pinned_native_paths_match_adaptive_paths() {
3349 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3350 let pinned = ring.pin_current_shape();
3351 assert_eq!(pinned.shape(), RingShape::Spsc);
3352
3353 let payload = [0xAAu8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3354 pinned.spsc_try_push(&payload).unwrap();
3355 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3356 let n = pinned.spsc_try_pop(&mut out).unwrap();
3357 assert_eq!(n, ADAPTIVE_SPSC_PAYLOAD_BYTES);
3358 assert_eq!(out, payload);
3359 assert!(pinned.is_still_valid());
3360 }
3361
3362 fn stamped_anon(
3367 max_producers: usize,
3368 max_consumers: usize,
3369 kind: StampKind,
3370 ) -> AdaptiveRing {
3371 AdaptiveRing::create_anon(max_producers, max_consumers, 64)
3372 .unwrap()
3373 .with_ordering_stamps_kind(kind)
3374 .unwrap()
3375 }
3376
3377 #[test]
3378 fn stamped_round_trip_strips_stamp_and_caps_payload() {
3379 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3380 assert!(ring.is_stamped());
3381 assert_eq!(ring.stamp_kind(), Some(StampKind::SharedCounter));
3382 assert_eq!(ring.ordering_mode(), Some(OrderingMode::Unordered));
3383
3384 let too_big = [0u8; STAMPED_PAYLOAD_BYTES + 1];
3386 assert_eq!(ring.try_send(0, &too_big).unwrap_err(),
3387 RingError::PayloadTooLarge);
3388
3389 let payload = [0xC3u8; STAMPED_PAYLOAD_BYTES];
3390 ring.try_send(0, &payload).unwrap();
3391 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3392 let n = ring.try_recv(0, &mut out).unwrap();
3393 assert_eq!(n, STAMPED_PAYLOAD_BYTES,
3394 "stamped recv returns payload bytes only");
3395 assert_eq!(out, payload, "the stamp must be stripped, not leak into the payload");
3396 }
3397
3398 #[test]
3399 fn unstamped_ring_rejects_ordering_calls() {
3400 let ring = AdaptiveRing::create_anon(2, 1, 64).unwrap();
3401 assert!(!ring.is_stamped());
3402 assert_eq!(ring.inversions(), 0);
3403 assert_eq!(ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap_err(),
3404 RingError::NotStamped);
3405 assert_eq!(ring.refresh_watermark(0).unwrap_err(), RingError::NotStamped);
3406 let pinned = ring.pin_current_shape();
3407 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3408 assert_eq!(pinned.ordered_try_pop(0, &mut out).unwrap_err(),
3409 RingError::NotStamped);
3410 assert_eq!(pinned.stamped_try_push(0, &[1u8; 8]).unwrap_err(),
3411 RingError::NotStamped);
3412 }
3413
3414 #[test]
3415 fn stamped_ring_rejects_vyukov_morph_and_vyukov_ring_rejects_stamps() {
3416 let ring = stamped_anon(2, 1, StampKind::Monotonic);
3417 assert_eq!(ring.morph_to(RingShape::Vyukov).unwrap_err(),
3418 RingError::LayoutMismatch);
3419
3420 let vyukov_first = AdaptiveRing::create_anon(2, 1, 64).unwrap();
3421 vyukov_first.morph_to(RingShape::Vyukov).unwrap();
3422 assert!(matches!(
3423 vyukov_first.with_ordering_stamps(),
3424 Err(RingError::LayoutMismatch)
3425 ));
3426 }
3427
3428 #[test]
3429 fn synthetic_interleave_fires_inversion_counter() {
3430 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3431 ring.morph_to(RingShape::Mpsc).unwrap();
3432
3433 ring.try_send(1, &1u64.to_le_bytes()).unwrap();
3438 ring.try_send(0, &2u64.to_le_bytes()).unwrap();
3439
3440 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3441 ring.try_recv(0, &mut out).unwrap();
3442 assert_eq!(ring.inversions(), 0, "first pop has no predecessor");
3443 ring.try_recv(0, &mut out).unwrap();
3444 assert_eq!(ring.inversions(), 1,
3445 "older-after-newer must count as one inversion");
3446 }
3447
3448 #[test]
3449 fn merge_mode_delivers_global_stamp_order() {
3450 let ring = stamped_anon(4, 1, StampKind::SharedCounter);
3451 ring.morph_to(RingShape::Mpsc).unwrap();
3452 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3453
3454 for i in 0..32u64 {
3457 let producer = (i % 4) as usize;
3458 ring.try_send(producer, &i.to_le_bytes()).unwrap();
3459 }
3460
3461 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3462 for expected in 0..32u64 {
3463 let n = ring.try_recv(0, &mut out).unwrap();
3464 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3465 let got = u64::from_le_bytes(out[..8].try_into().unwrap());
3466 assert_eq!(got, expected,
3467 "merge pop must deliver global push order");
3468 }
3469 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty);
3470 assert_eq!(ring.inversions(), 0,
3471 "merged pops must observe zero inversions");
3472 }
3473
3474 #[test]
3475 fn flag_flip_orders_backlog_retroactively_without_loss() {
3476 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3477 ring.morph_to(RingShape::Mpsc).unwrap();
3478
3479 for i in 0..16u64 {
3482 let producer = ((i + 1) % 2) as usize;
3483 ring.try_send(producer, &i.to_le_bytes()).unwrap();
3484 }
3485
3486 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3489 let mut popped = Vec::new();
3490 for _ in 0..2 {
3491 ring.try_recv(0, &mut out).unwrap();
3492 popped.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3493 }
3494 let inversions_before_flip = ring.inversions();
3495 assert!(inversions_before_flip > 0,
3496 "unordered interleave must show inversions before the flip");
3497
3498 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3501 let mut merged = Vec::new();
3502 while let Ok(_n) = ring.try_recv(0, &mut out) {
3503 merged.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3504 }
3505
3506 let mut all = popped.clone();
3508 all.extend(&merged);
3509 all.sort_unstable();
3510 assert_eq!(all, (0..16u64).collect::<Vec<_>>(),
3511 "no item may be lost across the mode flip");
3512 for pair in merged.windows(2) {
3515 assert!(pair[0] < pair[1],
3516 "post-flip pops must be globally ordered: {merged:?}");
3517 }
3518 assert_eq!(ring.inversions(), inversions_before_flip,
3519 "the flip itself and merged pops must add zero inversions");
3520 }
3521
3522 #[test]
3523 fn merge_strict_blocks_on_in_flight_stamp_then_releases() {
3524 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3525 ring.morph_to(RingShape::Mpsc).unwrap();
3526 ring.set_ordering_mode(OrderingMode::MergeStrict).unwrap();
3527 let region = ring.ordering_region().unwrap();
3528
3529 let stalled_stamp = region.next_stamp(1);
3533 ring.try_send(0, &42u64.to_le_bytes()).unwrap();
3535
3536 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3539 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3540 "strict merge must hold the candidate while a smaller stamp is in flight");
3541
3542 region.publish_watermark(1, stalled_stamp);
3548 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3549 "strict watermark gate must hold until the silent producer vouches");
3550 ring.refresh_watermark(1).unwrap();
3553 let n = ring.try_recv(0, &mut out).unwrap();
3554 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3555 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 42);
3556 }
3557
3558 #[test]
3559 fn merge_by_stamp_in_flight_gate_blocks_descheduled_producer() {
3560 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3565 ring.morph_to(RingShape::Mpsc).unwrap();
3566 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3567 let region = ring.ordering_region().unwrap();
3568
3569 let stalled = region.next_stamp(1); ring.try_send(0, &9u64.to_le_bytes()).unwrap();
3571
3572 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3573 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3574 "MergeByStamp must gate on in-flight stamps too");
3575 region.publish_watermark(1, stalled); let n = ring.try_recv(0, &mut out).unwrap();
3577 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3578 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 9);
3579 }
3580
3581 #[test]
3582 fn merge_strict_retired_producer_stops_gating() {
3583 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3584 ring.morph_to(RingShape::Mpsc).unwrap();
3585 ring.set_ordering_mode(OrderingMode::MergeStrict).unwrap();
3586
3587 ring.try_send(1, &1u64.to_le_bytes()).unwrap();
3591 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3592 ring.try_recv(0, &mut out).unwrap();
3593
3594 ring.try_send(0, &2u64.to_le_bytes()).unwrap();
3596 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3597 "strict couples release to the slowest in-use producer");
3598
3599 ring.retire_producer(1).unwrap();
3602 let n = ring.try_recv(0, &mut out).unwrap();
3603 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3604 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 2);
3605 }
3606
3607 #[test]
3608 fn multi_consumer_merge_enforces_single_drainer() {
3609 let ring = stamped_anon(2, 2, StampKind::SharedCounter);
3610 ring.morph_to(RingShape::Mpmc).unwrap();
3611 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3612
3613 for i in 0..4u64 {
3614 ring.try_send((i % 2) as usize, &i.to_le_bytes()).unwrap();
3615 }
3616
3617 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3619 ring.try_recv(0, &mut out).unwrap();
3620 assert_eq!(ring.try_recv(1, &mut out).unwrap_err(),
3622 RingError::NotDrainer);
3623 assert!(ring.release_drainer(0).unwrap());
3625 let n = ring.try_recv(1, &mut out).unwrap();
3626 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3627 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 1,
3628 "the new drainer continues in global stamp order");
3629 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(),
3631 RingError::NotDrainer);
3632 }
3633
3634 #[test]
3635 fn mode_flip_does_not_invalidate_pins() {
3636 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3637 ring.morph_to(RingShape::Mpsc).unwrap();
3638 let pinned = ring.pin_current_shape();
3639 assert!(pinned.is_still_valid());
3640
3641 pinned.stamped_try_push(1, &1u64.to_le_bytes()).unwrap();
3643 pinned.stamped_try_push(0, &2u64.to_le_bytes()).unwrap();
3644
3645 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3649 assert!(pinned.is_still_valid(),
3650 "ordering-mode flips must not invalidate pins");
3651
3652 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3653 pinned.ordered_try_pop(0, &mut out).unwrap();
3654 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 1,
3655 "pinned merge pop must deliver stamp order");
3656 pinned.ordered_try_pop(0, &mut out).unwrap();
3657 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 2);
3658 }
3659
3660 #[test]
3661 fn stamped_items_survive_shape_morphs() {
3662 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3663 for i in 0..3u64 {
3664 ring.try_send(0, &i.to_le_bytes()).unwrap();
3665 }
3666 ring.morph_to(RingShape::Mpsc).unwrap();
3667 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3668 let mut got = Vec::new();
3669 while ring.try_recv(0, &mut out).is_ok() {
3670 got.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3671 }
3672 got.sort_unstable();
3673 assert_eq!(got, vec![0, 1, 2],
3674 "stamped slots must transfer intact across shape morphs");
3675 }
3676
3677 #[test]
3678 fn stamped_file_ring_open_adopts_creator_kind_and_shares_mode() {
3679 let mut prefix = std::env::temp_dir();
3680 prefix.push(format!(
3681 "subetha_stamped_open_{}_{}",
3682 std::process::id(),
3683 std::time::SystemTime::now()
3684 .duration_since(std::time::UNIX_EPOCH)
3685 .map(|d| d.as_nanos()).unwrap_or(0),
3686 ));
3687
3688 let creator = AdaptiveRing::create(&prefix, 2, 1, 64)
3689 .unwrap()
3690 .with_ordering_stamps_kind(StampKind::SharedCounter)
3691 .unwrap();
3692 creator.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3693 creator.try_send(0, &7u64.to_le_bytes()).unwrap();
3694
3695 let opener = AdaptiveRing::open(&prefix, 2, 1, 64)
3696 .unwrap()
3697 .with_ordering_stamps()
3698 .unwrap();
3699 assert_eq!(opener.stamp_kind(), Some(StampKind::SharedCounter),
3700 "opener must adopt the creator's stamp kind");
3701 assert_eq!(opener.ordering_mode(), Some(OrderingMode::MergeByStamp),
3702 "the mode flag must be cross-process (region-resident)");
3703 assert!(matches!(
3705 AdaptiveRing::open(&prefix, 2, 1, 64)
3706 .unwrap()
3707 .with_ordering_stamps_kind(StampKind::Monotonic),
3708 Err(RingError::LayoutMismatch)
3709 ));
3710
3711 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3712 let n = opener.try_recv(0, &mut out).unwrap();
3713 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3714 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 7);
3715
3716 drop(creator);
3717 drop(opener);
3718 for suffix in [".spsc.bin", ".mpsc.0.bin", ".mpsc.1.bin",
3719 ".mpmc.0.bin", ".mpmc.1.bin", ".vyukov.bin",
3720 ".ordering.bin"] {
3721 let mut p = prefix.as_os_str().to_owned();
3722 p.push(suffix);
3723 std::fs::remove_file(std::path::PathBuf::from(p)).ok();
3724 }
3725 }
3726
3727 #[test]
3728 fn offset_frame_crosses_a_shared_file_backing() {
3729 let mut prefix = std::env::temp_dir();
3736 prefix.push(format!(
3737 "subetha_offset_frame_{}_{}",
3738 std::process::id(),
3739 std::time::SystemTime::now()
3740 .duration_since(std::time::UNIX_EPOCH)
3741 .map(|d| d.as_nanos()).unwrap_or(0),
3742 ));
3743
3744 let creator = AdaptiveRing::create(&prefix, 1, 1, 64).unwrap();
3745 let opener = AdaptiveRing::open(&prefix, 1, 1, 64).unwrap();
3746
3747 let small = vec![0xABu8; 20]; let large = vec![0xCDu8; 5000]; assert_eq!(creator.send_frame(0, &small).unwrap(), FrameClass::Inline);
3750 assert_eq!(creator.send_frame(0, &large).unwrap(), FrameClass::Offset);
3751
3752 let mut out = Vec::new();
3753 assert_eq!(opener.recv_frame(0, &mut out).unwrap(), FrameClass::Inline);
3754 assert_eq!(out, small, "inline frame must cross the boundary");
3755 assert_eq!(opener.recv_frame(0, &mut out).unwrap(), FrameClass::Offset,
3756 "offset frame must cross via the shared payload region");
3757 assert_eq!(out, large,
3758 "offset payload must be byte-exact across the boundary");
3759
3760 drop(creator);
3761 drop(opener);
3762 for suffix in [".spsc.bin", ".mpsc.0.bin", ".mpmc.0.bin",
3763 ".vyukov.bin", ".peers.bin", ".frames.bin"] {
3764 let mut p = prefix.as_os_str().to_owned();
3765 p.push(suffix);
3766 std::fs::remove_file(std::path::PathBuf::from(p)).ok();
3767 }
3768 }
3769
3770 #[test]
3771 fn qos_shape_policy_decision_matrix() {
3772 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3773 let policy = QosRingShapePolicy {
3774 qos: qos.clone(),
3775 hysteresis: std::time::Duration::from_millis(0),
3776 };
3777 let obs = |shape, stamped| PolicyObservation {
3778 active_producers: 2,
3779 active_consumers: 1,
3780 current_shape: shape,
3781 since_last_morph: std::time::Duration::from_secs(1),
3782 stamped,
3783 };
3784
3785 assert_eq!(policy.decide(&obs(RingShape::Spsc, false)),
3787 Some(RingShape::Mpsc));
3788 assert_eq!(policy.decide(&obs(RingShape::Mpsc, false)), None);
3789
3790 qos.set_ordering(crate::qos_policy::Ordering::GlobalFifo);
3792 assert_eq!(policy.decide(&obs(RingShape::Mpsc, false)),
3793 Some(RingShape::Vyukov));
3794 assert_eq!(policy.decide(&obs(RingShape::Vyukov, false)), None);
3795
3796 assert_eq!(policy.decide(&obs(RingShape::Spsc, true)),
3799 Some(RingShape::Mpsc));
3800 assert_eq!(policy.decide(&obs(RingShape::Mpsc, true)), None);
3801
3802 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3804 assert_eq!(policy.decide(&obs(RingShape::Vyukov, false)),
3805 Some(RingShape::Mpsc));
3806
3807 let cold = QosRingShapePolicy {
3809 qos: qos.clone(),
3810 hysteresis: std::time::Duration::from_secs(10),
3811 };
3812 let mut o = obs(RingShape::Spsc, false);
3813 o.since_last_morph = std::time::Duration::from_millis(1);
3814 assert_eq!(cold.decide(&o), None);
3815 }
3816
3817 #[test]
3818 fn default_ordering_policy_decision_matrix() {
3819 let obs = |mode, declared, rate, since_ms| OrderingPolicyObservation {
3820 inversions_per_sec: rate,
3821 current_mode: mode,
3822 declared,
3823 active_producers: 2,
3824 active_consumers: 1,
3825 since_last_change: std::time::Duration::from_millis(since_ms),
3826 };
3827 let declarative = DefaultOrderingPolicy {
3828 hysteresis: std::time::Duration::from_millis(0),
3829 auto_order_threshold: None,
3830 };
3831 assert_eq!(
3833 declarative.decide(&obs(
3834 OrderingMode::Unordered, QosOrdering::GlobalFifo, 0.0, 500)),
3835 Some(OrderingMode::MergeByStamp),
3836 );
3837 assert_eq!(
3838 declarative.decide(&obs(
3839 OrderingMode::MergeByStamp, QosOrdering::GlobalFifo, 0.0, 500)),
3840 None,
3841 );
3842 assert_eq!(
3844 declarative.decide(&obs(
3845 OrderingMode::MergeByStamp, QosOrdering::PerProducer, 0.0, 500)),
3846 Some(OrderingMode::Unordered),
3847 );
3848
3849 let auto = DefaultOrderingPolicy {
3850 hysteresis: std::time::Duration::from_millis(0),
3851 auto_order_threshold: Some(100.0),
3852 };
3853 assert_eq!(
3855 auto.decide(&obs(
3856 OrderingMode::Unordered, QosOrdering::PerProducer, 50.0, 500)),
3857 None,
3858 );
3859 assert_eq!(
3861 auto.decide(&obs(
3862 OrderingMode::Unordered, QosOrdering::PerProducer, 250.0, 500)),
3863 Some(OrderingMode::MergeByStamp),
3864 );
3865 assert_eq!(
3867 auto.decide(&obs(
3868 OrderingMode::MergeByStamp, QosOrdering::PerProducer, 0.0, 500)),
3869 None,
3870 );
3871
3872 let cold = DefaultOrderingPolicy {
3874 hysteresis: std::time::Duration::from_secs(10),
3875 auto_order_threshold: Some(1.0),
3876 };
3877 assert_eq!(
3878 cold.decide(&obs(
3879 OrderingMode::Unordered, QosOrdering::GlobalFifo, 1e6, 1)),
3880 None,
3881 );
3882 }
3883
3884 #[test]
3885 fn sidecar_spawn_with_qos_flips_merge_flag_on_declaration() {
3886 let ring = Arc::new(stamped_anon(2, 1, StampKind::SharedCounter));
3887 ring.morph_to(RingShape::Mpsc).unwrap();
3888 let _p0 = ring.register_producer().unwrap();
3889 let _p1 = ring.register_producer().unwrap();
3890 let _c0 = ring.register_consumer().unwrap();
3891
3892 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3893 let sidecar = AdaptiveRingSidecar::spawn_with_qos(
3894 ring.clone(),
3895 QosRingShapePolicy {
3896 qos: qos.clone(),
3897 hysteresis: std::time::Duration::from_millis(5),
3898 },
3899 DefaultOrderingPolicy {
3900 hysteresis: std::time::Duration::from_millis(5),
3901 auto_order_threshold: None,
3902 },
3903 qos.clone(),
3904 std::time::Duration::from_millis(10),
3905 );
3906
3907 qos.set_ordering(crate::qos_policy::Ordering::GlobalFifo);
3910 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
3911 while std::time::Instant::now() < deadline
3912 && ring.ordering_mode() != Some(OrderingMode::MergeByStamp)
3913 {
3914 std::thread::sleep(std::time::Duration::from_millis(10));
3915 }
3916 assert_eq!(ring.ordering_mode(), Some(OrderingMode::MergeByStamp),
3917 "sidecar must arm the merge flag on the GlobalFifo declaration");
3918 assert_eq!(ring.current_shape(), RingShape::Mpsc,
3919 "stamped ring must stay composed (no Vyukov morph)");
3920 assert!(sidecar.ordering_flips() >= 1);
3921
3922 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3924 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
3925 while std::time::Instant::now() < deadline
3926 && ring.ordering_mode() != Some(OrderingMode::Unordered)
3927 {
3928 std::thread::sleep(std::time::Duration::from_millis(10));
3929 }
3930 assert_eq!(ring.ordering_mode(), Some(OrderingMode::Unordered),
3931 "sidecar must disarm when the declaration is withdrawn");
3932 sidecar.shutdown();
3933 }
3934
3935 #[test]
3936 fn default_sidecar_gates_auto_arm_but_still_opens_on_sustained_inversions() {
3937 let ring = Arc::new(stamped_anon(2, 1, StampKind::SharedCounter));
3945 ring.morph_to(RingShape::Mpsc).unwrap();
3946 ring.register_producer().unwrap();
3947 ring.register_producer().unwrap();
3948 ring.register_consumer().unwrap();
3949 ring.set_ordering_mode(OrderingMode::Unordered).unwrap();
3950
3951 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3952 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3953 let sidecar = AdaptiveRingSidecar::spawn_with_qos(
3954 ring.clone(),
3955 DefaultRingShapePolicy::default(),
3956 DefaultOrderingPolicy {
3957 hysteresis: std::time::Duration::from_millis(0),
3958 auto_order_threshold: Some(50.0),
3959 },
3960 qos.clone(),
3961 std::time::Duration::from_millis(5),
3962 );
3963
3964 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
3965 let stop_c = stop.clone();
3966 let r = ring.clone();
3967 let consumer = std::thread::spawn(move || {
3968 let mut out = [0u8; 64];
3969 while !stop_c.load(Ordering::Acquire) {
3970 r.try_recv(0, &mut out).ok();
3971 }
3972 });
3973
3974 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(6);
3975 let mut seq = 0u64;
3976 while std::time::Instant::now() < deadline
3977 && ring.ordering_mode() != Some(OrderingMode::MergeByStamp)
3978 {
3979 ring.try_send(0, &seq.to_le_bytes()).ok();
3980 ring.try_send(1, &seq.to_le_bytes()).ok();
3981 seq += 1;
3982 }
3983 stop.store(true, Ordering::Release);
3984 consumer.join().unwrap();
3985
3986 assert_eq!(ring.ordering_mode(), Some(OrderingMode::MergeByStamp),
3987 "the gated auto-arm must still commit under sustained inversions");
3988 assert_eq!(sidecar.ordering_flips(), 1,
3989 "the one-way auto-arm fires exactly once");
3990 sidecar.shutdown();
3991 }
3992}