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;
108
109pub struct AdaptiveRing {
110 shape_tag: AtomicU8,
112
113 stale_shape_tag: AtomicU8,
122
123 pin_generation: AtomicU64,
126
127 frame_region: OnceLock<Arc<FrameRegion>>,
136
137 spsc: Arc<SpscRingCore>,
139
140 mpsc: Arc<MpscBacking>,
143
144 mpmc: Arc<MpmcBacking>,
147
148 vyukov: Arc<SharedRing>,
150
151 max_producers: usize,
156 max_consumers: usize,
157
158 capacity: usize,
161
162 directory: Arc<PeerDirectory>,
166
167 synced_epoch: AtomicU64,
170
171 grow_lock: parking_lot::Mutex<()>,
173
174 shape_auto: AtomicBool,
180
181 contract: Option<crate::ring_contract::RingContract>,
189
190 ordering: Option<Arc<OrderingState>>,
197
198 backing_id: BackingId,
201
202 header_sidecar: subetha_core::HandshakeHeader,
205 ring_sidecar: Box<subetha_core::ObservationRing>,
206}
207
208unsafe impl Send for AdaptiveRing {}
209unsafe impl Sync for AdaptiveRing {}
210
211impl subetha_sidecar::AdaptiveInstance for AdaptiveRing {
212 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
213 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
214 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
215 Box::new(subetha_sidecar::NoMigrationPolicy)
216 }
217}
218
219enum BackingId {
222 Anon,
223 File { prefix: std::path::PathBuf, created: bool },
224 Shm { prefix: String },
225}
226
227struct OrderingState {
230 region: OrderingRegion,
231 seen: Vec<SeenLine>,
235}
236
237#[repr(align(64))]
238struct SeenLine {
239 stamp: AtomicU64,
240 mode_tag: AtomicU32,
241 lease_gen: AtomicU64,
247}
248
249impl SeenLine {
250 fn new() -> Self {
251 Self {
252 stamp: AtomicU64::new(0),
253 mode_tag: AtomicU32::new(OrderingMode::Unordered as u32),
254 lease_gen: AtomicU64::new(u64::MAX),
255 }
256 }
257}
258
259#[inline]
261fn drainer_token(consumer_id: usize) -> u64 {
262 ((std::process::id() as u64) << 32) | (consumer_id as u64 & 0xFFFF_FFFF)
263}
264
265struct MpscBacking {
266 rings: ArcSwap<Vec<Arc<SpscRingCore>>>,
271 next_drain: AtomicUsize,
272}
273
274struct MpmcBacking {
275 rings: ArcSwap<Vec<Arc<SpscRingCore>>>,
276 consumer_cursors: Vec<PaddedCursor>,
282}
283
284#[repr(align(64))]
289struct PaddedCursor(AtomicUsize, AtomicUsize);
290
291fn consumer_cursor_table() -> Vec<PaddedCursor> {
292 (0..CONSUMER_SLOT_CEILING)
293 .map(|_| PaddedCursor(AtomicUsize::new(0), AtomicUsize::new(0)))
294 .collect()
295}
296
297#[cfg(target_os = "linux")]
306fn hugepage_region(bytes: usize) -> std::io::Result<crate::hugepages::HugepageRegion> {
307 use crate::hugepages::{HugepageRegion, HugepageSize, HUGEPAGE_2MB};
308 let pages = bytes.div_ceil(HUGEPAGE_2MB).max(1);
309 HugepageRegion::allocate(pages, HugepageSize::Mb2)
310}
311
312#[cfg(windows)]
313fn hugepage_region(bytes: usize) -> std::io::Result<crate::large_pages::LargePageRegion> {
314 use crate::large_pages::{enable_lock_memory_privilege, LargePageRegion};
315 enable_lock_memory_privilege()?;
318 LargePageRegion::allocate(bytes)
319}
320
321#[cfg(any(target_os = "freebsd", target_os = "macos"))]
322fn hugepage_region(bytes: usize) -> std::io::Result<crate::super_pages::SuperPageRegion> {
323 crate::super_pages::SuperPageRegion::allocate(bytes)
330}
331
332impl AdaptiveRing {
333 pub fn create_anon(
339 max_producers: usize,
340 max_consumers: usize,
341 capacity: usize,
342 ) -> Result<Self, RingError> {
343 assert!(max_producers >= 1, "max_producers must be >= 1");
344 assert!(max_consumers >= 1, "max_consumers must be >= 1");
345
346 let spsc = Arc::new(SpscRingCore::create_anon(capacity)?);
347
348 let mpsc_rings: Vec<Arc<SpscRingCore>> = (0..max_producers)
349 .map(|_| SpscRingCore::create_anon(capacity).map(Arc::new))
350 .collect::<Result<Vec<_>, _>>()?;
351 let mpsc = Arc::new(MpscBacking {
352 rings: ArcSwap::from_pointee(mpsc_rings),
353 next_drain: AtomicUsize::new(0),
354 });
355
356 let mpmc_rings: Vec<Arc<SpscRingCore>> = (0..max_producers)
357 .map(|_| SpscRingCore::create_anon(capacity).map(Arc::new))
358 .collect::<Result<Vec<_>, _>>()?;
359 let mpmc = Arc::new(MpmcBacking {
360 rings: ArcSwap::from_pointee(mpmc_rings),
361 consumer_cursors: consumer_cursor_table(),
362 });
363
364 let vyukov = Arc::new(SharedRing::create_anon(capacity)?);
365
366 let directory = Arc::new(PeerDirectory::create_anon()?);
367 directory.publish_rings(max_producers);
368
369 Ok(Self {
370 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
371 stale_shape_tag: AtomicU8::new(STALE_NONE),
372 pin_generation: AtomicU64::new(0),
373 frame_region: OnceLock::new(),
374 spsc,
375 mpsc,
376 mpmc,
377 vyukov,
378 max_producers,
379 max_consumers,
380 capacity,
381 directory,
382 synced_epoch: AtomicU64::new(u64::MAX),
383 grow_lock: parking_lot::Mutex::new(()),
384 contract: None,
385 shape_auto: AtomicBool::new(true),
386 ordering: None,
387 backing_id: BackingId::Anon,
388 header_sidecar: subetha_core::HandshakeHeader::new(),
389 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
390 })
391 }
392
393 #[cfg(any(target_os = "linux", windows, target_os = "freebsd", target_os = "macos"))]
410 pub fn create_hugepage(
411 max_producers: usize,
412 max_consumers: usize,
413 capacity: usize,
414 ) -> Result<Self, RingError> {
415 assert!(max_producers >= 1, "max_producers must be >= 1");
416 assert!(max_consumers >= 1, "max_consumers must be >= 1");
417
418 let spsc_bytes = crate::spsc_ring::spsc_ring_file_size(capacity);
419 let vyukov_bytes = crate::shared_ring::ring_file_size(capacity);
420
421 let spsc = Arc::new(SpscRingCore::create_in_region(
422 hugepage_region(spsc_bytes)?, capacity)?);
423
424 let mut mpsc_rings = Vec::with_capacity(max_producers);
425 for _ in 0..max_producers {
426 mpsc_rings.push(Arc::new(SpscRingCore::create_in_region(
427 hugepage_region(spsc_bytes)?, capacity)?));
428 }
429 let mpsc = Arc::new(MpscBacking {
430 rings: ArcSwap::from_pointee(mpsc_rings),
431 next_drain: AtomicUsize::new(0),
432 });
433
434 let mut mpmc_rings = Vec::with_capacity(max_producers);
435 for _ in 0..max_producers {
436 mpmc_rings.push(Arc::new(SpscRingCore::create_in_region(
437 hugepage_region(spsc_bytes)?, capacity)?));
438 }
439 let mpmc = Arc::new(MpmcBacking {
440 rings: ArcSwap::from_pointee(mpmc_rings),
441 consumer_cursors: consumer_cursor_table(),
442 });
443
444 let vyukov = Arc::new(SharedRing::create_in_region(
445 hugepage_region(vyukov_bytes)?, capacity)?);
446
447 let directory = Arc::new(PeerDirectory::create_anon()?);
448 directory.publish_rings(max_producers);
449
450 Ok(Self {
451 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
452 stale_shape_tag: AtomicU8::new(STALE_NONE),
453 pin_generation: AtomicU64::new(0),
454 frame_region: OnceLock::new(),
455 spsc,
456 mpsc,
457 mpmc,
458 vyukov,
459 max_producers,
460 max_consumers,
461 capacity,
462 directory,
463 synced_epoch: AtomicU64::new(u64::MAX),
464 grow_lock: parking_lot::Mutex::new(()),
465 contract: None,
466 shape_auto: AtomicBool::new(true),
467 ordering: None,
468 backing_id: BackingId::Anon,
473 header_sidecar: subetha_core::HandshakeHeader::new(),
474 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
475 })
476 }
477
478 pub fn create(
483 path_prefix: impl AsRef<Path>,
484 max_producers: usize,
485 max_consumers: usize,
486 capacity: usize,
487 ) -> Result<Self, RingError> {
488 assert!(max_producers >= 1 && max_consumers >= 1);
489 let base = path_prefix.as_ref();
490
491 let spsc_path = with_suffix(base, ".spsc.bin");
492 let spsc = Arc::new(SpscRingCore::create(&spsc_path, capacity)?);
493
494 let mut mpsc_rings = Vec::with_capacity(max_producers);
495 for i in 0..max_producers {
496 let p = with_suffix(base, &format!(".mpsc.{i}.bin"));
497 mpsc_rings.push(Arc::new(SpscRingCore::create(&p, capacity)?));
498 }
499 let mpsc = Arc::new(MpscBacking {
500 rings: ArcSwap::from_pointee(mpsc_rings),
501 next_drain: AtomicUsize::new(0),
502 });
503
504 let mut mpmc_rings = Vec::with_capacity(max_producers);
505 for i in 0..max_producers {
506 let p = with_suffix(base, &format!(".mpmc.{i}.bin"));
507 mpmc_rings.push(Arc::new(SpscRingCore::create(&p, capacity)?));
508 }
509 let mpmc = Arc::new(MpmcBacking {
510 rings: ArcSwap::from_pointee(mpmc_rings),
511 consumer_cursors: consumer_cursor_table(),
512 });
513
514 let vyukov_path = with_suffix(base, ".vyukov.bin");
515 let vyukov = Arc::new(SharedRing::create(&vyukov_path, capacity)?);
516
517 let directory = Arc::new(
518 PeerDirectory::create(with_suffix(base, ".peers.bin"))?,
519 );
520 directory.publish_rings(max_producers);
521
522 Ok(Self {
523 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
524 stale_shape_tag: AtomicU8::new(STALE_NONE),
525 pin_generation: AtomicU64::new(0),
526 frame_region: OnceLock::new(),
527 spsc,
528 mpsc,
529 mpmc,
530 vyukov,
531 max_producers,
532 max_consumers,
533 capacity,
534 directory,
535 synced_epoch: AtomicU64::new(u64::MAX),
536 grow_lock: parking_lot::Mutex::new(()),
537 contract: None,
538 shape_auto: AtomicBool::new(true),
539 ordering: None,
540 backing_id: BackingId::File {
541 prefix: base.to_path_buf(),
542 created: true,
543 },
544 header_sidecar: subetha_core::HandshakeHeader::new(),
545 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
546 })
547 }
548
549 pub fn open(
561 path_prefix: impl AsRef<Path>,
562 max_producers: usize,
563 max_consumers: usize,
564 expected_capacity: usize,
565 ) -> Result<Self, RingError> {
566 assert!(max_producers >= 1 && max_consumers >= 1);
567 let base = path_prefix.as_ref();
568
569 let directory = Arc::new(
574 PeerDirectory::open(with_suffix(base, ".peers.bin"))?,
575 );
576 let n_rings = directory.published().max(1);
577
578 let spsc_path = with_suffix(base, ".spsc.bin");
579 let spsc = Arc::new(SpscRingCore::open(&spsc_path, expected_capacity)?);
580
581 let mut mpsc_rings = Vec::with_capacity(n_rings);
582 for i in 0..n_rings {
583 let p = with_suffix(base, &format!(".mpsc.{i}.bin"));
584 mpsc_rings.push(Arc::new(SpscRingCore::open(&p, expected_capacity)?));
585 }
586 let mpsc = Arc::new(MpscBacking {
587 rings: ArcSwap::from_pointee(mpsc_rings),
588 next_drain: AtomicUsize::new(0),
589 });
590
591 let mut mpmc_rings = Vec::with_capacity(n_rings);
592 for i in 0..n_rings {
593 let p = with_suffix(base, &format!(".mpmc.{i}.bin"));
594 mpmc_rings.push(Arc::new(SpscRingCore::open(&p, expected_capacity)?));
595 }
596 let mpmc = Arc::new(MpmcBacking {
597 rings: ArcSwap::from_pointee(mpmc_rings),
598 consumer_cursors: consumer_cursor_table(),
599 });
600
601 let vyukov_path = with_suffix(base, ".vyukov.bin");
602 let vyukov = Arc::new(SharedRing::open(&vyukov_path, expected_capacity)?);
603
604 Ok(Self {
605 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
606 stale_shape_tag: AtomicU8::new(STALE_NONE),
607 pin_generation: AtomicU64::new(0),
608 frame_region: OnceLock::new(),
609 spsc,
610 mpsc,
611 mpmc,
612 vyukov,
613 max_producers,
614 max_consumers,
615 capacity: expected_capacity,
616 directory,
617 synced_epoch: AtomicU64::new(u64::MAX),
618 grow_lock: parking_lot::Mutex::new(()),
619 contract: None,
620 shape_auto: AtomicBool::new(true),
621 ordering: None,
622 backing_id: BackingId::File {
623 prefix: base.to_path_buf(),
624 created: false,
625 },
626 header_sidecar: subetha_core::HandshakeHeader::new(),
627 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
628 })
629 }
630
631 pub fn create_shmfs(
640 name_prefix: &str,
641 max_producers: usize,
642 max_consumers: usize,
643 capacity: usize,
644 ) -> Result<Self, RingError> {
645 assert!(max_producers >= 1, "max_producers must be >= 1");
646 assert!(max_consumers >= 1, "max_consumers must be >= 1");
647
648 let spsc_size = crate::spsc_ring::spsc_ring_file_size(capacity);
649 let vyukov_size = crate::shared_ring::ring_file_size(capacity);
650
651 let spsc_shm = crate::shm_file::ShmFile::create_or_open_named(
653 &format!("{name_prefix}_spsc"), spsc_size,
654 ).map_err(|_| RingError::PayloadTooLarge)?;
655 let spsc = Arc::new(SpscRingCore::create_from_shm(spsc_shm, capacity)?);
656
657 let mut mpsc_rings = Vec::with_capacity(max_producers);
659 for i in 0..max_producers {
660 let shm = crate::shm_file::ShmFile::create_or_open_named(
661 &format!("{name_prefix}_mpsc_{i}"), spsc_size,
662 ).map_err(|_| RingError::PayloadTooLarge)?;
663 mpsc_rings.push(Arc::new(SpscRingCore::create_from_shm(shm, capacity)?));
664 }
665 let mpsc = Arc::new(MpscBacking {
666 rings: ArcSwap::from_pointee(mpsc_rings),
667 next_drain: AtomicUsize::new(0),
668 });
669
670 let mut mpmc_rings = Vec::with_capacity(max_producers);
672 for i in 0..max_producers {
673 let shm = crate::shm_file::ShmFile::create_or_open_named(
674 &format!("{name_prefix}_mpmc_{i}"), spsc_size,
675 ).map_err(|_| RingError::PayloadTooLarge)?;
676 mpmc_rings.push(Arc::new(SpscRingCore::create_from_shm(shm, capacity)?));
677 }
678 let mpmc = Arc::new(MpmcBacking {
679 rings: ArcSwap::from_pointee(mpmc_rings),
680 consumer_cursors: consumer_cursor_table(),
681 });
682
683 let vyukov_shm = crate::shm_file::ShmFile::create_or_open_named(
685 &format!("{name_prefix}_vyukov"), vyukov_size,
686 ).map_err(|_| RingError::PayloadTooLarge)?;
687 let vyukov = Arc::new(SharedRing::create_from_shm(vyukov_shm, capacity)?);
688
689 let directory = Arc::new(PeerDirectory::create_or_open_shm(
690 &format!("{name_prefix}_peers"),
691 )?);
692 directory.publish_rings(max_producers);
693
694 Ok(Self {
695 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
696 stale_shape_tag: AtomicU8::new(STALE_NONE),
697 pin_generation: AtomicU64::new(0),
698 frame_region: OnceLock::new(),
699 spsc, mpsc, mpmc, vyukov,
700 max_producers, max_consumers,
701 capacity,
702 directory,
703 synced_epoch: AtomicU64::new(u64::MAX),
704 grow_lock: parking_lot::Mutex::new(()),
705 contract: None,
706 shape_auto: AtomicBool::new(true),
707 ordering: None,
708 backing_id: BackingId::Shm { prefix: name_prefix.to_owned() },
709 header_sidecar: subetha_core::HandshakeHeader::new(),
710 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
711 })
712 }
713
714 pub fn open_shmfs(
734 name_prefix: &str,
735 max_producers: usize,
736 max_consumers: usize,
737 expected_capacity: usize,
738 ) -> Result<Self, RingError> {
739 assert!(max_producers >= 1, "max_producers must be >= 1");
740 assert!(max_consumers >= 1, "max_consumers must be >= 1");
741
742 let spsc_size = crate::spsc_ring::spsc_ring_file_size(expected_capacity);
743 let vyukov_size = crate::shared_ring::ring_file_size(expected_capacity);
744
745 let directory = Arc::new(PeerDirectory::create_or_open_shm(
750 &format!("{name_prefix}_peers"),
751 )?);
752 let n_rings = directory.published().max(1);
753
754 let spsc_shm = crate::shm_file::ShmFile::create_or_open_named(
756 &format!("{name_prefix}_spsc"), spsc_size,
757 ).map_err(|_| RingError::PayloadTooLarge)?;
758 let spsc = Arc::new(SpscRingCore::open_from_shm(spsc_shm, expected_capacity)?);
759
760 let mut mpsc_rings = Vec::with_capacity(n_rings);
762 for i in 0..n_rings {
763 let shm = crate::shm_file::ShmFile::create_or_open_named(
764 &format!("{name_prefix}_mpsc_{i}"), spsc_size,
765 ).map_err(|_| RingError::PayloadTooLarge)?;
766 mpsc_rings.push(Arc::new(SpscRingCore::open_from_shm(shm, expected_capacity)?));
767 }
768 let mpsc = Arc::new(MpscBacking {
769 rings: ArcSwap::from_pointee(mpsc_rings),
770 next_drain: AtomicUsize::new(0),
771 });
772
773 let mut mpmc_rings = Vec::with_capacity(n_rings);
775 for i in 0..n_rings {
776 let shm = crate::shm_file::ShmFile::create_or_open_named(
777 &format!("{name_prefix}_mpmc_{i}"), spsc_size,
778 ).map_err(|_| RingError::PayloadTooLarge)?;
779 mpmc_rings.push(Arc::new(SpscRingCore::open_from_shm(shm, expected_capacity)?));
780 }
781 let mpmc = Arc::new(MpmcBacking {
782 rings: ArcSwap::from_pointee(mpmc_rings),
783 consumer_cursors: consumer_cursor_table(),
784 });
785
786 let vyukov_shm = crate::shm_file::ShmFile::create_or_open_named(
788 &format!("{name_prefix}_vyukov"), vyukov_size,
789 ).map_err(|_| RingError::PayloadTooLarge)?;
790 let vyukov = Arc::new(SharedRing::open_from_shm(vyukov_shm, expected_capacity)?);
791
792 Ok(Self {
793 shape_tag: AtomicU8::new(RingShape::Spsc as u8),
794 stale_shape_tag: AtomicU8::new(STALE_NONE),
795 pin_generation: AtomicU64::new(0),
796 frame_region: OnceLock::new(),
797 spsc, mpsc, mpmc, vyukov,
798 max_producers, max_consumers,
799 capacity: expected_capacity,
800 directory,
801 synced_epoch: AtomicU64::new(u64::MAX),
802 grow_lock: parking_lot::Mutex::new(()),
803 contract: None,
804 shape_auto: AtomicBool::new(true),
805 ordering: None,
806 backing_id: BackingId::Shm { prefix: name_prefix.to_owned() },
807 header_sidecar: subetha_core::HandshakeHeader::new(),
808 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
809 })
810 }
811
812 pub fn with_ordering_stamps(self) -> Result<Self, RingError> {
836 self.with_ordering_stamps_impl(None)
837 }
838
839 pub fn with_ordering_stamps_kind(self, kind: StampKind) -> Result<Self, RingError> {
846 self.with_ordering_stamps_impl(Some(kind))
847 }
848
849 fn with_ordering_stamps_impl(
850 mut self,
851 kind: Option<StampKind>,
852 ) -> Result<Self, RingError> {
853 if self.ordering.is_some() {
854 return Ok(self);
855 }
856 if self.current_shape() == RingShape::Vyukov {
857 return Err(RingError::LayoutMismatch);
858 }
859 let lines = crate::peer_directory::PRODUCER_SLOT_CEILING;
866 let region = match &self.backing_id {
867 BackingId::Anon => OrderingRegion::create_anon(
868 lines,
869 kind.unwrap_or_else(default_stamp_kind),
870 )?,
871 BackingId::File { prefix, created } => {
872 let path = with_suffix(prefix, ".ordering.bin");
873 if *created {
874 OrderingRegion::create(
875 &path,
876 lines,
877 kind.unwrap_or_else(default_stamp_kind),
878 )?
879 } else {
880 let region = OrderingRegion::open(&path, lines)?;
881 if let Some(k) = kind
882 && region.stamp_kind() != k
883 {
884 return Err(RingError::LayoutMismatch);
885 }
886 region
887 }
888 }
889 BackingId::Shm { prefix } => {
890 let size = ordering_region_size(lines);
891 let shm = crate::shm_file::ShmFile::create_or_open_named(
892 &format!("{prefix}_ordering"),
893 size,
894 ).map_err(|e| RingError::IoError(e.kind()))?;
895 OrderingRegion::create_shm(
896 shm,
897 lines,
898 kind.unwrap_or_else(default_stamp_kind),
899 )?
900 }
901 };
902 let seen = (0..CONSUMER_SLOT_CEILING).map(|_| SeenLine::new()).collect();
903 self.ordering = Some(Arc::new(OrderingState { region, seen }));
904 Ok(self)
905 }
906
907 pub fn current_shape(&self) -> RingShape {
909 RingShape::from_u8(self.shape_tag.load(Ordering::Acquire))
910 }
911
912 pub fn peek_spsc_slot(&self) -> Option<PeekedSpscSlot<'_>> {
923 if self.current_shape() != RingShape::Spsc {
924 return None;
925 }
926 self.spsc.peek_slot().map(|inner| PeekedSpscSlot { inner })
927 }
928
929 pub fn is_empty(&self) -> bool {
948 if let Some(stale) = self.stale_shape()
949 && !self.backing_is_empty(stale)
950 {
951 return false;
952 }
953 self.backing_is_empty(self.current_shape())
954 }
955
956 pub fn approx_len(&self) -> usize {
961 let stale_len = match self.stale_shape() {
962 Some(stale) if stale != self.current_shape() => {
963 self.backing_approx_len(stale)
964 }
965 _ => 0,
966 };
967 stale_len + self.backing_approx_len(self.current_shape())
968 }
969
970 fn backing_approx_len(&self, shape: RingShape) -> usize {
971 match shape {
972 RingShape::Spsc => self.spsc.approx_len(),
973 RingShape::Mpsc => self.mpsc.rings.load().iter().map(|r| r.approx_len()).sum(),
974 RingShape::Mpmc => self.mpmc.rings.load().iter().map(|r| r.approx_len()).sum(),
975 RingShape::Vyukov => self.vyukov.approx_len(),
976 }
977 }
978
979 pub fn sub_ring_capacity(&self) -> usize {
984 match self.current_shape() {
985 RingShape::Spsc => self.spsc.capacity(),
986 RingShape::Mpsc => self.mpsc.rings.load().first().map(|r| r.capacity()).unwrap_or(0),
987 RingShape::Mpmc => self.mpmc.rings.load().first().map(|r| r.capacity()).unwrap_or(0),
988 RingShape::Vyukov => self.vyukov.capacity(),
989 }
990 }
991
992 pub fn total_slot_capacity(&self) -> usize {
997 match self.current_shape() {
998 RingShape::Spsc => self.spsc.capacity(),
999 RingShape::Mpsc => self.mpsc.rings.load().iter().map(|r| r.capacity()).sum(),
1000 RingShape::Mpmc => self.mpmc.rings.load().iter().map(|r| r.capacity()).sum(),
1001 RingShape::Vyukov => self.vyukov.capacity(),
1002 }
1003 }
1004
1005 pub fn pin_generation(&self) -> u64 {
1008 self.pin_generation.load(Ordering::Acquire)
1009 }
1010
1011 pub fn max_producers(&self) -> usize { self.max_producers }
1015
1016 pub fn max_consumers(&self) -> usize { self.max_consumers }
1019
1020 pub fn published_producers(&self) -> usize {
1023 self.directory.published()
1024 }
1025
1026 pub fn contract(&self) -> crate::ring_contract::RingContract {
1031 self.contract.unwrap_or_else(crate::ring_contract::RingContract::unbounded)
1032 }
1033
1034 pub fn with_contract(mut self, contract: crate::ring_contract::RingContract) -> Self {
1040 self.contract = Some(contract);
1041 self
1042 }
1043
1044 pub fn contract_filtered_shape(&self, target: RingShape) -> RingShape {
1056 if self.contract().permits_shape(target) {
1057 return target;
1058 }
1059 if self.ordering.is_none() {
1060 RingShape::Vyukov
1061 } else {
1062 target
1063 }
1064 }
1065
1066 fn reshape_for_counts(&self) -> bool {
1078 if !self.shape_auto.load(Ordering::Relaxed)
1079 || self.current_shape() == RingShape::Vyukov
1080 {
1081 return true;
1082 }
1083 let p = self.directory.active_producers();
1084 let c = self.directory.active_consumers();
1085 if let Some(target) = DefaultRingShapePolicy::target_shape(p, c)
1086 && target != self.current_shape()
1087 {
1088 return self.morph_shape(self.contract_filtered_shape(target)).is_ok();
1089 }
1090 true
1091 }
1092
1093 pub fn pin_shape(&self) {
1098 self.shape_auto.store(false, Ordering::Relaxed);
1099 }
1100
1101 pub fn resume_auto_shape(&self) {
1107 self.shape_auto.store(true, Ordering::Relaxed);
1108 let p = self.directory.active_producers();
1109 let c = self.directory.active_consumers();
1110 if let Some(target) = DefaultRingShapePolicy::target_shape(p, c) {
1111 self.morph_shape(self.contract_filtered_shape(target)).ok();
1112 }
1113 }
1114
1115 pub fn shape_is_auto(&self) -> bool {
1119 self.shape_auto.load(Ordering::Relaxed)
1120 }
1121
1122 #[inline]
1127 fn ensure_synced(&self) {
1128 let e = self.directory.epoch();
1129 if e != self.synced_epoch.load(Ordering::Relaxed) {
1130 self.sync_topology(e);
1131 }
1132 }
1133
1134 #[cold]
1135 fn sync_topology(&self, epoch: u64) {
1136 self.directory.reap_dead_peers();
1137 let arrays_ok = self.refresh_local_arrays().is_ok();
1138 let shape_ok = self.reshape_for_counts();
1139 if arrays_ok && shape_ok {
1140 self.synced_epoch.store(epoch, Ordering::Relaxed);
1144 }
1145 }
1146
1147 fn refresh_local_arrays(&self) -> Result<(), RingError> {
1152 let published = self.directory.published();
1153 if self.mpsc.rings.load().len() >= published {
1154 return Ok(());
1155 }
1156 let _guard = self.grow_lock.lock();
1157 let cur_mpsc = self.mpsc.rings.load_full();
1158 let cur_mpmc = self.mpmc.rings.load_full();
1159 if cur_mpsc.len() >= published {
1160 return Ok(());
1161 }
1162 let mut mpsc_new = (*cur_mpsc).clone();
1163 let mut mpmc_new = (*cur_mpmc).clone();
1164 for i in cur_mpsc.len()..published {
1165 let (a, b) = self.open_ring_backing(i)?;
1166 mpsc_new.push(a);
1167 mpmc_new.push(b);
1168 }
1169 self.mpsc.rings.store(Arc::new(mpsc_new));
1170 self.mpmc.rings.store(Arc::new(mpmc_new));
1171 self.pin_generation.fetch_add(1, Ordering::AcqRel);
1172 Ok(())
1173 }
1174
1175 fn open_ring_backing(
1179 &self,
1180 i: usize,
1181 ) -> Result<(Arc<SpscRingCore>, Arc<SpscRingCore>), RingError> {
1182 match &self.backing_id {
1183 BackingId::File { prefix, .. } => {
1184 let a = SpscRingCore::open(
1185 with_suffix(prefix, &format!(".mpsc.{i}.bin")), self.capacity)?;
1186 let b = SpscRingCore::open(
1187 with_suffix(prefix, &format!(".mpmc.{i}.bin")), self.capacity)?;
1188 Ok((Arc::new(a), Arc::new(b)))
1189 }
1190 BackingId::Shm { prefix } => {
1191 let size = crate::spsc_ring::spsc_ring_file_size(self.capacity);
1192 let shm_a = crate::shm_file::ShmFile::create_or_open_named(
1193 &format!("{prefix}_mpsc_{i}"), size,
1194 ).map_err(|e| RingError::IoError(e.kind()))?;
1195 let shm_b = crate::shm_file::ShmFile::create_or_open_named(
1196 &format!("{prefix}_mpmc_{i}"), size,
1197 ).map_err(|e| RingError::IoError(e.kind()))?;
1198 let a = SpscRingCore::create_from_shm(shm_a, self.capacity)?;
1199 let b = SpscRingCore::create_from_shm(shm_b, self.capacity)?;
1200 Ok((Arc::new(a), Arc::new(b)))
1201 }
1202 BackingId::Anon => Err(RingError::LayoutMismatch),
1205 }
1206 }
1207
1208 fn create_ring_backing(
1212 &self,
1213 i: usize,
1214 ) -> Result<(Arc<SpscRingCore>, Arc<SpscRingCore>), RingError> {
1215 match &self.backing_id {
1216 BackingId::Anon => {
1217 let a = SpscRingCore::create_anon(self.capacity)?;
1218 let b = SpscRingCore::create_anon(self.capacity)?;
1219 Ok((Arc::new(a), Arc::new(b)))
1220 }
1221 BackingId::File { prefix, .. } => {
1222 let a = SpscRingCore::create(
1223 with_suffix(prefix, &format!(".mpsc.{i}.bin")), self.capacity)?;
1224 let b = SpscRingCore::create(
1225 with_suffix(prefix, &format!(".mpmc.{i}.bin")), self.capacity)?;
1226 Ok((Arc::new(a), Arc::new(b)))
1227 }
1228 BackingId::Shm { prefix } => {
1229 let size = crate::spsc_ring::spsc_ring_file_size(self.capacity);
1230 let shm_a = crate::shm_file::ShmFile::create_or_open_named(
1231 &format!("{prefix}_mpsc_{i}"), size,
1232 ).map_err(|e| RingError::IoError(e.kind()))?;
1233 let shm_b = crate::shm_file::ShmFile::create_or_open_named(
1234 &format!("{prefix}_mpmc_{i}"), size,
1235 ).map_err(|e| RingError::IoError(e.kind()))?;
1236 let a = SpscRingCore::create_from_shm(shm_a, self.capacity)?;
1237 let b = SpscRingCore::create_from_shm(shm_b, self.capacity)?;
1238 Ok((Arc::new(a), Arc::new(b)))
1239 }
1240 }
1241 }
1242
1243 fn grow_rings_to(&self, want: usize) -> Result<(), RingError> {
1248 let _guard = self.grow_lock.lock();
1249 let published = self.directory.published();
1250 let cur_mpsc = self.mpsc.rings.load_full();
1251 let cur_mpmc = self.mpmc.rings.load_full();
1252 let mut mpsc_new = (*cur_mpsc).clone();
1253 let mut mpmc_new = (*cur_mpmc).clone();
1254 for i in cur_mpsc.len()..published {
1257 let (a, b) = self.open_ring_backing(i)?;
1258 mpsc_new.push(a);
1259 mpmc_new.push(b);
1260 }
1261 for i in published..want {
1262 let (a, b) = self.create_ring_backing(i)?;
1263 mpsc_new.push(a);
1264 mpmc_new.push(b);
1265 }
1266 if mpsc_new.len() > cur_mpsc.len() {
1267 self.mpsc.rings.store(Arc::new(mpsc_new));
1268 self.mpmc.rings.store(Arc::new(mpmc_new));
1269 self.pin_generation.fetch_add(1, Ordering::AcqRel);
1270 }
1271 if want > published {
1272 self.directory.publish_rings(want);
1273 }
1274 Ok(())
1275 }
1276
1277 pub fn register_producer(&self) -> Result<usize, AdaptiveError> {
1288 let slot = self.directory.claim_producer_slot()
1289 .ok_or(AdaptiveError::TooManyProducers)?;
1290 if let Some(g) = self.contract
1291 && !g.permits_producer(self.directory.active_producers() - 1)
1292 {
1293 self.directory.release_producer_slot(slot);
1294 return Err(AdaptiveError::TooManyProducers);
1295 }
1296 if slot >= self.directory.published()
1297 && self.grow_rings_to(slot + 1).is_err()
1298 {
1299 self.directory.release_producer_slot(slot);
1300 return Err(AdaptiveError::GrowthFailed);
1301 }
1302 self.ensure_synced();
1303 self.reshape_for_counts();
1304 Ok(slot)
1305 }
1306
1307 pub fn unregister_producer(&self, producer_id: usize) {
1312 self.directory.release_producer_slot(producer_id);
1313 self.reshape_for_counts();
1314 }
1315
1316 pub fn register_consumer(&self) -> Result<usize, AdaptiveError> {
1323 let slot = self.directory.claim_consumer_slot()
1324 .ok_or(AdaptiveError::TooManyConsumers)?;
1325 if let Some(g) = self.contract
1326 && !g.permits_consumer(self.directory.active_consumers() - 1)
1327 {
1328 self.directory.release_consumer_slot(slot);
1329 return Err(AdaptiveError::TooManyConsumers);
1330 }
1331 self.rebalance_ownership();
1332 self.ensure_synced();
1333 self.reshape_for_counts();
1334 Ok(slot)
1335 }
1336
1337 pub fn unregister_consumer(&self, consumer_id: usize) {
1342 let me = consumer_id as u16;
1343 let remaining: Vec<u16> = self.directory.claimed_consumer_slots()
1344 .into_iter()
1345 .filter(|s| *s != me)
1346 .collect();
1347 let n = self.directory.published();
1348 for r in 0..n {
1349 let (owner, _) = self.directory.ring_owner(r);
1350 if owner == me {
1351 match remaining.get(r % remaining.len().max(1)) {
1352 Some(to) => self.directory.transfer_ring(r, me, *to),
1353 None => self.directory.transfer_ring(r, me, OWNER_NONE),
1354 }
1355 }
1356 }
1357 self.directory.release_consumer_slot(consumer_id);
1358 self.reshape_for_counts();
1359 }
1360
1361 fn rebalance_ownership(&self) {
1367 let slots = self.directory.claimed_consumer_slots();
1368 if slots.is_empty() {
1369 return;
1370 }
1371 let n = self.directory.published();
1372 for r in 0..n {
1373 let desired = slots[r % slots.len()];
1374 let (owner, pending) = self.directory.ring_owner(r);
1375 if owner == desired {
1376 continue;
1377 }
1378 if owner == OWNER_NONE {
1379 self.directory.try_claim_ring(r, desired);
1380 } else if pending != desired {
1381 self.directory.request_handoff(r, desired);
1382 }
1383 }
1384 }
1385
1386 pub fn active_producers(&self) -> usize {
1388 self.directory.active_producers()
1389 }
1390
1391 pub fn active_consumers(&self) -> usize {
1393 self.directory.active_consumers()
1394 }
1395
1396 pub fn is_stamped(&self) -> bool {
1398 self.ordering.is_some()
1399 }
1400
1401 pub fn stamp_kind(&self) -> Option<StampKind> {
1403 self.ordering.as_ref().map(|o| o.region.stamp_kind())
1404 }
1405
1406 pub fn ordering_mode(&self) -> Option<OrderingMode> {
1411 self.ordering.as_ref().map(|o| o.region.mode())
1412 }
1413
1414 pub fn set_ordering_mode(&self, mode: OrderingMode) -> Result<(), RingError> {
1420 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1421 ord.region.set_mode(mode);
1422 Ok(())
1423 }
1424
1425 pub fn inversions(&self) -> u64 {
1428 self.ordering.as_ref().map(|o| o.region.inversions()).unwrap_or(0)
1429 }
1430
1431 pub fn refresh_watermark(&self, producer_id: usize) -> Result<(), RingError> {
1434 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1435 if producer_id >= ord.region.max_producers() {
1436 return Err(RingError::PayloadTooLarge);
1437 }
1438 ord.region.refresh_watermark(producer_id);
1439 Ok(())
1440 }
1441
1442 pub fn retire_producer(&self, producer_id: usize) -> Result<(), RingError> {
1447 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1448 if producer_id >= ord.region.max_producers() {
1449 return Err(RingError::PayloadTooLarge);
1450 }
1451 ord.region.retire_producer(producer_id);
1452 Ok(())
1453 }
1454
1455 pub fn release_drainer(&self, consumer_id: usize) -> Result<bool, RingError> {
1459 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1460 Ok(ord.region.release_drainer(drainer_token(consumer_id)))
1461 }
1462
1463 pub fn tick_drainer_epoch(&self) -> Result<u64, RingError> {
1468 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1469 Ok(ord.region.tick_drainer_epoch())
1470 }
1471
1472 pub fn ordering_region(&self) -> Option<&OrderingRegion> {
1477 self.ordering.as_ref().map(|o| &o.region)
1478 }
1479
1480 pub fn try_send(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
1492 self.ensure_synced();
1493 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1494 if let Some(ord) = &self.ordering {
1495 return self.stamped_send_inner(ord, shape, producer_id, payload);
1496 }
1497 match shape {
1498 RingShape::Spsc => self.spsc.try_push(payload),
1499 RingShape::Mpsc => {
1500 let rings = self.mpsc.rings.load();
1501 let ring = rings.get(producer_id)
1502 .ok_or(RingError::PayloadTooLarge)?; ring.try_push(payload)
1504 }
1505 RingShape::Mpmc => {
1506 let rings = self.mpmc.rings.load();
1507 let ring = rings.get(producer_id)
1508 .ok_or(RingError::PayloadTooLarge)?;
1509 ring.try_push(payload)
1510 }
1511 RingShape::Vyukov => self.vyukov.try_push(payload),
1512 }
1513 }
1514
1515 pub fn try_recv(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
1525 self.ensure_synced();
1526 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1527 if let Some(ord) = &self.ordering {
1528 return self.ordered_recv_inner(ord, shape, consumer_id, out)
1529 .map(|(n, _stamp)| n);
1530 }
1531 if let Some(stale) = self.stale_shape()
1535 && stale != shape
1536 && Self::may_walk_stale(stale, consumer_id)
1537 && let Ok(n) = self.shape_pop(stale, consumer_id, out)
1538 {
1539 return Ok(n);
1540 }
1541 self.shape_pop(shape, consumer_id, out)
1542 }
1543
1544 pub const FRAME_INLINE_BUDGET: usize = PAYLOAD_BYTES - 5;
1550
1551 pub const FRAME_DEFAULT_BLOCK_SIZE: usize = 8192;
1556
1557 pub fn with_frames(self, block_size: usize, block_count: usize) -> Self {
1563 self.frame_region
1564 .get_or_init(|| self.build_frame_region(block_size, block_count));
1565 self
1566 }
1567
1568 fn frame_region(&self) -> &FrameRegion {
1569 self.frame_region.get_or_init(|| {
1570 let blocks = self.spsc.capacity().max(16);
1571 self.build_frame_region(Self::FRAME_DEFAULT_BLOCK_SIZE, blocks)
1572 })
1573 }
1574
1575 fn build_frame_region(&self, block_size: usize, block_count: usize) -> Arc<FrameRegion> {
1591 let region = match &self.backing_id {
1592 BackingId::Anon => FrameRegion::create_anon(block_size, block_count),
1593 BackingId::Shm { prefix } => FrameRegion::create_or_open_shm(
1594 &format!("{prefix}_frames"),
1595 block_size,
1596 block_count,
1597 ),
1598 BackingId::File { prefix, .. } => FrameRegion::create_or_open_file(
1599 with_suffix(prefix, ".frames.bin"),
1600 block_size,
1601 block_count,
1602 ),
1603 };
1604 Arc::new(region.expect("frame region create-or-open"))
1605 }
1606
1607 pub fn send_frame(&self, producer_id: usize, payload: &[u8])
1621 -> Result<FrameClass, RingError>
1622 {
1623 self.send_frame_as(producer_id, payload, LayoutHint::Auto)
1624 }
1625
1626 pub fn send_frame_as(&self, producer_id: usize, payload: &[u8], hint: LayoutHint)
1629 -> Result<FrameClass, RingError>
1630 {
1631 if self.ordering.is_some() {
1632 return Err(RingError::LayoutMismatch);
1633 }
1634 let inline = match hint {
1635 LayoutHint::ForceInline => {
1636 if payload.len() > Self::FRAME_INLINE_BUDGET {
1637 return Err(RingError::PayloadTooLarge);
1638 }
1639 true
1640 }
1641 LayoutHint::ForceOffset => false,
1642 LayoutHint::Auto => payload.len() <= Self::FRAME_INLINE_BUDGET,
1643 };
1644 let len = payload.len() as u32;
1645 if inline {
1646 let mut buf = [0u8; PAYLOAD_BYTES];
1649 buf[0] = FrameClass::Inline as u8;
1650 buf[1..5].copy_from_slice(&len.to_le_bytes());
1651 buf[5..5 + payload.len()].copy_from_slice(payload);
1652 self.try_send(producer_id, &buf[..5 + payload.len()])?;
1653 Ok(FrameClass::Inline)
1654 } else {
1655 let region = self.frame_region();
1656 if payload.len() > region.block_size() {
1657 return Err(RingError::PayloadTooLarge);
1658 }
1659 let idx = region.alloc().ok_or(RingError::Full)?;
1660 region.write_block(idx, payload);
1661 let mut buf = [0u8; 9];
1663 buf[0] = FrameClass::Offset as u8;
1664 buf[1..5].copy_from_slice(&len.to_le_bytes());
1665 buf[5..9].copy_from_slice(&idx.to_le_bytes());
1666 match self.try_send(producer_id, &buf) {
1667 Ok(()) => Ok(FrameClass::Offset),
1668 Err(e) => {
1669 region.free(idx);
1672 Err(e)
1673 }
1674 }
1675 }
1676 }
1677
1678 pub fn recv_frame(&self, consumer_id: usize, out: &mut Vec<u8>)
1686 -> Result<FrameClass, RingError>
1687 {
1688 if self.ordering.is_some() {
1689 return Err(RingError::LayoutMismatch);
1690 }
1691 let mut slot = [0u8; SPSC_PAYLOAD_BYTES];
1693 self.try_recv(consumer_id, &mut slot)?;
1694 let len = u32::from_le_bytes([slot[1], slot[2], slot[3], slot[4]]) as usize;
1695 out.clear();
1696 if slot[0] == FrameClass::Inline as u8 {
1697 out.extend_from_slice(&slot[5..5 + len]);
1698 Ok(FrameClass::Inline)
1699 } else {
1700 let idx = u32::from_le_bytes([slot[5], slot[6], slot[7], slot[8]]);
1701 let region = self.frame_region();
1702 region.read_block_into(idx, len, out);
1703 region.free(idx);
1704 Ok(FrameClass::Offset)
1705 }
1706 }
1707
1708 pub fn try_recv_with_stamp(
1714 &self,
1715 consumer_id: usize,
1716 out: &mut [u8],
1717 ) -> Result<(usize, u64), RingError> {
1718 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1719 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1720 self.ordered_recv_inner(ord, shape, consumer_id, out)
1721 }
1722
1723 fn stamped_send_inner(
1730 &self,
1731 ord: &OrderingState,
1732 shape: RingShape,
1733 producer_id: usize,
1734 payload: &[u8],
1735 ) -> Result<(), RingError> {
1736 if payload.len() > STAMPED_PAYLOAD_BYTES {
1737 return Err(RingError::PayloadTooLarge);
1738 }
1739 if producer_id >= ord.region.max_producers() {
1740 return Err(RingError::PayloadTooLarge);
1741 }
1742 let mpsc_guard;
1743 let mpmc_guard;
1744 let ring: &SpscRingCore = match shape {
1745 RingShape::Spsc => &self.spsc,
1746 RingShape::Mpsc => {
1747 mpsc_guard = self.mpsc.rings.load();
1748 mpsc_guard.get(producer_id).ok_or(RingError::PayloadTooLarge)?
1749 }
1750 RingShape::Mpmc => {
1751 mpmc_guard = self.mpmc.rings.load();
1752 mpmc_guard.get(producer_id).ok_or(RingError::PayloadTooLarge)?
1753 }
1754 RingShape::Vyukov => return Err(RingError::LayoutMismatch),
1759 };
1760 let stamp = ord.region.next_stamp(producer_id);
1761 let mut buf = [0u8; SPSC_PAYLOAD_BYTES];
1762 buf[..STAMP_BYTES].copy_from_slice(&stamp.to_le_bytes());
1763 buf[STAMP_BYTES..STAMP_BYTES + payload.len()].copy_from_slice(payload);
1764 let result = ring.try_push(&buf[..STAMP_BYTES + payload.len()]);
1765 ord.region.publish_watermark(producer_id, stamp);
1766 result
1767 }
1768
1769 fn ordered_recv_inner(
1773 &self,
1774 ord: &OrderingState,
1775 shape: RingShape,
1776 consumer_id: usize,
1777 out: &mut [u8],
1778 ) -> Result<(usize, u64), RingError> {
1779 if consumer_id >= CONSUMER_SLOT_CEILING {
1780 return Err(RingError::PayloadTooLarge);
1781 }
1782 if out.len() < STAMPED_PAYLOAD_BYTES {
1783 return Err(RingError::PayloadTooLarge);
1784 }
1785 let mode = ord.region.mode();
1786 match mode {
1787 OrderingMode::Unordered => {
1788 let mut buf = [0u8; SPSC_PAYLOAD_BYTES];
1789 let popped = self.stale_shape()
1793 .filter(|stale| *stale != shape)
1794 .filter(|stale| Self::may_walk_stale(*stale, consumer_id))
1795 .and_then(|stale| {
1796 self.shape_pop(stale, consumer_id, &mut buf).ok()
1797 });
1798 if popped.is_none() {
1799 match shape {
1800 RingShape::Spsc => self.spsc.try_pop(&mut buf),
1801 RingShape::Mpsc => self.mpsc_pop(&mut buf),
1802 RingShape::Mpmc => self.mpmc_pop(consumer_id, &mut buf),
1803 RingShape::Vyukov => Err(RingError::LayoutMismatch),
1804 }?;
1805 }
1806 let stamp = u64::from_le_bytes(
1807 buf[..STAMP_BYTES].try_into().unwrap(),
1808 );
1809 self.note_stamp(ord, consumer_id, mode, stamp);
1810 out[..STAMPED_PAYLOAD_BYTES]
1811 .copy_from_slice(&buf[STAMP_BYTES..]);
1812 Ok((STAMPED_PAYLOAD_BYTES, stamp))
1813 }
1814 OrderingMode::MergeByStamp | OrderingMode::MergeStrict => {
1815 let seen = &ord.seen[consumer_id];
1826 let lease_gen_now = ord.region.lease_generation();
1827 if seen.lease_gen.load(Ordering::Relaxed) != lease_gen_now {
1828 if !ord.region.try_acquire_drainer(
1829 drainer_token(consumer_id),
1830 DRAINER_GRACE_EPOCHS,
1831 ) {
1832 return Err(RingError::NotDrainer);
1833 }
1834 seen.lease_gen.store(lease_gen_now, Ordering::Relaxed);
1835 }
1836 if let Some(stale) = self.stale_shape()
1842 && stale != shape
1843 {
1844 match self.merge_pop(ord, stale, consumer_id, mode, out) {
1845 Ok(result) => return Ok(result),
1846 Err(RingError::Empty) => {}
1847 Err(e) => return Err(e),
1848 }
1849 }
1850 self.merge_pop(ord, shape, consumer_id, mode, out)
1851 }
1852 }
1853 }
1854
1855
1856 fn merge_pop(
1887 &self,
1888 ord: &OrderingState,
1889 shape: RingShape,
1890 consumer_id: usize,
1891 mode: OrderingMode,
1892 out: &mut [u8],
1893 ) -> Result<(usize, u64), RingError> {
1894 let mpsc_guard;
1897 let mpmc_guard;
1898 let rings: &[Arc<SpscRingCore>] = match shape {
1899 RingShape::Spsc => std::slice::from_ref(&self.spsc),
1900 RingShape::Mpsc => {
1901 mpsc_guard = self.mpsc.rings.load();
1902 mpsc_guard.as_slice()
1903 }
1904 RingShape::Mpmc => {
1905 mpmc_guard = self.mpmc.rings.load();
1906 mpmc_guard.as_slice()
1907 }
1908 RingShape::Vyukov => &[],
1909 };
1910 if rings.is_empty() {
1911 return Err(RingError::LayoutMismatch);
1912 }
1913 let gate_lines = self.directory.published()
1918 .min(ord.region.max_producers());
1919 let kind = ord.region.stamp_kind();
1920 loop {
1921 let mut best: Option<(usize, u64)> = None;
1925 let mut any_empty = false;
1926 for (i, ring) in rings.iter().enumerate() {
1927 match ring.peek_slot() {
1928 Some(peek) => {
1929 let s = u64::from_le_bytes(
1930 peek[..STAMP_BYTES].try_into().unwrap(),
1931 );
1932 if best.is_none_or(|(_, bs)| s < bs) {
1933 best = Some((i, s));
1934 }
1935 }
1936 None => any_empty = true,
1937 }
1938 }
1939 let Some((idx, stamp)) = best else {
1940 return Err(RingError::Empty);
1941 };
1942
1943 for line in 0..gate_lines {
1944 if ord.region.in_flight_below(line, stamp) {
1945 return Err(RingError::Empty);
1946 }
1947 }
1948 if mode == OrderingMode::MergeStrict {
1949 for line in 0..gate_lines {
1950 if ord.region.issued(line) != 0
1955 && rings[line.min(rings.len() - 1)].approx_len() == 0
1956 && ord.region.watermark(line) < stamp
1957 {
1958 return Err(RingError::Empty);
1959 }
1960 }
1961 }
1962 if any_empty
1963 && let Some(guard) = kind.freshness_guard()
1964 && stamp_now(kind).wrapping_sub(stamp) < guard
1965 {
1966 std::hint::spin_loop();
1967 continue;
1968 }
1969
1970 let peek = rings[idx].peek_slot().expect(
1971 "single drainer holds the lease; a peeked head cannot vanish",
1972 );
1973 let confirmed_stamp = u64::from_le_bytes(
1974 peek[..STAMP_BYTES].try_into().unwrap(),
1975 );
1976 out[..STAMPED_PAYLOAD_BYTES].copy_from_slice(&peek[STAMP_BYTES..]);
1977 peek.confirm();
1978 self.note_stamp(ord, consumer_id, mode, confirmed_stamp);
1979 return Ok((STAMPED_PAYLOAD_BYTES, confirmed_stamp));
1980 }
1981 }
1982
1983 fn note_stamp(
1990 &self,
1991 ord: &OrderingState,
1992 consumer_id: usize,
1993 mode: OrderingMode,
1994 stamp: u64,
1995 ) {
1996 let line = &ord.seen[consumer_id];
1997 if line.mode_tag.swap(mode as u32, Ordering::Relaxed) != mode as u32 {
1998 line.stamp.store(0, Ordering::Relaxed);
1999 }
2000 let last = line.stamp.load(Ordering::Relaxed);
2001 if stamp < last {
2002 ord.region.record_inversion();
2003 self.ring_sidecar
2004 .push_op(crate::sidecar_ops::ordering::OP_ORDER_INVERSION, 0);
2005 }
2006 line.stamp.store(stamp, Ordering::Relaxed);
2007 }
2008
2009 fn mpsc_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2010 let rings = self.mpsc.rings.load();
2011 self.mpsc_pop_in(&rings, out)
2012 }
2013
2014 fn mpsc_pop_in(
2015 &self,
2016 rings: &[Arc<SpscRingCore>],
2017 out: &mut [u8],
2018 ) -> Result<usize, RingError> {
2019 let n = rings.len();
2020 if n == 0 {
2021 return Err(RingError::Empty);
2022 }
2023 let start = self.mpsc.next_drain.load(Ordering::Relaxed);
2024 for i in 0..n {
2025 let idx = (start + i) % n;
2026 if let Ok(bytes) = rings[idx].try_pop(out) {
2027 self.mpsc.next_drain.store((idx + 1) % n, Ordering::Relaxed);
2028 return Ok(bytes);
2029 }
2030 }
2031 Err(RingError::Empty)
2032 }
2033
2034 fn mpmc_pop(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
2042 let rings = self.mpmc.rings.load();
2043 self.mpmc_pop_in(&rings, consumer_id, out)
2044 }
2045
2046 fn mpmc_pop_in(
2047 &self,
2048 rings: &[Arc<SpscRingCore>],
2049 consumer_id: usize,
2050 out: &mut [u8],
2051 ) -> Result<usize, RingError> {
2052 let cursor_line = self.mpmc.consumer_cursors.get(consumer_id)
2053 .ok_or(RingError::PayloadTooLarge)?;
2054 let me = consumer_id as u16;
2055 let n = rings.len();
2056 if n == 0 {
2057 return Err(RingError::Empty);
2058 }
2059 let start = cursor_line.0.load(Ordering::Relaxed) % n;
2060 let mut stuck: Option<(usize, u16)> = None;
2063 for i in 0..n {
2064 let idx = (start + i) % n;
2065 let (owner, pending) = self.directory.ring_owner(idx);
2066 if owner == me {
2067 if pending != OWNER_NONE
2068 && self.directory.apply_handoff(idx, me).is_some()
2069 {
2070 continue; }
2072 if let Ok(bytes) = rings[idx].try_pop(out) {
2073 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
2074 return Ok(bytes);
2075 }
2076 } else if owner == OWNER_NONE {
2077 if self.directory.try_claim_ring(idx, me)
2078 && let Ok(bytes) = rings[idx].try_pop(out)
2079 {
2080 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
2081 return Ok(bytes);
2082 }
2083 } else if stuck.is_none() && rings[idx].approx_len() > 0 {
2084 stuck = Some((idx, owner));
2085 }
2086 }
2087 if let Some((idx, owner)) = stuck {
2090 let probes = cursor_line.1.fetch_add(1, Ordering::Relaxed);
2091 if probes % 1024 == 1023
2092 && self.directory.try_takeover(idx, owner, me)
2093 && let Ok(bytes) = rings[idx].try_pop(out)
2094 {
2095 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
2096 return Ok(bytes);
2097 }
2098 }
2099 Err(RingError::Empty)
2100 }
2101
2102 pub fn pin_current_shape(&self) -> PinnedRing<'_> {
2109 self.ensure_synced();
2110 let captured_gen = self.pin_generation.load(Ordering::Acquire);
2111 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
2112 PinnedRing {
2113 parent: self,
2114 pinned_generation: captured_gen,
2115 shape,
2116 mpsc_rings: self.mpsc.rings.load_full(),
2117 mpmc_rings: self.mpmc.rings.load_full(),
2118 _not_sync: PhantomData,
2119 }
2120 }
2121
2122 pub fn morph_to(&self, new_shape: RingShape) -> Result<(), RingError> {
2149 self.shape_auto.store(false, Ordering::Relaxed);
2150 self.morph_shape(new_shape)
2151 }
2152
2153 pub(crate) fn morph_shape(&self, new_shape: RingShape) -> Result<(), RingError> {
2157 let old_shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
2158 if old_shape == new_shape {
2159 return Ok(());
2160 }
2161
2162 if self.ordering.is_some() && new_shape == RingShape::Vyukov {
2168 return Err(RingError::LayoutMismatch);
2169 }
2170
2171 let prior_stale = self.stale_shape_tag.load(Ordering::Acquire);
2174 if prior_stale != STALE_NONE
2175 && !self.backing_is_empty(RingShape::from_u8(prior_stale))
2176 {
2177 return Err(RingError::StaleBacklog);
2178 }
2179
2180 self.pin_generation.fetch_add(1, Ordering::AcqRel);
2185 self.stale_shape_tag.store(old_shape as u8, Ordering::Release);
2186 self.shape_tag.store(new_shape as u8, Ordering::Release);
2187 Ok(())
2188 }
2189
2190 fn backing_is_empty(&self, shape: RingShape) -> bool {
2192 match shape {
2193 RingShape::Spsc => self.spsc.approx_len() == 0,
2194 RingShape::Mpsc => self.mpsc.rings.load().iter().all(|r| r.approx_len() == 0),
2195 RingShape::Mpmc => self.mpmc.rings.load().iter().all(|r| r.approx_len() == 0),
2196 RingShape::Vyukov => self.vyukov.approx_len() == 0,
2197 }
2198 }
2199
2200 fn stale_shape(&self) -> Option<RingShape> {
2202 let tag = self.stale_shape_tag.load(Ordering::Acquire);
2203 if tag == STALE_NONE {
2204 None
2205 } else {
2206 Some(RingShape::from_u8(tag))
2207 }
2208 }
2209
2210 fn may_walk_stale(shape: RingShape, consumer_id: usize) -> bool {
2215 match shape {
2216 RingShape::Spsc | RingShape::Mpsc => consumer_id == 0,
2217 RingShape::Mpmc | RingShape::Vyukov => true,
2218 }
2219 }
2220
2221 fn shape_pop(
2223 &self,
2224 shape: RingShape,
2225 consumer_id: usize,
2226 out: &mut [u8],
2227 ) -> Result<usize, RingError> {
2228 match shape {
2229 RingShape::Spsc => self.spsc.try_pop(out),
2230 RingShape::Mpsc => self.mpsc_pop(out),
2231 RingShape::Mpmc => self.mpmc_pop(consumer_id, out),
2232 RingShape::Vyukov => self.vyukov.try_pop(out),
2233 }
2234 }
2235}
2236
2237fn with_suffix(base: &std::path::Path, suffix: &str) -> std::path::PathBuf {
2238 let mut s = base.as_os_str().to_owned();
2239 s.push(suffix);
2240 std::path::PathBuf::from(s)
2241}
2242
2243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2245pub enum AdaptiveError {
2246 TooManyProducers,
2251 TooManyConsumers,
2254 GrowthFailed,
2257}
2258
2259pub struct PinnedRing<'a> {
2266 parent: &'a AdaptiveRing,
2267 pinned_generation: u64,
2268 shape: RingShape,
2269 mpsc_rings: Arc<Vec<Arc<SpscRingCore>>>,
2273 mpmc_rings: Arc<Vec<Arc<SpscRingCore>>>,
2274 _not_sync: PhantomData<Cell<()>>,
2275}
2276
2277impl<'a> PinnedRing<'a> {
2278 pub fn shape(&self) -> RingShape { self.shape }
2280
2281 pub fn recv_signal(&self, shape: RingShape) -> &AtomicU64 {
2298 match shape {
2299 RingShape::Spsc => self.parent.spsc.head_signal(),
2300 RingShape::Mpsc => self.mpsc_rings[0].head_signal(),
2301 RingShape::Mpmc => self.mpmc_rings[0].head_signal(),
2302 RingShape::Vyukov => self.parent.vyukov.next_pop_signal(),
2303 }
2304 }
2305
2306 pub fn is_still_valid(&self) -> bool {
2310 self.parent.pin_generation.load(Ordering::Acquire) == self.pinned_generation
2311 }
2312
2313 pub fn spsc_try_push(&self, payload: &[u8]) -> Result<(), RingError> {
2316 self.parent.spsc.try_push(payload)
2317 }
2318
2319 pub fn spsc_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2321 self.parent.spsc.try_pop(out)
2322 }
2323
2324 pub fn mpsc_try_push(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
2326 let ring = self.mpsc_rings.get(producer_id)
2327 .ok_or(RingError::PayloadTooLarge)?;
2328 ring.try_push(payload)
2329 }
2330
2331 pub fn mpsc_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2334 self.parent.mpsc_pop_in(&self.mpsc_rings, out)
2335 }
2336
2337 pub fn mpmc_try_push(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
2339 let ring = self.mpmc_rings.get(producer_id)
2340 .ok_or(RingError::PayloadTooLarge)?;
2341 ring.try_push(payload)
2342 }
2343
2344 pub fn mpmc_try_pop(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
2348 self.parent.mpmc_pop_in(&self.mpmc_rings, consumer_id, out)
2349 }
2350
2351 pub fn vyukov_try_push(&self, payload: &[u8]) -> Result<(), RingError> {
2353 self.parent.vyukov.try_push(payload)
2354 }
2355
2356 pub fn vyukov_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2358 self.parent.vyukov.try_pop(out)
2359 }
2360
2361 pub fn stamped_try_push(
2367 &self,
2368 producer_id: usize,
2369 payload: &[u8],
2370 ) -> Result<(), RingError> {
2371 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2372 self.parent.stamped_send_inner(ord, self.shape, producer_id, payload)
2373 }
2374
2375 pub fn ordered_try_pop(
2383 &self,
2384 consumer_id: usize,
2385 out: &mut [u8],
2386 ) -> Result<usize, RingError> {
2387 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2388 self.parent
2389 .ordered_recv_inner(ord, self.shape, consumer_id, out)
2390 .map(|(n, _stamp)| n)
2391 }
2392
2393 pub fn ordered_try_pop_with_stamp(
2397 &self,
2398 consumer_id: usize,
2399 out: &mut [u8],
2400 ) -> Result<(usize, u64), RingError> {
2401 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2402 self.parent.ordered_recv_inner(ord, self.shape, consumer_id, out)
2403 }
2404}
2405
2406pub struct PeekedSpscSlot<'a> {
2411 inner: crate::spsc_ring::PeekedSlot<'a>,
2412}
2413
2414impl<'a> PeekedSpscSlot<'a> {
2415 pub fn as_slice(&self) -> &[u8] { self.inner.as_slice() }
2416 pub fn len(&self) -> usize { self.inner.len() }
2417 pub fn is_empty(&self) -> bool { self.inner.is_empty() }
2418 pub fn confirm(self) { self.inner.confirm() }
2419}
2420
2421impl<'a> std::ops::Deref for PeekedSpscSlot<'a> {
2422 type Target = [u8];
2423 fn deref(&self) -> &[u8] { &self.inner }
2424}
2425
2426pub const ADAPTIVE_SPSC_PAYLOAD_BYTES: usize = SPSC_PAYLOAD_BYTES;
2429
2430pub const ADAPTIVE_VYUKOV_PAYLOAD_BYTES: usize = PAYLOAD_BYTES;
2433
2434#[derive(Debug, Clone, Copy)]
2442pub struct PolicyObservation {
2443 pub active_producers: usize,
2444 pub active_consumers: usize,
2445 pub current_shape: RingShape,
2446 pub since_last_morph: std::time::Duration,
2447 pub stamped: bool,
2453}
2454
2455pub trait RingShapePolicy: Send + Sync + 'static {
2463 fn decide(&self, observation: &PolicyObservation) -> Option<RingShape>;
2464}
2465
2466pub struct DefaultRingShapePolicy {
2482 pub hysteresis: std::time::Duration,
2483}
2484
2485impl Default for DefaultRingShapePolicy {
2486 fn default() -> Self {
2487 Self { hysteresis: std::time::Duration::from_millis(100) }
2488 }
2489}
2490
2491impl DefaultRingShapePolicy {
2492 pub fn target_shape(producers: usize, consumers: usize) -> Option<RingShape> {
2493 match (producers, consumers) {
2494 (0, _) | (_, 0) => None,
2495 (1, 1) => Some(RingShape::Spsc),
2496 (_, 1) => Some(RingShape::Mpsc),
2497 (_, _) => Some(RingShape::Mpmc),
2498 }
2499 }
2500}
2501
2502impl RingShapePolicy for DefaultRingShapePolicy {
2503 fn decide(&self, obs: &PolicyObservation) -> Option<RingShape> {
2504 if obs.since_last_morph < self.hysteresis {
2505 return None;
2506 }
2507 let target = Self::target_shape(obs.active_producers, obs.active_consumers)?;
2508 if target == obs.current_shape {
2509 None
2510 } else {
2511 Some(target)
2512 }
2513 }
2514}
2515
2516pub struct QosRingShapePolicy {
2529 pub qos: Arc<crate::qos_policy::QosPolicy>,
2530 pub hysteresis: std::time::Duration,
2531}
2532
2533impl QosRingShapePolicy {
2534 pub fn new(qos: Arc<crate::qos_policy::QosPolicy>) -> Self {
2535 Self { qos, hysteresis: std::time::Duration::from_millis(100) }
2536 }
2537}
2538
2539impl RingShapePolicy for QosRingShapePolicy {
2540 fn decide(&self, obs: &PolicyObservation) -> Option<RingShape> {
2541 if obs.since_last_morph < self.hysteresis {
2542 return None;
2543 }
2544 let target = match self.qos.ordering() {
2545 QosOrdering::GlobalFifo if !obs.stamped => Some(RingShape::Vyukov),
2546 _ => DefaultRingShapePolicy::target_shape(
2547 obs.active_producers,
2548 obs.active_consumers,
2549 ),
2550 }?;
2551 if target == obs.current_shape {
2552 None
2553 } else {
2554 Some(target)
2555 }
2556 }
2557}
2558
2559#[derive(Debug, Clone, Copy)]
2562pub struct OrderingPolicyObservation {
2563 pub inversions_per_sec: f64,
2567 pub current_mode: OrderingMode,
2569 pub declared: QosOrdering,
2571 pub active_producers: usize,
2572 pub active_consumers: usize,
2573 pub since_last_change: std::time::Duration,
2575}
2576
2577pub trait OrderingPolicy: Send + Sync + 'static {
2582 fn decide(&self, observation: &OrderingPolicyObservation) -> Option<OrderingMode>;
2583}
2584
2585pub struct DefaultOrderingPolicy {
2600 pub hysteresis: std::time::Duration,
2601 pub auto_order_threshold: Option<f64>,
2602}
2603
2604impl Default for DefaultOrderingPolicy {
2605 fn default() -> Self {
2606 Self {
2607 hysteresis: std::time::Duration::from_millis(100),
2608 auto_order_threshold: None,
2609 }
2610 }
2611}
2612
2613impl OrderingPolicy for DefaultOrderingPolicy {
2614 fn decide(&self, obs: &OrderingPolicyObservation) -> Option<OrderingMode> {
2615 if obs.since_last_change < self.hysteresis {
2616 return None;
2617 }
2618 match obs.declared {
2619 QosOrdering::GlobalFifo => {
2620 if obs.current_mode == OrderingMode::Unordered {
2621 Some(OrderingMode::MergeByStamp)
2622 } else {
2623 None
2624 }
2625 }
2626 QosOrdering::PerProducer => {
2627 match self.auto_order_threshold {
2628 Some(threshold) => {
2629 if obs.current_mode == OrderingMode::Unordered
2630 && obs.inversions_per_sec > threshold
2631 {
2632 Some(OrderingMode::MergeByStamp)
2633 } else {
2634 None
2635 }
2636 }
2637 None => {
2638 if obs.current_mode != OrderingMode::Unordered {
2639 Some(OrderingMode::Unordered)
2640 } else {
2641 None
2642 }
2643 }
2644 }
2645 }
2646 }
2647 }
2648}
2649
2650pub struct AdaptiveRingSidecar {
2658 handle: Option<std::thread::JoinHandle<()>>,
2659 stop: Arc<std::sync::atomic::AtomicBool>,
2660 morphs_triggered: Arc<std::sync::atomic::AtomicU64>,
2661 ordering_flips: Arc<std::sync::atomic::AtomicU64>,
2662}
2663
2664impl AdaptiveRingSidecar {
2665 pub fn spawn<P: RingShapePolicy>(
2668 ring: Arc<AdaptiveRing>,
2669 policy: P,
2670 scan_interval: std::time::Duration,
2671 ) -> Self {
2672 Self::spawn_gated(
2673 ring,
2674 policy,
2675 scan_interval,
2676 crate::policy_gate::GateConfig::default(),
2677 )
2678 }
2679
2680 pub fn spawn_gated<P: RingShapePolicy>(
2684 ring: Arc<AdaptiveRing>,
2685 policy: P,
2686 scan_interval: std::time::Duration,
2687 gate_cfg: crate::policy_gate::GateConfig,
2688 ) -> Self {
2689 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
2690 let morphs_triggered = Arc::new(std::sync::atomic::AtomicU64::new(0));
2691
2692 let stop_c = stop.clone();
2693 let morphs_c = morphs_triggered.clone();
2694 let handle = std::thread::spawn(move || {
2695 let mut last_morph = std::time::Instant::now();
2696 let mut gate = crate::policy_gate::ConfidenceGate::new(gate_cfg);
2697 while !stop_c.load(Ordering::Acquire) {
2698 let obs = PolicyObservation {
2699 active_producers: ring.active_producers(),
2700 active_consumers: ring.active_consumers(),
2701 current_shape: ring.current_shape(),
2702 since_last_morph: last_morph.elapsed(),
2703 stamped: ring.is_stamped(),
2704 };
2705 if let Some(new_shape) = gate
2709 .observe(policy.decide(&obs).map(|s| ring.contract_filtered_shape(s)))
2710 && ring.morph_shape(new_shape).is_ok()
2711 {
2712 last_morph = std::time::Instant::now();
2713 morphs_c.fetch_add(1, Ordering::Relaxed);
2714 }
2715 std::thread::sleep(scan_interval);
2716 }
2717 });
2718
2719 Self {
2720 handle: Some(handle),
2721 stop,
2722 morphs_triggered,
2723 ordering_flips: Arc::new(std::sync::atomic::AtomicU64::new(0)),
2724 }
2725 }
2726
2727 pub fn spawn_with_qos<P: RingShapePolicy, O: OrderingPolicy>(
2756 ring: Arc<AdaptiveRing>,
2757 shape_policy: P,
2758 ordering_policy: O,
2759 qos: Arc<crate::qos_policy::QosPolicy>,
2760 scan_interval: std::time::Duration,
2761 ) -> Self {
2762 Self::spawn_with_qos_core(
2763 ring,
2764 shape_policy,
2765 ordering_policy,
2766 qos,
2767 scan_interval,
2768 crate::policy_gate::GateConfig::default(),
2769 crate::policy_gate::GateConfig::enabled_with_arity(2),
2770 )
2771 }
2772
2773 pub fn spawn_with_qos_gated<P: RingShapePolicy, O: OrderingPolicy>(
2783 ring: Arc<AdaptiveRing>,
2784 shape_policy: P,
2785 ordering_policy: O,
2786 qos: Arc<crate::qos_policy::QosPolicy>,
2787 scan_interval: std::time::Duration,
2788 gate_cfg: crate::policy_gate::GateConfig,
2789 ) -> Self {
2790 Self::spawn_with_qos_core(
2791 ring,
2792 shape_policy,
2793 ordering_policy,
2794 qos,
2795 scan_interval,
2796 gate_cfg,
2797 gate_cfg,
2798 )
2799 }
2800
2801 fn spawn_with_qos_core<P: RingShapePolicy, O: OrderingPolicy>(
2802 ring: Arc<AdaptiveRing>,
2803 shape_policy: P,
2804 ordering_policy: O,
2805 qos: Arc<crate::qos_policy::QosPolicy>,
2806 scan_interval: std::time::Duration,
2807 shape_gate_cfg: crate::policy_gate::GateConfig,
2808 order_gate_cfg: crate::policy_gate::GateConfig,
2809 ) -> Self {
2810 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
2811 let morphs_triggered = Arc::new(std::sync::atomic::AtomicU64::new(0));
2812 let ordering_flips = Arc::new(std::sync::atomic::AtomicU64::new(0));
2813
2814 let stop_c = stop.clone();
2815 let morphs_c = morphs_triggered.clone();
2816 let flips_c = ordering_flips.clone();
2817 let handle = std::thread::spawn(move || {
2818 let mut last_morph = std::time::Instant::now();
2819 let mut last_flip = std::time::Instant::now();
2820 let mut last_inversions = ring.inversions();
2821 let mut last_scan = std::time::Instant::now();
2822 let mut shape_gate = crate::policy_gate::ConfidenceGate::new(shape_gate_cfg);
2823 let mut order_gate = crate::policy_gate::ConfidenceGate::new(order_gate_cfg);
2824 let mut last_peers = (0usize, 0usize);
2825 let mut first_scan = true;
2826 while !stop_c.load(Ordering::Acquire) {
2827 let obs = PolicyObservation {
2828 active_producers: ring.active_producers(),
2829 active_consumers: ring.active_consumers(),
2830 current_shape: ring.current_shape(),
2831 since_last_morph: last_morph.elapsed(),
2832 stamped: ring.is_stamped(),
2833 };
2834 let peers = (obs.active_producers, obs.active_consumers);
2835 if !first_scan && peers != last_peers {
2836 shape_gate.shock();
2837 order_gate.shock();
2838 }
2839 last_peers = peers;
2840 first_scan = false;
2841
2842 if let Some(new_shape) = shape_gate
2843 .observe(shape_policy.decide(&obs).map(|s| ring.contract_filtered_shape(s)))
2844 && ring.morph_shape(new_shape).is_ok()
2845 {
2846 last_morph = std::time::Instant::now();
2847 morphs_c.fetch_add(1, Ordering::Relaxed);
2848 }
2849
2850 if let Some(current_mode) = ring.ordering_mode() {
2851 ring.tick_drainer_epoch().ok();
2852
2853 let now_inversions = ring.inversions();
2854 let elapsed = last_scan.elapsed().as_secs_f64().max(1e-9);
2855 let rate = now_inversions
2856 .saturating_sub(last_inversions) as f64 / elapsed;
2857 last_inversions = now_inversions;
2858 last_scan = std::time::Instant::now();
2859
2860 let declared = qos.ordering();
2861 let ord_obs = OrderingPolicyObservation {
2862 inversions_per_sec: rate,
2863 current_mode,
2864 declared,
2865 active_producers: obs.active_producers,
2866 active_consumers: obs.active_consumers,
2867 since_last_change: last_flip.elapsed(),
2868 };
2869 let decision = ordering_policy
2870 .decide(&ord_obs)
2871 .filter(|m| *m != current_mode);
2872
2873 let is_auto_arm = declared == QosOrdering::PerProducer
2882 && decision == Some(OrderingMode::MergeByStamp);
2883 let gated = if is_auto_arm {
2884 order_gate.observe(decision)
2885 } else {
2886 decision
2887 };
2888 if let Some(new_mode) = gated
2889 && ring.set_ordering_mode(new_mode).is_ok()
2890 {
2891 last_flip = std::time::Instant::now();
2892 flips_c.fetch_add(1, Ordering::Relaxed);
2893 }
2894 }
2895 std::thread::sleep(scan_interval);
2896 }
2897 });
2898
2899 Self {
2900 handle: Some(handle),
2901 stop,
2902 morphs_triggered,
2903 ordering_flips,
2904 }
2905 }
2906
2907 pub fn morphs_triggered(&self) -> u64 {
2910 self.morphs_triggered.load(Ordering::Acquire)
2911 }
2912
2913 pub fn ordering_flips(&self) -> u64 {
2916 self.ordering_flips.load(Ordering::Acquire)
2917 }
2918
2919 pub fn shutdown(mut self) {
2921 self.stop.store(true, Ordering::Release);
2922 if let Some(h) = self.handle.take() {
2923 h.join().ok();
2924 }
2925 }
2926}
2927
2928impl Drop for AdaptiveRingSidecar {
2929 fn drop(&mut self) {
2930 self.stop.store(true, Ordering::Release);
2931 if let Some(h) = self.handle.take() {
2932 h.join().ok();
2933 }
2934 }
2935}
2936
2937#[cfg(test)]
2938mod tests {
2939 use super::*;
2940
2941 #[test]
2942 fn create_starts_in_spsc_shape() {
2943 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2944 assert_eq!(ring.current_shape(), RingShape::Spsc);
2945 assert_eq!(ring.pin_generation(), 0);
2946 }
2947
2948 #[test]
2949 fn adaptive_dispatch_round_trip_each_shape() {
2950 for shape in [RingShape::Spsc, RingShape::Mpsc, RingShape::Mpmc, RingShape::Vyukov] {
2951 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2952 ring.shape_tag.store(shape as u8, Ordering::Release);
2953 let payload = [0xCDu8; 56];
2957 ring.try_send(0, &payload).unwrap();
2958 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
2959 let n = ring.try_recv(0, &mut out).unwrap();
2960 assert!(n > 0, "shape {:?} delivered zero bytes", shape);
2961 assert_eq!(&out[..payload.len()], &payload[..]);
2962 }
2963 }
2964
2965 #[test]
2966 fn pin_captures_shape_and_generation() {
2967 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2968 let pinned = ring.pin_current_shape();
2969 assert_eq!(pinned.shape(), RingShape::Spsc);
2970 assert!(pinned.is_still_valid());
2971 let pinned = ring.pin_current_shape();
2973 assert!(pinned.is_still_valid());
2974 }
2975
2976 #[test]
2977 fn morph_invalidates_outstanding_pin() {
2978 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2979 let pinned = ring.pin_current_shape();
2980 assert!(pinned.is_still_valid());
2981
2982 ring.morph_to(RingShape::Mpsc).unwrap();
2983 assert!(!pinned.is_still_valid(),
2984 "pin must invalidate after morph_to");
2985 assert_eq!(ring.current_shape(), RingShape::Mpsc);
2986 }
2987
2988 #[test]
2989 fn morph_to_same_shape_is_no_op() {
2990 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2991 let gen_before = ring.pin_generation();
2992 ring.morph_to(RingShape::Spsc).unwrap();
2993 let gen_after = ring.pin_generation();
2994 assert_eq!(gen_before, gen_after,
2995 "morph_to(same shape) must not bump pin_generation");
2996 }
2997
2998 #[test]
2999 fn morph_preserves_in_flight_items_via_stale_walk() {
3000 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3001
3002 for i in 0..3u32 {
3004 let mut buf = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3005 buf[..4].copy_from_slice(&i.to_le_bytes());
3006 ring.try_send(0, &buf).unwrap();
3007 }
3008
3009 ring.morph_to(RingShape::Mpsc).unwrap();
3012 assert_eq!(ring.current_shape(), RingShape::Mpsc);
3013 assert_eq!(ring.approx_len(), 3,
3014 "the stale backlog must stay visible through approx_len");
3015
3016 let mut buf = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3019 buf[..4].copy_from_slice(&99u32.to_le_bytes());
3020 ring.try_send(0, &buf).unwrap();
3021
3022 let mut seen = Vec::new();
3023 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3024 while ring.try_recv(0, &mut out).is_ok() {
3025 seen.push(u32::from_le_bytes(out[..4].try_into().unwrap()));
3026 }
3027 assert_eq!(seen, vec![0u32, 1, 2, 99],
3028 "stale backlog must drain before post-morph items");
3029 assert!(ring.is_empty());
3030 }
3031
3032 #[test]
3033 fn frame_round_trip_all_shapes() {
3034 use crate::frame_ring::FrameClass;
3035 for shape in [RingShape::Spsc, RingShape::Mpsc, RingShape::Mpmc, RingShape::Vyukov] {
3036 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3037 if shape != RingShape::Spsc {
3038 ring.morph_to(shape).unwrap();
3039 }
3040 let small = b"small inline payload".to_vec();
3041 let large = vec![0xABu8; 4000];
3042 assert_eq!(ring.send_frame(0, &small).unwrap(), FrameClass::Inline,
3043 "{shape:?} small should inline");
3044 assert_eq!(ring.send_frame(0, &large).unwrap(), FrameClass::Offset,
3045 "{shape:?} large should offset");
3046 let mut out = Vec::new();
3047 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Inline);
3048 assert_eq!(out, small, "{shape:?} small round-trip");
3049 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Offset);
3050 assert_eq!(out, large, "{shape:?} large round-trip");
3051 }
3052 }
3053
3054 #[test]
3055 fn frame_survives_morph() {
3056 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3057 ring.send_frame(0, b"pre-morph small").unwrap();
3059 ring.send_frame(0, &vec![1u8; 3000]).unwrap();
3060 ring.morph_to(RingShape::Mpsc).unwrap();
3064 ring.send_frame(0, b"post-morph small").unwrap();
3065 ring.send_frame(0, &vec![2u8; 3000]).unwrap();
3066 let mut out = Vec::new();
3067 ring.recv_frame(0, &mut out).unwrap();
3068 assert_eq!(out, b"pre-morph small");
3069 ring.recv_frame(0, &mut out).unwrap();
3070 assert_eq!(out, vec![1u8; 3000]);
3071 ring.recv_frame(0, &mut out).unwrap();
3072 assert_eq!(out, b"post-morph small");
3073 ring.recv_frame(0, &mut out).unwrap();
3074 assert_eq!(out, vec![2u8; 3000]);
3075 }
3076
3077 #[test]
3078 fn frame_override_and_limits() {
3079 use crate::frame_ring::{FrameClass, LayoutHint};
3080 let ring = AdaptiveRing::create_anon(2, 2, 64).unwrap();
3081 let mut out = Vec::new();
3082 assert_eq!(ring.send_frame_as(0, b"tiny", LayoutHint::ForceOffset).unwrap(),
3084 FrameClass::Offset);
3085 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Offset);
3086 assert_eq!(out, b"tiny");
3087 let big = vec![0u8; AdaptiveRing::FRAME_INLINE_BUDGET + 1];
3089 assert_eq!(ring.send_frame_as(0, &big, LayoutHint::ForceInline).unwrap_err(),
3090 RingError::PayloadTooLarge);
3091 let at = vec![7u8; AdaptiveRing::FRAME_INLINE_BUDGET];
3093 assert_eq!(ring.send_frame(0, &at).unwrap(), FrameClass::Inline);
3094 ring.recv_frame(0, &mut out).unwrap();
3095 assert_eq!(out, at);
3096 }
3097
3098 #[test]
3099 fn frame_rejected_on_stamped_ring() {
3100 let ring = AdaptiveRing::create_anon(2, 2, 64)
3103 .unwrap()
3104 .with_ordering_stamps()
3105 .unwrap();
3106 assert_eq!(ring.send_frame(0, b"x").unwrap_err(), RingError::LayoutMismatch);
3107 let mut out = Vec::new();
3108 assert_eq!(ring.recv_frame(0, &mut out).unwrap_err(), RingError::LayoutMismatch);
3109 }
3110
3111 #[test]
3112 fn frame_vyukov_two_thread_mixed_size() {
3113 use std::sync::Arc;
3114 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtOrd};
3115 use std::thread;
3116
3117 const PER: u32 = 5_000;
3118 const PRODUCERS: u32 = 2;
3119 let total = (PER * PRODUCERS) as usize;
3120
3121 let ring = Arc::new(AdaptiveRing::create_anon(2, 2, 256).unwrap());
3127 ring.morph_to(RingShape::Vyukov).unwrap();
3128 let seen: Arc<Vec<AtomicBool>> =
3131 Arc::new((0..total).map(|_| AtomicBool::new(false)).collect());
3132 let received = Arc::new(AtomicUsize::new(0));
3133
3134 let mut prods = Vec::new();
3135 for p in 0..PRODUCERS {
3136 let ring = ring.clone();
3137 prods.push(thread::spawn(move || {
3138 for i in 0..PER {
3139 let id = p * PER + i;
3140 let len = (id as usize % 200) + 4; let mut payload = vec![0u8; len];
3142 payload[0..4].copy_from_slice(&id.to_le_bytes());
3143 for k in 4..len {
3144 payload[k] = id.wrapping_add(k as u32) as u8;
3145 }
3146 while ring.send_frame(p as usize, &payload).is_err() {
3147 std::hint::spin_loop();
3148 }
3149 }
3150 }));
3151 }
3152
3153 let mut cons = Vec::new();
3154 for c in 0..2usize {
3155 let ring = ring.clone();
3156 let seen = seen.clone();
3157 let received = received.clone();
3158 cons.push(thread::spawn(move || {
3159 let mut out = Vec::new();
3160 while received.load(AtOrd::Acquire) < total {
3161 if ring.recv_frame(c, &mut out).is_ok() {
3162 let id = u32::from_le_bytes(out[0..4].try_into().unwrap());
3163 let len = (id as usize % 200) + 4;
3164 assert_eq!(out.len(), len, "id {id} length");
3165 for k in 4..len {
3166 assert_eq!(out[k], id.wrapping_add(k as u32) as u8,
3167 "id {id} byte {k}");
3168 }
3169 let already = seen[id as usize].swap(true, AtOrd::AcqRel);
3170 assert!(!already, "id {id} delivered twice");
3171 received.fetch_add(1, AtOrd::AcqRel);
3172 } else {
3173 std::hint::spin_loop();
3174 }
3175 }
3176 }));
3177 }
3178
3179 for p in prods { p.join().unwrap(); }
3180 for c in cons { c.join().unwrap(); }
3181 assert_eq!(received.load(AtOrd::Acquire), total);
3182 assert!(seen.iter().all(|b| b.load(AtOrd::Acquire)),
3183 "every id delivered exactly once");
3184 }
3185
3186 #[test]
3187 fn second_morph_blocked_until_stale_backlog_drains() {
3188 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3189 ring.try_send(0, &[7u8; 8]).unwrap();
3190 ring.morph_to(RingShape::Mpsc).unwrap();
3191
3192 assert_eq!(ring.morph_to(RingShape::Mpmc).unwrap_err(),
3194 RingError::StaleBacklog);
3195
3196 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3197 ring.try_recv(0, &mut out).unwrap();
3198 ring.morph_to(RingShape::Mpmc).unwrap();
3200 assert_eq!(ring.current_shape(), RingShape::Mpmc);
3201 }
3202
3203 #[test]
3204 fn register_producer_grows_past_hint_and_recycles_slots() {
3205 let ring = AdaptiveRing::create_anon(3, 1, 64).unwrap();
3206 let id0 = ring.register_producer().unwrap();
3207 let id1 = ring.register_producer().unwrap();
3208 let id2 = ring.register_producer().unwrap();
3209 assert_eq!((id0, id1, id2), (0, 1, 2));
3210
3211 let id3 = ring.register_producer().unwrap();
3214 assert_eq!(id3, 3);
3215 assert_eq!(ring.published_producers(), 4);
3216 ring.try_send(id3, &7u64.to_le_bytes()).unwrap();
3217 let mut out = [0u8; 64];
3218 let _c = ring.register_consumer().unwrap();
3222 let n = ring.try_recv(0, &mut out).unwrap();
3223 assert!(n >= 8, "popped record too short: {n}");
3224 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 7);
3225
3226 ring.unregister_producer(id1);
3229 assert_eq!(ring.register_producer().unwrap(), 1);
3230
3231 let pinned = AdaptiveRing::create_anon(2, 1, 64)
3233 .unwrap()
3234 .with_contract(crate::ring_contract::RingContract::from_counts(2, 1));
3235 pinned.register_producer().unwrap();
3236 pinned.register_producer().unwrap();
3237 assert_eq!(pinned.register_producer().unwrap_err(),
3238 AdaptiveError::TooManyProducers);
3239 }
3240
3241 #[test]
3242 fn default_policy_target_shape_per_peer_count() {
3243 assert_eq!(DefaultRingShapePolicy::target_shape(0, 1), None);
3245 assert_eq!(DefaultRingShapePolicy::target_shape(1, 0), None);
3246 assert_eq!(DefaultRingShapePolicy::target_shape(0, 0), None);
3247 assert_eq!(DefaultRingShapePolicy::target_shape(1, 1), Some(RingShape::Spsc));
3249 assert_eq!(DefaultRingShapePolicy::target_shape(2, 1), Some(RingShape::Mpsc));
3251 assert_eq!(DefaultRingShapePolicy::target_shape(8, 1), Some(RingShape::Mpsc));
3252 assert_eq!(DefaultRingShapePolicy::target_shape(1, 2), Some(RingShape::Mpmc));
3254 assert_eq!(DefaultRingShapePolicy::target_shape(4, 4), Some(RingShape::Mpmc));
3255 }
3256
3257 #[test]
3258 fn default_policy_returns_none_during_hysteresis() {
3259 let policy = DefaultRingShapePolicy {
3260 hysteresis: std::time::Duration::from_secs(1),
3261 };
3262 let obs = PolicyObservation {
3263 active_producers: 4,
3264 active_consumers: 4,
3265 current_shape: RingShape::Spsc,
3266 since_last_morph: std::time::Duration::from_millis(50),
3267 stamped: false,
3268 };
3269 assert_eq!(policy.decide(&obs), None);
3271 }
3272
3273 #[test]
3274 fn default_policy_returns_target_after_hysteresis() {
3275 let policy = DefaultRingShapePolicy {
3276 hysteresis: std::time::Duration::from_millis(10),
3277 };
3278 let obs = PolicyObservation {
3279 active_producers: 4,
3280 active_consumers: 4,
3281 current_shape: RingShape::Spsc,
3282 since_last_morph: std::time::Duration::from_secs(1),
3283 stamped: false,
3284 };
3285 assert_eq!(policy.decide(&obs), Some(RingShape::Mpmc));
3286 }
3287
3288 #[test]
3289 fn default_policy_returns_none_when_target_equals_current() {
3290 let policy = DefaultRingShapePolicy::default();
3291 let obs = PolicyObservation {
3292 active_producers: 1,
3293 active_consumers: 1,
3294 current_shape: RingShape::Spsc,
3295 since_last_morph: std::time::Duration::from_secs(1),
3296 stamped: false,
3297 };
3298 assert_eq!(policy.decide(&obs), None);
3299 }
3300
3301 #[test]
3302 fn shape_tracks_peer_counts_and_sidecar_stays_idle() {
3303 let ring = Arc::new(AdaptiveRing::create_anon(4, 4, 64).unwrap());
3304 let policy = DefaultRingShapePolicy {
3305 hysteresis: std::time::Duration::from_millis(5),
3306 };
3307 let sidecar = AdaptiveRingSidecar::spawn(
3308 ring.clone(),
3309 policy,
3310 std::time::Duration::from_millis(10),
3311 );
3312
3313 let _p0 = ring.register_producer().unwrap();
3315 let _c0 = ring.register_consumer().unwrap();
3316 assert_eq!(ring.current_shape(), RingShape::Spsc);
3317
3318 let _p1 = ring.register_producer().unwrap();
3321 assert_eq!(ring.current_shape(), RingShape::Mpsc,
3322 "2nd producer registration must morph to MPSC immediately");
3323
3324 let _c1 = ring.register_consumer().unwrap();
3325 assert_eq!(ring.current_shape(), RingShape::Mpmc,
3326 "2nd consumer registration must morph to MPMC immediately");
3327
3328 std::thread::sleep(std::time::Duration::from_millis(60));
3331 assert_eq!(sidecar.morphs_triggered(), 0,
3332 "register-path morphs left the sidecar nothing to do");
3333
3334 ring.unregister_consumer(1);
3337 ring.unregister_producer(1);
3338 assert_eq!(ring.current_shape(), RingShape::Spsc,
3339 "unregister must morph back down automatically");
3340
3341 sidecar.shutdown();
3342 }
3343
3344 #[test]
3345 fn pinned_native_paths_match_adaptive_paths() {
3346 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3347 let pinned = ring.pin_current_shape();
3348 assert_eq!(pinned.shape(), RingShape::Spsc);
3349
3350 let payload = [0xAAu8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3351 pinned.spsc_try_push(&payload).unwrap();
3352 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3353 let n = pinned.spsc_try_pop(&mut out).unwrap();
3354 assert_eq!(n, ADAPTIVE_SPSC_PAYLOAD_BYTES);
3355 assert_eq!(out, payload);
3356 assert!(pinned.is_still_valid());
3357 }
3358
3359 fn stamped_anon(
3364 max_producers: usize,
3365 max_consumers: usize,
3366 kind: StampKind,
3367 ) -> AdaptiveRing {
3368 AdaptiveRing::create_anon(max_producers, max_consumers, 64)
3369 .unwrap()
3370 .with_ordering_stamps_kind(kind)
3371 .unwrap()
3372 }
3373
3374 #[test]
3375 fn stamped_round_trip_strips_stamp_and_caps_payload() {
3376 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3377 assert!(ring.is_stamped());
3378 assert_eq!(ring.stamp_kind(), Some(StampKind::SharedCounter));
3379 assert_eq!(ring.ordering_mode(), Some(OrderingMode::Unordered));
3380
3381 let too_big = [0u8; STAMPED_PAYLOAD_BYTES + 1];
3383 assert_eq!(ring.try_send(0, &too_big).unwrap_err(),
3384 RingError::PayloadTooLarge);
3385
3386 let payload = [0xC3u8; STAMPED_PAYLOAD_BYTES];
3387 ring.try_send(0, &payload).unwrap();
3388 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3389 let n = ring.try_recv(0, &mut out).unwrap();
3390 assert_eq!(n, STAMPED_PAYLOAD_BYTES,
3391 "stamped recv returns payload bytes only");
3392 assert_eq!(out, payload, "the stamp must be stripped, not leak into the payload");
3393 }
3394
3395 #[test]
3396 fn unstamped_ring_rejects_ordering_calls() {
3397 let ring = AdaptiveRing::create_anon(2, 1, 64).unwrap();
3398 assert!(!ring.is_stamped());
3399 assert_eq!(ring.inversions(), 0);
3400 assert_eq!(ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap_err(),
3401 RingError::NotStamped);
3402 assert_eq!(ring.refresh_watermark(0).unwrap_err(), RingError::NotStamped);
3403 let pinned = ring.pin_current_shape();
3404 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3405 assert_eq!(pinned.ordered_try_pop(0, &mut out).unwrap_err(),
3406 RingError::NotStamped);
3407 assert_eq!(pinned.stamped_try_push(0, &[1u8; 8]).unwrap_err(),
3408 RingError::NotStamped);
3409 }
3410
3411 #[test]
3412 fn stamped_ring_rejects_vyukov_morph_and_vyukov_ring_rejects_stamps() {
3413 let ring = stamped_anon(2, 1, StampKind::Monotonic);
3414 assert_eq!(ring.morph_to(RingShape::Vyukov).unwrap_err(),
3415 RingError::LayoutMismatch);
3416
3417 let vyukov_first = AdaptiveRing::create_anon(2, 1, 64).unwrap();
3418 vyukov_first.morph_to(RingShape::Vyukov).unwrap();
3419 assert!(matches!(
3420 vyukov_first.with_ordering_stamps(),
3421 Err(RingError::LayoutMismatch)
3422 ));
3423 }
3424
3425 #[test]
3426 fn synthetic_interleave_fires_inversion_counter() {
3427 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3428 ring.morph_to(RingShape::Mpsc).unwrap();
3429
3430 ring.try_send(1, &1u64.to_le_bytes()).unwrap();
3435 ring.try_send(0, &2u64.to_le_bytes()).unwrap();
3436
3437 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3438 ring.try_recv(0, &mut out).unwrap();
3439 assert_eq!(ring.inversions(), 0, "first pop has no predecessor");
3440 ring.try_recv(0, &mut out).unwrap();
3441 assert_eq!(ring.inversions(), 1,
3442 "older-after-newer must count as one inversion");
3443 }
3444
3445 #[test]
3446 fn merge_mode_delivers_global_stamp_order() {
3447 let ring = stamped_anon(4, 1, StampKind::SharedCounter);
3448 ring.morph_to(RingShape::Mpsc).unwrap();
3449 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3450
3451 for i in 0..32u64 {
3454 let producer = (i % 4) as usize;
3455 ring.try_send(producer, &i.to_le_bytes()).unwrap();
3456 }
3457
3458 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3459 for expected in 0..32u64 {
3460 let n = ring.try_recv(0, &mut out).unwrap();
3461 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3462 let got = u64::from_le_bytes(out[..8].try_into().unwrap());
3463 assert_eq!(got, expected,
3464 "merge pop must deliver global push order");
3465 }
3466 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty);
3467 assert_eq!(ring.inversions(), 0,
3468 "merged pops must observe zero inversions");
3469 }
3470
3471 #[test]
3472 fn flag_flip_orders_backlog_retroactively_without_loss() {
3473 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3474 ring.morph_to(RingShape::Mpsc).unwrap();
3475
3476 for i in 0..16u64 {
3479 let producer = ((i + 1) % 2) as usize;
3480 ring.try_send(producer, &i.to_le_bytes()).unwrap();
3481 }
3482
3483 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3486 let mut popped = Vec::new();
3487 for _ in 0..2 {
3488 ring.try_recv(0, &mut out).unwrap();
3489 popped.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3490 }
3491 let inversions_before_flip = ring.inversions();
3492 assert!(inversions_before_flip > 0,
3493 "unordered interleave must show inversions before the flip");
3494
3495 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3498 let mut merged = Vec::new();
3499 while let Ok(_n) = ring.try_recv(0, &mut out) {
3500 merged.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3501 }
3502
3503 let mut all = popped.clone();
3505 all.extend(&merged);
3506 all.sort_unstable();
3507 assert_eq!(all, (0..16u64).collect::<Vec<_>>(),
3508 "no item may be lost across the mode flip");
3509 for pair in merged.windows(2) {
3512 assert!(pair[0] < pair[1],
3513 "post-flip pops must be globally ordered: {merged:?}");
3514 }
3515 assert_eq!(ring.inversions(), inversions_before_flip,
3516 "the flip itself and merged pops must add zero inversions");
3517 }
3518
3519 #[test]
3520 fn merge_strict_blocks_on_in_flight_stamp_then_releases() {
3521 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3522 ring.morph_to(RingShape::Mpsc).unwrap();
3523 ring.set_ordering_mode(OrderingMode::MergeStrict).unwrap();
3524 let region = ring.ordering_region().unwrap();
3525
3526 let stalled_stamp = region.next_stamp(1);
3530 ring.try_send(0, &42u64.to_le_bytes()).unwrap();
3532
3533 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3536 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3537 "strict merge must hold the candidate while a smaller stamp is in flight");
3538
3539 region.publish_watermark(1, stalled_stamp);
3545 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3546 "strict watermark gate must hold until the silent producer vouches");
3547 ring.refresh_watermark(1).unwrap();
3550 let n = ring.try_recv(0, &mut out).unwrap();
3551 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3552 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 42);
3553 }
3554
3555 #[test]
3556 fn merge_by_stamp_in_flight_gate_blocks_descheduled_producer() {
3557 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3562 ring.morph_to(RingShape::Mpsc).unwrap();
3563 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3564 let region = ring.ordering_region().unwrap();
3565
3566 let stalled = region.next_stamp(1); ring.try_send(0, &9u64.to_le_bytes()).unwrap();
3568
3569 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3570 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3571 "MergeByStamp must gate on in-flight stamps too");
3572 region.publish_watermark(1, stalled); let n = ring.try_recv(0, &mut out).unwrap();
3574 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3575 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 9);
3576 }
3577
3578 #[test]
3579 fn merge_strict_retired_producer_stops_gating() {
3580 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3581 ring.morph_to(RingShape::Mpsc).unwrap();
3582 ring.set_ordering_mode(OrderingMode::MergeStrict).unwrap();
3583
3584 ring.try_send(1, &1u64.to_le_bytes()).unwrap();
3588 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3589 ring.try_recv(0, &mut out).unwrap();
3590
3591 ring.try_send(0, &2u64.to_le_bytes()).unwrap();
3593 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3594 "strict couples release to the slowest in-use producer");
3595
3596 ring.retire_producer(1).unwrap();
3599 let n = ring.try_recv(0, &mut out).unwrap();
3600 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3601 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 2);
3602 }
3603
3604 #[test]
3605 fn multi_consumer_merge_enforces_single_drainer() {
3606 let ring = stamped_anon(2, 2, StampKind::SharedCounter);
3607 ring.morph_to(RingShape::Mpmc).unwrap();
3608 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3609
3610 for i in 0..4u64 {
3611 ring.try_send((i % 2) as usize, &i.to_le_bytes()).unwrap();
3612 }
3613
3614 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3616 ring.try_recv(0, &mut out).unwrap();
3617 assert_eq!(ring.try_recv(1, &mut out).unwrap_err(),
3619 RingError::NotDrainer);
3620 assert!(ring.release_drainer(0).unwrap());
3622 let n = ring.try_recv(1, &mut out).unwrap();
3623 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3624 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 1,
3625 "the new drainer continues in global stamp order");
3626 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(),
3628 RingError::NotDrainer);
3629 }
3630
3631 #[test]
3632 fn mode_flip_does_not_invalidate_pins() {
3633 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3634 ring.morph_to(RingShape::Mpsc).unwrap();
3635 let pinned = ring.pin_current_shape();
3636 assert!(pinned.is_still_valid());
3637
3638 pinned.stamped_try_push(1, &1u64.to_le_bytes()).unwrap();
3640 pinned.stamped_try_push(0, &2u64.to_le_bytes()).unwrap();
3641
3642 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3646 assert!(pinned.is_still_valid(),
3647 "ordering-mode flips must not invalidate pins");
3648
3649 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3650 pinned.ordered_try_pop(0, &mut out).unwrap();
3651 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 1,
3652 "pinned merge pop must deliver stamp order");
3653 pinned.ordered_try_pop(0, &mut out).unwrap();
3654 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 2);
3655 }
3656
3657 #[test]
3658 fn stamped_items_survive_shape_morphs() {
3659 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3660 for i in 0..3u64 {
3661 ring.try_send(0, &i.to_le_bytes()).unwrap();
3662 }
3663 ring.morph_to(RingShape::Mpsc).unwrap();
3664 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3665 let mut got = Vec::new();
3666 while ring.try_recv(0, &mut out).is_ok() {
3667 got.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3668 }
3669 got.sort_unstable();
3670 assert_eq!(got, vec![0, 1, 2],
3671 "stamped slots must transfer intact across shape morphs");
3672 }
3673
3674 #[test]
3675 fn stamped_file_ring_open_adopts_creator_kind_and_shares_mode() {
3676 let mut prefix = std::env::temp_dir();
3677 prefix.push(format!(
3678 "subetha_stamped_open_{}_{}",
3679 std::process::id(),
3680 std::time::SystemTime::now()
3681 .duration_since(std::time::UNIX_EPOCH)
3682 .map(|d| d.as_nanos()).unwrap_or(0),
3683 ));
3684
3685 let creator = AdaptiveRing::create(&prefix, 2, 1, 64)
3686 .unwrap()
3687 .with_ordering_stamps_kind(StampKind::SharedCounter)
3688 .unwrap();
3689 creator.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3690 creator.try_send(0, &7u64.to_le_bytes()).unwrap();
3691
3692 let opener = AdaptiveRing::open(&prefix, 2, 1, 64)
3693 .unwrap()
3694 .with_ordering_stamps()
3695 .unwrap();
3696 assert_eq!(opener.stamp_kind(), Some(StampKind::SharedCounter),
3697 "opener must adopt the creator's stamp kind");
3698 assert_eq!(opener.ordering_mode(), Some(OrderingMode::MergeByStamp),
3699 "the mode flag must be cross-process (region-resident)");
3700 assert!(matches!(
3702 AdaptiveRing::open(&prefix, 2, 1, 64)
3703 .unwrap()
3704 .with_ordering_stamps_kind(StampKind::Monotonic),
3705 Err(RingError::LayoutMismatch)
3706 ));
3707
3708 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3709 let n = opener.try_recv(0, &mut out).unwrap();
3710 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3711 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 7);
3712
3713 drop(creator);
3714 drop(opener);
3715 for suffix in [".spsc.bin", ".mpsc.0.bin", ".mpsc.1.bin",
3716 ".mpmc.0.bin", ".mpmc.1.bin", ".vyukov.bin",
3717 ".ordering.bin"] {
3718 let mut p = prefix.as_os_str().to_owned();
3719 p.push(suffix);
3720 std::fs::remove_file(std::path::PathBuf::from(p)).ok();
3721 }
3722 }
3723
3724 #[test]
3725 fn offset_frame_crosses_a_shared_file_backing() {
3726 let mut prefix = std::env::temp_dir();
3734 prefix.push(format!(
3735 "subetha_offset_frame_{}_{}",
3736 std::process::id(),
3737 std::time::SystemTime::now()
3738 .duration_since(std::time::UNIX_EPOCH)
3739 .map(|d| d.as_nanos()).unwrap_or(0),
3740 ));
3741
3742 let creator = AdaptiveRing::create(&prefix, 1, 1, 64).unwrap();
3743 let opener = AdaptiveRing::open(&prefix, 1, 1, 64).unwrap();
3744
3745 let small = vec![0xABu8; 20]; let large = vec![0xCDu8; 5000]; assert_eq!(creator.send_frame(0, &small).unwrap(), FrameClass::Inline);
3748 assert_eq!(creator.send_frame(0, &large).unwrap(), FrameClass::Offset);
3749
3750 let mut out = Vec::new();
3751 assert_eq!(opener.recv_frame(0, &mut out).unwrap(), FrameClass::Inline);
3752 assert_eq!(out, small, "inline frame must cross the boundary");
3753 assert_eq!(opener.recv_frame(0, &mut out).unwrap(), FrameClass::Offset,
3754 "offset frame must cross via the shared payload region");
3755 assert_eq!(out, large,
3756 "offset payload must be byte-exact across the boundary");
3757
3758 drop(creator);
3759 drop(opener);
3760 for suffix in [".spsc.bin", ".mpsc.0.bin", ".mpmc.0.bin",
3761 ".vyukov.bin", ".peers.bin", ".frames.bin"] {
3762 let mut p = prefix.as_os_str().to_owned();
3763 p.push(suffix);
3764 std::fs::remove_file(std::path::PathBuf::from(p)).ok();
3765 }
3766 }
3767
3768 #[test]
3769 fn qos_shape_policy_decision_matrix() {
3770 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3771 let policy = QosRingShapePolicy {
3772 qos: qos.clone(),
3773 hysteresis: std::time::Duration::from_millis(0),
3774 };
3775 let obs = |shape, stamped| PolicyObservation {
3776 active_producers: 2,
3777 active_consumers: 1,
3778 current_shape: shape,
3779 since_last_morph: std::time::Duration::from_secs(1),
3780 stamped,
3781 };
3782
3783 assert_eq!(policy.decide(&obs(RingShape::Spsc, false)),
3785 Some(RingShape::Mpsc));
3786 assert_eq!(policy.decide(&obs(RingShape::Mpsc, false)), None);
3787
3788 qos.set_ordering(crate::qos_policy::Ordering::GlobalFifo);
3790 assert_eq!(policy.decide(&obs(RingShape::Mpsc, false)),
3791 Some(RingShape::Vyukov));
3792 assert_eq!(policy.decide(&obs(RingShape::Vyukov, false)), None);
3793
3794 assert_eq!(policy.decide(&obs(RingShape::Spsc, true)),
3797 Some(RingShape::Mpsc));
3798 assert_eq!(policy.decide(&obs(RingShape::Mpsc, true)), None);
3799
3800 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3802 assert_eq!(policy.decide(&obs(RingShape::Vyukov, false)),
3803 Some(RingShape::Mpsc));
3804
3805 let cold = QosRingShapePolicy {
3807 qos: qos.clone(),
3808 hysteresis: std::time::Duration::from_secs(10),
3809 };
3810 let mut o = obs(RingShape::Spsc, false);
3811 o.since_last_morph = std::time::Duration::from_millis(1);
3812 assert_eq!(cold.decide(&o), None);
3813 }
3814
3815 #[test]
3816 fn default_ordering_policy_decision_matrix() {
3817 let obs = |mode, declared, rate, since_ms| OrderingPolicyObservation {
3818 inversions_per_sec: rate,
3819 current_mode: mode,
3820 declared,
3821 active_producers: 2,
3822 active_consumers: 1,
3823 since_last_change: std::time::Duration::from_millis(since_ms),
3824 };
3825 let declarative = DefaultOrderingPolicy {
3826 hysteresis: std::time::Duration::from_millis(0),
3827 auto_order_threshold: None,
3828 };
3829 assert_eq!(
3831 declarative.decide(&obs(
3832 OrderingMode::Unordered, QosOrdering::GlobalFifo, 0.0, 500)),
3833 Some(OrderingMode::MergeByStamp),
3834 );
3835 assert_eq!(
3836 declarative.decide(&obs(
3837 OrderingMode::MergeByStamp, QosOrdering::GlobalFifo, 0.0, 500)),
3838 None,
3839 );
3840 assert_eq!(
3842 declarative.decide(&obs(
3843 OrderingMode::MergeByStamp, QosOrdering::PerProducer, 0.0, 500)),
3844 Some(OrderingMode::Unordered),
3845 );
3846
3847 let auto = DefaultOrderingPolicy {
3848 hysteresis: std::time::Duration::from_millis(0),
3849 auto_order_threshold: Some(100.0),
3850 };
3851 assert_eq!(
3853 auto.decide(&obs(
3854 OrderingMode::Unordered, QosOrdering::PerProducer, 50.0, 500)),
3855 None,
3856 );
3857 assert_eq!(
3859 auto.decide(&obs(
3860 OrderingMode::Unordered, QosOrdering::PerProducer, 250.0, 500)),
3861 Some(OrderingMode::MergeByStamp),
3862 );
3863 assert_eq!(
3865 auto.decide(&obs(
3866 OrderingMode::MergeByStamp, QosOrdering::PerProducer, 0.0, 500)),
3867 None,
3868 );
3869
3870 let cold = DefaultOrderingPolicy {
3872 hysteresis: std::time::Duration::from_secs(10),
3873 auto_order_threshold: Some(1.0),
3874 };
3875 assert_eq!(
3876 cold.decide(&obs(
3877 OrderingMode::Unordered, QosOrdering::GlobalFifo, 1e6, 1)),
3878 None,
3879 );
3880 }
3881
3882 #[test]
3883 fn sidecar_spawn_with_qos_flips_merge_flag_on_declaration() {
3884 let ring = Arc::new(stamped_anon(2, 1, StampKind::SharedCounter));
3885 ring.morph_to(RingShape::Mpsc).unwrap();
3886 let _p0 = ring.register_producer().unwrap();
3887 let _p1 = ring.register_producer().unwrap();
3888 let _c0 = ring.register_consumer().unwrap();
3889
3890 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3891 let sidecar = AdaptiveRingSidecar::spawn_with_qos(
3892 ring.clone(),
3893 QosRingShapePolicy {
3894 qos: qos.clone(),
3895 hysteresis: std::time::Duration::from_millis(5),
3896 },
3897 DefaultOrderingPolicy {
3898 hysteresis: std::time::Duration::from_millis(5),
3899 auto_order_threshold: None,
3900 },
3901 qos.clone(),
3902 std::time::Duration::from_millis(10),
3903 );
3904
3905 qos.set_ordering(crate::qos_policy::Ordering::GlobalFifo);
3908 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
3909 while std::time::Instant::now() < deadline
3910 && ring.ordering_mode() != Some(OrderingMode::MergeByStamp)
3911 {
3912 std::thread::sleep(std::time::Duration::from_millis(10));
3913 }
3914 assert_eq!(ring.ordering_mode(), Some(OrderingMode::MergeByStamp),
3915 "sidecar must arm the merge flag on the GlobalFifo declaration");
3916 assert_eq!(ring.current_shape(), RingShape::Mpsc,
3917 "stamped ring must stay composed (no Vyukov morph)");
3918 assert!(sidecar.ordering_flips() >= 1);
3919
3920 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3922 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
3923 while std::time::Instant::now() < deadline
3924 && ring.ordering_mode() != Some(OrderingMode::Unordered)
3925 {
3926 std::thread::sleep(std::time::Duration::from_millis(10));
3927 }
3928 assert_eq!(ring.ordering_mode(), Some(OrderingMode::Unordered),
3929 "sidecar must disarm when the declaration is withdrawn");
3930 sidecar.shutdown();
3931 }
3932
3933 #[test]
3934 fn default_sidecar_gates_auto_arm_but_still_opens_on_sustained_inversions() {
3935 let ring = Arc::new(stamped_anon(2, 1, StampKind::SharedCounter));
3943 ring.morph_to(RingShape::Mpsc).unwrap();
3944 ring.register_producer().unwrap();
3945 ring.register_producer().unwrap();
3946 ring.register_consumer().unwrap();
3947 ring.set_ordering_mode(OrderingMode::Unordered).unwrap();
3948
3949 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3950 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3951 let sidecar = AdaptiveRingSidecar::spawn_with_qos(
3952 ring.clone(),
3953 DefaultRingShapePolicy::default(),
3954 DefaultOrderingPolicy {
3955 hysteresis: std::time::Duration::from_millis(0),
3956 auto_order_threshold: Some(50.0),
3957 },
3958 qos.clone(),
3959 std::time::Duration::from_millis(5),
3960 );
3961
3962 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
3963 let stop_c = stop.clone();
3964 let r = ring.clone();
3965 let consumer = std::thread::spawn(move || {
3966 let mut out = [0u8; 64];
3967 while !stop_c.load(Ordering::Acquire) {
3968 r.try_recv(0, &mut out).ok();
3969 }
3970 });
3971
3972 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(6);
3973 let mut seq = 0u64;
3974 while std::time::Instant::now() < deadline
3975 && ring.ordering_mode() != Some(OrderingMode::MergeByStamp)
3976 {
3977 ring.try_send(0, &seq.to_le_bytes()).ok();
3978 ring.try_send(1, &seq.to_le_bytes()).ok();
3979 seq += 1;
3980 }
3981 stop.store(true, Ordering::Release);
3982 consumer.join().unwrap();
3983
3984 assert_eq!(ring.ordering_mode(), Some(OrderingMode::MergeByStamp),
3985 "the gated auto-arm must still commit under sustained inversions");
3986 assert_eq!(sidecar.ordering_flips(), 1,
3987 "the one-way auto-arm fires exactly once");
3988 sidecar.shutdown();
3989 }
3990}