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 with_ordering_stamps(self) -> Result<Self, RingError> {
738 self.with_ordering_stamps_impl(None)
739 }
740
741 pub fn with_ordering_stamps_kind(self, kind: StampKind) -> Result<Self, RingError> {
748 self.with_ordering_stamps_impl(Some(kind))
749 }
750
751 fn with_ordering_stamps_impl(
752 mut self,
753 kind: Option<StampKind>,
754 ) -> Result<Self, RingError> {
755 if self.ordering.is_some() {
756 return Ok(self);
757 }
758 if self.current_shape() == RingShape::Vyukov {
759 return Err(RingError::LayoutMismatch);
760 }
761 let lines = crate::peer_directory::PRODUCER_SLOT_CEILING;
768 let region = match &self.backing_id {
769 BackingId::Anon => OrderingRegion::create_anon(
770 lines,
771 kind.unwrap_or_else(default_stamp_kind),
772 )?,
773 BackingId::File { prefix, created } => {
774 let path = with_suffix(prefix, ".ordering.bin");
775 if *created {
776 OrderingRegion::create(
777 &path,
778 lines,
779 kind.unwrap_or_else(default_stamp_kind),
780 )?
781 } else {
782 let region = OrderingRegion::open(&path, lines)?;
783 if let Some(k) = kind
784 && region.stamp_kind() != k
785 {
786 return Err(RingError::LayoutMismatch);
787 }
788 region
789 }
790 }
791 BackingId::Shm { prefix } => {
792 let size = ordering_region_size(lines);
793 let shm = crate::shm_file::ShmFile::create_or_open_named(
794 &format!("{prefix}_ordering"),
795 size,
796 ).map_err(|e| RingError::IoError(e.kind()))?;
797 OrderingRegion::create_shm(
798 shm,
799 lines,
800 kind.unwrap_or_else(default_stamp_kind),
801 )?
802 }
803 };
804 let seen = (0..CONSUMER_SLOT_CEILING).map(|_| SeenLine::new()).collect();
805 self.ordering = Some(Arc::new(OrderingState { region, seen }));
806 Ok(self)
807 }
808
809 pub fn current_shape(&self) -> RingShape {
811 RingShape::from_u8(self.shape_tag.load(Ordering::Acquire))
812 }
813
814 pub fn peek_spsc_slot(&self) -> Option<PeekedSpscSlot<'_>> {
825 if self.current_shape() != RingShape::Spsc {
826 return None;
827 }
828 self.spsc.peek_slot().map(|inner| PeekedSpscSlot { inner })
829 }
830
831 pub fn is_empty(&self) -> bool {
850 if let Some(stale) = self.stale_shape()
851 && !self.backing_is_empty(stale)
852 {
853 return false;
854 }
855 self.backing_is_empty(self.current_shape())
856 }
857
858 pub fn approx_len(&self) -> usize {
863 let stale_len = match self.stale_shape() {
864 Some(stale) if stale != self.current_shape() => {
865 self.backing_approx_len(stale)
866 }
867 _ => 0,
868 };
869 stale_len + self.backing_approx_len(self.current_shape())
870 }
871
872 fn backing_approx_len(&self, shape: RingShape) -> usize {
873 match shape {
874 RingShape::Spsc => self.spsc.approx_len(),
875 RingShape::Mpsc => self.mpsc.rings.load().iter().map(|r| r.approx_len()).sum(),
876 RingShape::Mpmc => self.mpmc.rings.load().iter().map(|r| r.approx_len()).sum(),
877 RingShape::Vyukov => self.vyukov.approx_len(),
878 }
879 }
880
881 pub fn sub_ring_capacity(&self) -> usize {
886 match self.current_shape() {
887 RingShape::Spsc => self.spsc.capacity(),
888 RingShape::Mpsc => self.mpsc.rings.load().first().map(|r| r.capacity()).unwrap_or(0),
889 RingShape::Mpmc => self.mpmc.rings.load().first().map(|r| r.capacity()).unwrap_or(0),
890 RingShape::Vyukov => self.vyukov.capacity(),
891 }
892 }
893
894 pub fn total_slot_capacity(&self) -> usize {
899 match self.current_shape() {
900 RingShape::Spsc => self.spsc.capacity(),
901 RingShape::Mpsc => self.mpsc.rings.load().iter().map(|r| r.capacity()).sum(),
902 RingShape::Mpmc => self.mpmc.rings.load().iter().map(|r| r.capacity()).sum(),
903 RingShape::Vyukov => self.vyukov.capacity(),
904 }
905 }
906
907 pub fn pin_generation(&self) -> u64 {
910 self.pin_generation.load(Ordering::Acquire)
911 }
912
913 pub fn max_producers(&self) -> usize { self.max_producers }
917
918 pub fn max_consumers(&self) -> usize { self.max_consumers }
921
922 pub fn published_producers(&self) -> usize {
925 self.directory.published()
926 }
927
928 pub fn contract(&self) -> crate::ring_contract::RingContract {
933 self.contract.unwrap_or_else(crate::ring_contract::RingContract::unbounded)
934 }
935
936 pub fn with_contract(mut self, contract: crate::ring_contract::RingContract) -> Self {
942 self.contract = Some(contract);
943 self
944 }
945
946 pub fn contract_filtered_shape(&self, target: RingShape) -> RingShape {
958 if self.contract().permits_shape(target) {
959 return target;
960 }
961 if self.ordering.is_none() {
962 RingShape::Vyukov
963 } else {
964 target
965 }
966 }
967
968 fn reshape_for_counts(&self) -> bool {
980 if !self.shape_auto.load(Ordering::Relaxed)
981 || self.current_shape() == RingShape::Vyukov
982 {
983 return true;
984 }
985 let p = self.directory.active_producers();
986 let c = self.directory.active_consumers();
987 if let Some(target) = DefaultRingShapePolicy::target_shape(p, c)
988 && target != self.current_shape()
989 {
990 return self.morph_shape(self.contract_filtered_shape(target)).is_ok();
991 }
992 true
993 }
994
995 pub fn pin_shape(&self) {
1000 self.shape_auto.store(false, Ordering::Relaxed);
1001 }
1002
1003 pub fn resume_auto_shape(&self) {
1009 self.shape_auto.store(true, Ordering::Relaxed);
1010 let p = self.directory.active_producers();
1011 let c = self.directory.active_consumers();
1012 if let Some(target) = DefaultRingShapePolicy::target_shape(p, c) {
1013 self.morph_shape(self.contract_filtered_shape(target)).ok();
1014 }
1015 }
1016
1017 pub fn shape_is_auto(&self) -> bool {
1021 self.shape_auto.load(Ordering::Relaxed)
1022 }
1023
1024 #[inline]
1029 fn ensure_synced(&self) {
1030 let e = self.directory.epoch();
1031 if e != self.synced_epoch.load(Ordering::Relaxed) {
1032 self.sync_topology(e);
1033 }
1034 }
1035
1036 #[cold]
1037 fn sync_topology(&self, epoch: u64) {
1038 self.directory.reap_dead_peers();
1039 let arrays_ok = self.refresh_local_arrays().is_ok();
1040 let shape_ok = self.reshape_for_counts();
1041 if arrays_ok && shape_ok {
1042 self.synced_epoch.store(epoch, Ordering::Relaxed);
1046 }
1047 }
1048
1049 fn refresh_local_arrays(&self) -> Result<(), RingError> {
1054 let published = self.directory.published();
1055 if self.mpsc.rings.load().len() >= published {
1056 return Ok(());
1057 }
1058 let _guard = self.grow_lock.lock();
1059 let cur_mpsc = self.mpsc.rings.load_full();
1060 let cur_mpmc = self.mpmc.rings.load_full();
1061 if cur_mpsc.len() >= published {
1062 return Ok(());
1063 }
1064 let mut mpsc_new = (*cur_mpsc).clone();
1065 let mut mpmc_new = (*cur_mpmc).clone();
1066 for i in cur_mpsc.len()..published {
1067 let (a, b) = self.open_ring_backing(i)?;
1068 mpsc_new.push(a);
1069 mpmc_new.push(b);
1070 }
1071 self.mpsc.rings.store(Arc::new(mpsc_new));
1072 self.mpmc.rings.store(Arc::new(mpmc_new));
1073 self.pin_generation.fetch_add(1, Ordering::AcqRel);
1074 Ok(())
1075 }
1076
1077 fn open_ring_backing(
1081 &self,
1082 i: usize,
1083 ) -> Result<(Arc<SpscRingCore>, Arc<SpscRingCore>), RingError> {
1084 match &self.backing_id {
1085 BackingId::File { prefix, .. } => {
1086 let a = SpscRingCore::open(
1087 with_suffix(prefix, &format!(".mpsc.{i}.bin")), self.capacity)?;
1088 let b = SpscRingCore::open(
1089 with_suffix(prefix, &format!(".mpmc.{i}.bin")), self.capacity)?;
1090 Ok((Arc::new(a), Arc::new(b)))
1091 }
1092 BackingId::Shm { prefix } => {
1093 let size = crate::spsc_ring::spsc_ring_file_size(self.capacity);
1094 let shm_a = crate::shm_file::ShmFile::create_or_open_named(
1095 &format!("{prefix}_mpsc_{i}"), size,
1096 ).map_err(|e| RingError::IoError(e.kind()))?;
1097 let shm_b = crate::shm_file::ShmFile::create_or_open_named(
1098 &format!("{prefix}_mpmc_{i}"), size,
1099 ).map_err(|e| RingError::IoError(e.kind()))?;
1100 let a = SpscRingCore::create_from_shm(shm_a, self.capacity)?;
1101 let b = SpscRingCore::create_from_shm(shm_b, self.capacity)?;
1102 Ok((Arc::new(a), Arc::new(b)))
1103 }
1104 BackingId::Anon => Err(RingError::LayoutMismatch),
1107 }
1108 }
1109
1110 fn create_ring_backing(
1114 &self,
1115 i: usize,
1116 ) -> Result<(Arc<SpscRingCore>, Arc<SpscRingCore>), RingError> {
1117 match &self.backing_id {
1118 BackingId::Anon => {
1119 let a = SpscRingCore::create_anon(self.capacity)?;
1120 let b = SpscRingCore::create_anon(self.capacity)?;
1121 Ok((Arc::new(a), Arc::new(b)))
1122 }
1123 BackingId::File { prefix, .. } => {
1124 let a = SpscRingCore::create(
1125 with_suffix(prefix, &format!(".mpsc.{i}.bin")), self.capacity)?;
1126 let b = SpscRingCore::create(
1127 with_suffix(prefix, &format!(".mpmc.{i}.bin")), self.capacity)?;
1128 Ok((Arc::new(a), Arc::new(b)))
1129 }
1130 BackingId::Shm { prefix } => {
1131 let size = crate::spsc_ring::spsc_ring_file_size(self.capacity);
1132 let shm_a = crate::shm_file::ShmFile::create_or_open_named(
1133 &format!("{prefix}_mpsc_{i}"), size,
1134 ).map_err(|e| RingError::IoError(e.kind()))?;
1135 let shm_b = crate::shm_file::ShmFile::create_or_open_named(
1136 &format!("{prefix}_mpmc_{i}"), size,
1137 ).map_err(|e| RingError::IoError(e.kind()))?;
1138 let a = SpscRingCore::create_from_shm(shm_a, self.capacity)?;
1139 let b = SpscRingCore::create_from_shm(shm_b, self.capacity)?;
1140 Ok((Arc::new(a), Arc::new(b)))
1141 }
1142 }
1143 }
1144
1145 fn grow_rings_to(&self, want: usize) -> Result<(), RingError> {
1150 let _guard = self.grow_lock.lock();
1151 let published = self.directory.published();
1152 let cur_mpsc = self.mpsc.rings.load_full();
1153 let cur_mpmc = self.mpmc.rings.load_full();
1154 let mut mpsc_new = (*cur_mpsc).clone();
1155 let mut mpmc_new = (*cur_mpmc).clone();
1156 for i in cur_mpsc.len()..published {
1159 let (a, b) = self.open_ring_backing(i)?;
1160 mpsc_new.push(a);
1161 mpmc_new.push(b);
1162 }
1163 for i in published..want {
1164 let (a, b) = self.create_ring_backing(i)?;
1165 mpsc_new.push(a);
1166 mpmc_new.push(b);
1167 }
1168 if mpsc_new.len() > cur_mpsc.len() {
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 }
1173 if want > published {
1174 self.directory.publish_rings(want);
1175 }
1176 Ok(())
1177 }
1178
1179 pub fn register_producer(&self) -> Result<usize, AdaptiveError> {
1190 let slot = self.directory.claim_producer_slot()
1191 .ok_or(AdaptiveError::TooManyProducers)?;
1192 if let Some(g) = self.contract
1193 && !g.permits_producer(self.directory.active_producers() - 1)
1194 {
1195 self.directory.release_producer_slot(slot);
1196 return Err(AdaptiveError::TooManyProducers);
1197 }
1198 if slot >= self.directory.published()
1199 && self.grow_rings_to(slot + 1).is_err()
1200 {
1201 self.directory.release_producer_slot(slot);
1202 return Err(AdaptiveError::GrowthFailed);
1203 }
1204 self.ensure_synced();
1205 self.reshape_for_counts();
1206 Ok(slot)
1207 }
1208
1209 pub fn unregister_producer(&self, producer_id: usize) {
1214 self.directory.release_producer_slot(producer_id);
1215 self.reshape_for_counts();
1216 }
1217
1218 pub fn register_consumer(&self) -> Result<usize, AdaptiveError> {
1225 let slot = self.directory.claim_consumer_slot()
1226 .ok_or(AdaptiveError::TooManyConsumers)?;
1227 if let Some(g) = self.contract
1228 && !g.permits_consumer(self.directory.active_consumers() - 1)
1229 {
1230 self.directory.release_consumer_slot(slot);
1231 return Err(AdaptiveError::TooManyConsumers);
1232 }
1233 self.rebalance_ownership();
1234 self.ensure_synced();
1235 self.reshape_for_counts();
1236 Ok(slot)
1237 }
1238
1239 pub fn unregister_consumer(&self, consumer_id: usize) {
1244 let me = consumer_id as u16;
1245 let remaining: Vec<u16> = self.directory.claimed_consumer_slots()
1246 .into_iter()
1247 .filter(|s| *s != me)
1248 .collect();
1249 let n = self.directory.published();
1250 for r in 0..n {
1251 let (owner, _) = self.directory.ring_owner(r);
1252 if owner == me {
1253 match remaining.get(r % remaining.len().max(1)) {
1254 Some(to) => self.directory.transfer_ring(r, me, *to),
1255 None => self.directory.transfer_ring(r, me, OWNER_NONE),
1256 }
1257 }
1258 }
1259 self.directory.release_consumer_slot(consumer_id);
1260 self.reshape_for_counts();
1261 }
1262
1263 fn rebalance_ownership(&self) {
1269 let slots = self.directory.claimed_consumer_slots();
1270 if slots.is_empty() {
1271 return;
1272 }
1273 let n = self.directory.published();
1274 for r in 0..n {
1275 let desired = slots[r % slots.len()];
1276 let (owner, pending) = self.directory.ring_owner(r);
1277 if owner == desired {
1278 continue;
1279 }
1280 if owner == OWNER_NONE {
1281 self.directory.try_claim_ring(r, desired);
1282 } else if pending != desired {
1283 self.directory.request_handoff(r, desired);
1284 }
1285 }
1286 }
1287
1288 pub fn active_producers(&self) -> usize {
1290 self.directory.active_producers()
1291 }
1292
1293 pub fn active_consumers(&self) -> usize {
1295 self.directory.active_consumers()
1296 }
1297
1298 pub fn is_stamped(&self) -> bool {
1300 self.ordering.is_some()
1301 }
1302
1303 pub fn stamp_kind(&self) -> Option<StampKind> {
1305 self.ordering.as_ref().map(|o| o.region.stamp_kind())
1306 }
1307
1308 pub fn ordering_mode(&self) -> Option<OrderingMode> {
1313 self.ordering.as_ref().map(|o| o.region.mode())
1314 }
1315
1316 pub fn set_ordering_mode(&self, mode: OrderingMode) -> Result<(), RingError> {
1322 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1323 ord.region.set_mode(mode);
1324 Ok(())
1325 }
1326
1327 pub fn inversions(&self) -> u64 {
1330 self.ordering.as_ref().map(|o| o.region.inversions()).unwrap_or(0)
1331 }
1332
1333 pub fn refresh_watermark(&self, producer_id: usize) -> Result<(), RingError> {
1336 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1337 if producer_id >= ord.region.max_producers() {
1338 return Err(RingError::PayloadTooLarge);
1339 }
1340 ord.region.refresh_watermark(producer_id);
1341 Ok(())
1342 }
1343
1344 pub fn retire_producer(&self, producer_id: usize) -> Result<(), RingError> {
1349 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1350 if producer_id >= ord.region.max_producers() {
1351 return Err(RingError::PayloadTooLarge);
1352 }
1353 ord.region.retire_producer(producer_id);
1354 Ok(())
1355 }
1356
1357 pub fn release_drainer(&self, consumer_id: usize) -> Result<bool, RingError> {
1361 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1362 Ok(ord.region.release_drainer(drainer_token(consumer_id)))
1363 }
1364
1365 pub fn tick_drainer_epoch(&self) -> Result<u64, RingError> {
1370 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1371 Ok(ord.region.tick_drainer_epoch())
1372 }
1373
1374 pub fn ordering_region(&self) -> Option<&OrderingRegion> {
1379 self.ordering.as_ref().map(|o| &o.region)
1380 }
1381
1382 pub fn try_send(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
1394 self.ensure_synced();
1395 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1396 if let Some(ord) = &self.ordering {
1397 return self.stamped_send_inner(ord, shape, producer_id, payload);
1398 }
1399 match shape {
1400 RingShape::Spsc => self.spsc.try_push(payload),
1401 RingShape::Mpsc => {
1402 let rings = self.mpsc.rings.load();
1403 let ring = rings.get(producer_id)
1404 .ok_or(RingError::PayloadTooLarge)?; ring.try_push(payload)
1406 }
1407 RingShape::Mpmc => {
1408 let rings = self.mpmc.rings.load();
1409 let ring = rings.get(producer_id)
1410 .ok_or(RingError::PayloadTooLarge)?;
1411 ring.try_push(payload)
1412 }
1413 RingShape::Vyukov => self.vyukov.try_push(payload),
1414 }
1415 }
1416
1417 pub fn try_recv(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
1427 self.ensure_synced();
1428 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1429 if let Some(ord) = &self.ordering {
1430 return self.ordered_recv_inner(ord, shape, consumer_id, out)
1431 .map(|(n, _stamp)| n);
1432 }
1433 if let Some(stale) = self.stale_shape()
1437 && stale != shape
1438 && Self::may_walk_stale(stale, consumer_id)
1439 && let Ok(n) = self.shape_pop(stale, consumer_id, out)
1440 {
1441 return Ok(n);
1442 }
1443 self.shape_pop(shape, consumer_id, out)
1444 }
1445
1446 pub const FRAME_INLINE_BUDGET: usize = PAYLOAD_BYTES - 5;
1452
1453 pub const FRAME_DEFAULT_BLOCK_SIZE: usize = 8192;
1458
1459 pub fn with_frames(self, block_size: usize, block_count: usize) -> Self {
1465 self.frame_region.get_or_init(|| {
1466 Arc::new(
1467 FrameRegion::create_anon(block_size, block_count)
1468 .expect("frame region create"),
1469 )
1470 });
1471 self
1472 }
1473
1474 fn frame_region(&self) -> &FrameRegion {
1475 self.frame_region.get_or_init(|| {
1476 let blocks = self.spsc.capacity().max(16);
1477 Arc::new(
1478 FrameRegion::create_anon(Self::FRAME_DEFAULT_BLOCK_SIZE, blocks)
1479 .expect("frame region create"),
1480 )
1481 })
1482 }
1483
1484 pub fn send_frame(&self, producer_id: usize, payload: &[u8])
1498 -> Result<FrameClass, RingError>
1499 {
1500 self.send_frame_as(producer_id, payload, LayoutHint::Auto)
1501 }
1502
1503 pub fn send_frame_as(&self, producer_id: usize, payload: &[u8], hint: LayoutHint)
1506 -> Result<FrameClass, RingError>
1507 {
1508 if self.ordering.is_some() {
1509 return Err(RingError::LayoutMismatch);
1510 }
1511 let inline = match hint {
1512 LayoutHint::ForceInline => {
1513 if payload.len() > Self::FRAME_INLINE_BUDGET {
1514 return Err(RingError::PayloadTooLarge);
1515 }
1516 true
1517 }
1518 LayoutHint::ForceOffset => false,
1519 LayoutHint::Auto => payload.len() <= Self::FRAME_INLINE_BUDGET,
1520 };
1521 let len = payload.len() as u32;
1522 if inline {
1523 let mut buf = [0u8; PAYLOAD_BYTES];
1526 buf[0] = FrameClass::Inline as u8;
1527 buf[1..5].copy_from_slice(&len.to_le_bytes());
1528 buf[5..5 + payload.len()].copy_from_slice(payload);
1529 self.try_send(producer_id, &buf[..5 + payload.len()])?;
1530 Ok(FrameClass::Inline)
1531 } else {
1532 let region = self.frame_region();
1533 if payload.len() > region.block_size() {
1534 return Err(RingError::PayloadTooLarge);
1535 }
1536 let idx = region.alloc().ok_or(RingError::Full)?;
1537 region.write_block(idx, payload);
1538 let mut buf = [0u8; 9];
1540 buf[0] = FrameClass::Offset as u8;
1541 buf[1..5].copy_from_slice(&len.to_le_bytes());
1542 buf[5..9].copy_from_slice(&idx.to_le_bytes());
1543 match self.try_send(producer_id, &buf) {
1544 Ok(()) => Ok(FrameClass::Offset),
1545 Err(e) => {
1546 region.free(idx);
1549 Err(e)
1550 }
1551 }
1552 }
1553 }
1554
1555 pub fn recv_frame(&self, consumer_id: usize, out: &mut Vec<u8>)
1563 -> Result<FrameClass, RingError>
1564 {
1565 if self.ordering.is_some() {
1566 return Err(RingError::LayoutMismatch);
1567 }
1568 let mut slot = [0u8; SPSC_PAYLOAD_BYTES];
1570 self.try_recv(consumer_id, &mut slot)?;
1571 let len = u32::from_le_bytes([slot[1], slot[2], slot[3], slot[4]]) as usize;
1572 out.clear();
1573 if slot[0] == FrameClass::Inline as u8 {
1574 out.extend_from_slice(&slot[5..5 + len]);
1575 Ok(FrameClass::Inline)
1576 } else {
1577 let idx = u32::from_le_bytes([slot[5], slot[6], slot[7], slot[8]]);
1578 let region = self.frame_region();
1579 region.read_block_into(idx, len, out);
1580 region.free(idx);
1581 Ok(FrameClass::Offset)
1582 }
1583 }
1584
1585 pub fn try_recv_with_stamp(
1591 &self,
1592 consumer_id: usize,
1593 out: &mut [u8],
1594 ) -> Result<(usize, u64), RingError> {
1595 let ord = self.ordering.as_ref().ok_or(RingError::NotStamped)?;
1596 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1597 self.ordered_recv_inner(ord, shape, consumer_id, out)
1598 }
1599
1600 fn stamped_send_inner(
1607 &self,
1608 ord: &OrderingState,
1609 shape: RingShape,
1610 producer_id: usize,
1611 payload: &[u8],
1612 ) -> Result<(), RingError> {
1613 if payload.len() > STAMPED_PAYLOAD_BYTES {
1614 return Err(RingError::PayloadTooLarge);
1615 }
1616 if producer_id >= ord.region.max_producers() {
1617 return Err(RingError::PayloadTooLarge);
1618 }
1619 let mpsc_guard;
1620 let mpmc_guard;
1621 let ring: &SpscRingCore = match shape {
1622 RingShape::Spsc => &self.spsc,
1623 RingShape::Mpsc => {
1624 mpsc_guard = self.mpsc.rings.load();
1625 mpsc_guard.get(producer_id).ok_or(RingError::PayloadTooLarge)?
1626 }
1627 RingShape::Mpmc => {
1628 mpmc_guard = self.mpmc.rings.load();
1629 mpmc_guard.get(producer_id).ok_or(RingError::PayloadTooLarge)?
1630 }
1631 RingShape::Vyukov => return Err(RingError::LayoutMismatch),
1636 };
1637 let stamp = ord.region.next_stamp(producer_id);
1638 let mut buf = [0u8; SPSC_PAYLOAD_BYTES];
1639 buf[..STAMP_BYTES].copy_from_slice(&stamp.to_le_bytes());
1640 buf[STAMP_BYTES..STAMP_BYTES + payload.len()].copy_from_slice(payload);
1641 let result = ring.try_push(&buf[..STAMP_BYTES + payload.len()]);
1642 ord.region.publish_watermark(producer_id, stamp);
1643 result
1644 }
1645
1646 fn ordered_recv_inner(
1650 &self,
1651 ord: &OrderingState,
1652 shape: RingShape,
1653 consumer_id: usize,
1654 out: &mut [u8],
1655 ) -> Result<(usize, u64), RingError> {
1656 if consumer_id >= CONSUMER_SLOT_CEILING {
1657 return Err(RingError::PayloadTooLarge);
1658 }
1659 if out.len() < STAMPED_PAYLOAD_BYTES {
1660 return Err(RingError::PayloadTooLarge);
1661 }
1662 let mode = ord.region.mode();
1663 match mode {
1664 OrderingMode::Unordered => {
1665 let mut buf = [0u8; SPSC_PAYLOAD_BYTES];
1666 let popped = self.stale_shape()
1670 .filter(|stale| *stale != shape)
1671 .filter(|stale| Self::may_walk_stale(*stale, consumer_id))
1672 .and_then(|stale| {
1673 self.shape_pop(stale, consumer_id, &mut buf).ok()
1674 });
1675 if popped.is_none() {
1676 match shape {
1677 RingShape::Spsc => self.spsc.try_pop(&mut buf),
1678 RingShape::Mpsc => self.mpsc_pop(&mut buf),
1679 RingShape::Mpmc => self.mpmc_pop(consumer_id, &mut buf),
1680 RingShape::Vyukov => Err(RingError::LayoutMismatch),
1681 }?;
1682 }
1683 let stamp = u64::from_le_bytes(
1684 buf[..STAMP_BYTES].try_into().unwrap(),
1685 );
1686 self.note_stamp(ord, consumer_id, mode, stamp);
1687 out[..STAMPED_PAYLOAD_BYTES]
1688 .copy_from_slice(&buf[STAMP_BYTES..]);
1689 Ok((STAMPED_PAYLOAD_BYTES, stamp))
1690 }
1691 OrderingMode::MergeByStamp | OrderingMode::MergeStrict => {
1692 let seen = &ord.seen[consumer_id];
1703 let lease_gen_now = ord.region.lease_generation();
1704 if seen.lease_gen.load(Ordering::Relaxed) != lease_gen_now {
1705 if !ord.region.try_acquire_drainer(
1706 drainer_token(consumer_id),
1707 DRAINER_GRACE_EPOCHS,
1708 ) {
1709 return Err(RingError::NotDrainer);
1710 }
1711 seen.lease_gen.store(lease_gen_now, Ordering::Relaxed);
1712 }
1713 if let Some(stale) = self.stale_shape()
1719 && stale != shape
1720 {
1721 match self.merge_pop(ord, stale, consumer_id, mode, out) {
1722 Ok(result) => return Ok(result),
1723 Err(RingError::Empty) => {}
1724 Err(e) => return Err(e),
1725 }
1726 }
1727 self.merge_pop(ord, shape, consumer_id, mode, out)
1728 }
1729 }
1730 }
1731
1732
1733 fn merge_pop(
1764 &self,
1765 ord: &OrderingState,
1766 shape: RingShape,
1767 consumer_id: usize,
1768 mode: OrderingMode,
1769 out: &mut [u8],
1770 ) -> Result<(usize, u64), RingError> {
1771 let mpsc_guard;
1774 let mpmc_guard;
1775 let rings: &[Arc<SpscRingCore>] = match shape {
1776 RingShape::Spsc => std::slice::from_ref(&self.spsc),
1777 RingShape::Mpsc => {
1778 mpsc_guard = self.mpsc.rings.load();
1779 mpsc_guard.as_slice()
1780 }
1781 RingShape::Mpmc => {
1782 mpmc_guard = self.mpmc.rings.load();
1783 mpmc_guard.as_slice()
1784 }
1785 RingShape::Vyukov => &[],
1786 };
1787 if rings.is_empty() {
1788 return Err(RingError::LayoutMismatch);
1789 }
1790 let gate_lines = self.directory.published()
1795 .min(ord.region.max_producers());
1796 let kind = ord.region.stamp_kind();
1797 loop {
1798 let mut best: Option<(usize, u64)> = None;
1802 let mut any_empty = false;
1803 for (i, ring) in rings.iter().enumerate() {
1804 match ring.peek_slot() {
1805 Some(peek) => {
1806 let s = u64::from_le_bytes(
1807 peek[..STAMP_BYTES].try_into().unwrap(),
1808 );
1809 if best.is_none_or(|(_, bs)| s < bs) {
1810 best = Some((i, s));
1811 }
1812 }
1813 None => any_empty = true,
1814 }
1815 }
1816 let Some((idx, stamp)) = best else {
1817 return Err(RingError::Empty);
1818 };
1819
1820 for line in 0..gate_lines {
1821 if ord.region.in_flight_below(line, stamp) {
1822 return Err(RingError::Empty);
1823 }
1824 }
1825 if mode == OrderingMode::MergeStrict {
1826 for line in 0..gate_lines {
1827 if ord.region.issued(line) != 0
1832 && rings[line.min(rings.len() - 1)].approx_len() == 0
1833 && ord.region.watermark(line) < stamp
1834 {
1835 return Err(RingError::Empty);
1836 }
1837 }
1838 }
1839 if any_empty
1840 && let Some(guard) = kind.freshness_guard()
1841 && stamp_now(kind).wrapping_sub(stamp) < guard
1842 {
1843 std::hint::spin_loop();
1844 continue;
1845 }
1846
1847 let peek = rings[idx].peek_slot().expect(
1848 "single drainer holds the lease; a peeked head cannot vanish",
1849 );
1850 let confirmed_stamp = u64::from_le_bytes(
1851 peek[..STAMP_BYTES].try_into().unwrap(),
1852 );
1853 out[..STAMPED_PAYLOAD_BYTES].copy_from_slice(&peek[STAMP_BYTES..]);
1854 peek.confirm();
1855 self.note_stamp(ord, consumer_id, mode, confirmed_stamp);
1856 return Ok((STAMPED_PAYLOAD_BYTES, confirmed_stamp));
1857 }
1858 }
1859
1860 fn note_stamp(
1867 &self,
1868 ord: &OrderingState,
1869 consumer_id: usize,
1870 mode: OrderingMode,
1871 stamp: u64,
1872 ) {
1873 let line = &ord.seen[consumer_id];
1874 if line.mode_tag.swap(mode as u32, Ordering::Relaxed) != mode as u32 {
1875 line.stamp.store(0, Ordering::Relaxed);
1876 }
1877 let last = line.stamp.load(Ordering::Relaxed);
1878 if stamp < last {
1879 ord.region.record_inversion();
1880 self.ring_sidecar
1881 .push_op(crate::sidecar_ops::ordering::OP_ORDER_INVERSION, 0);
1882 }
1883 line.stamp.store(stamp, Ordering::Relaxed);
1884 }
1885
1886 fn mpsc_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
1887 let rings = self.mpsc.rings.load();
1888 self.mpsc_pop_in(&rings, out)
1889 }
1890
1891 fn mpsc_pop_in(
1892 &self,
1893 rings: &[Arc<SpscRingCore>],
1894 out: &mut [u8],
1895 ) -> Result<usize, RingError> {
1896 let n = rings.len();
1897 if n == 0 {
1898 return Err(RingError::Empty);
1899 }
1900 let start = self.mpsc.next_drain.load(Ordering::Relaxed);
1901 for i in 0..n {
1902 let idx = (start + i) % n;
1903 if let Ok(bytes) = rings[idx].try_pop(out) {
1904 self.mpsc.next_drain.store((idx + 1) % n, Ordering::Relaxed);
1905 return Ok(bytes);
1906 }
1907 }
1908 Err(RingError::Empty)
1909 }
1910
1911 fn mpmc_pop(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
1919 let rings = self.mpmc.rings.load();
1920 self.mpmc_pop_in(&rings, consumer_id, out)
1921 }
1922
1923 fn mpmc_pop_in(
1924 &self,
1925 rings: &[Arc<SpscRingCore>],
1926 consumer_id: usize,
1927 out: &mut [u8],
1928 ) -> Result<usize, RingError> {
1929 let cursor_line = self.mpmc.consumer_cursors.get(consumer_id)
1930 .ok_or(RingError::PayloadTooLarge)?;
1931 let me = consumer_id as u16;
1932 let n = rings.len();
1933 if n == 0 {
1934 return Err(RingError::Empty);
1935 }
1936 let start = cursor_line.0.load(Ordering::Relaxed) % n;
1937 let mut stuck: Option<(usize, u16)> = None;
1940 for i in 0..n {
1941 let idx = (start + i) % n;
1942 let (owner, pending) = self.directory.ring_owner(idx);
1943 if owner == me {
1944 if pending != OWNER_NONE
1945 && self.directory.apply_handoff(idx, me).is_some()
1946 {
1947 continue; }
1949 if let Ok(bytes) = rings[idx].try_pop(out) {
1950 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
1951 return Ok(bytes);
1952 }
1953 } else if owner == OWNER_NONE {
1954 if self.directory.try_claim_ring(idx, me)
1955 && let Ok(bytes) = rings[idx].try_pop(out)
1956 {
1957 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
1958 return Ok(bytes);
1959 }
1960 } else if stuck.is_none() && rings[idx].approx_len() > 0 {
1961 stuck = Some((idx, owner));
1962 }
1963 }
1964 if let Some((idx, owner)) = stuck {
1967 let probes = cursor_line.1.fetch_add(1, Ordering::Relaxed);
1968 if probes % 1024 == 1023
1969 && self.directory.try_takeover(idx, owner, me)
1970 && let Ok(bytes) = rings[idx].try_pop(out)
1971 {
1972 cursor_line.0.store((idx + 1) % n, Ordering::Relaxed);
1973 return Ok(bytes);
1974 }
1975 }
1976 Err(RingError::Empty)
1977 }
1978
1979 pub fn pin_current_shape(&self) -> PinnedRing<'_> {
1986 self.ensure_synced();
1987 let captured_gen = self.pin_generation.load(Ordering::Acquire);
1988 let shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
1989 PinnedRing {
1990 parent: self,
1991 pinned_generation: captured_gen,
1992 shape,
1993 mpsc_rings: self.mpsc.rings.load_full(),
1994 mpmc_rings: self.mpmc.rings.load_full(),
1995 _not_sync: PhantomData,
1996 }
1997 }
1998
1999 pub fn morph_to(&self, new_shape: RingShape) -> Result<(), RingError> {
2026 self.shape_auto.store(false, Ordering::Relaxed);
2027 self.morph_shape(new_shape)
2028 }
2029
2030 pub(crate) fn morph_shape(&self, new_shape: RingShape) -> Result<(), RingError> {
2034 let old_shape = RingShape::from_u8(self.shape_tag.load(Ordering::Acquire));
2035 if old_shape == new_shape {
2036 return Ok(());
2037 }
2038
2039 if self.ordering.is_some() && new_shape == RingShape::Vyukov {
2045 return Err(RingError::LayoutMismatch);
2046 }
2047
2048 let prior_stale = self.stale_shape_tag.load(Ordering::Acquire);
2051 if prior_stale != STALE_NONE
2052 && !self.backing_is_empty(RingShape::from_u8(prior_stale))
2053 {
2054 return Err(RingError::StaleBacklog);
2055 }
2056
2057 self.pin_generation.fetch_add(1, Ordering::AcqRel);
2062 self.stale_shape_tag.store(old_shape as u8, Ordering::Release);
2063 self.shape_tag.store(new_shape as u8, Ordering::Release);
2064 Ok(())
2065 }
2066
2067 fn backing_is_empty(&self, shape: RingShape) -> bool {
2069 match shape {
2070 RingShape::Spsc => self.spsc.approx_len() == 0,
2071 RingShape::Mpsc => self.mpsc.rings.load().iter().all(|r| r.approx_len() == 0),
2072 RingShape::Mpmc => self.mpmc.rings.load().iter().all(|r| r.approx_len() == 0),
2073 RingShape::Vyukov => self.vyukov.approx_len() == 0,
2074 }
2075 }
2076
2077 fn stale_shape(&self) -> Option<RingShape> {
2079 let tag = self.stale_shape_tag.load(Ordering::Acquire);
2080 if tag == STALE_NONE {
2081 None
2082 } else {
2083 Some(RingShape::from_u8(tag))
2084 }
2085 }
2086
2087 fn may_walk_stale(shape: RingShape, consumer_id: usize) -> bool {
2092 match shape {
2093 RingShape::Spsc | RingShape::Mpsc => consumer_id == 0,
2094 RingShape::Mpmc | RingShape::Vyukov => true,
2095 }
2096 }
2097
2098 fn shape_pop(
2100 &self,
2101 shape: RingShape,
2102 consumer_id: usize,
2103 out: &mut [u8],
2104 ) -> Result<usize, RingError> {
2105 match shape {
2106 RingShape::Spsc => self.spsc.try_pop(out),
2107 RingShape::Mpsc => self.mpsc_pop(out),
2108 RingShape::Mpmc => self.mpmc_pop(consumer_id, out),
2109 RingShape::Vyukov => self.vyukov.try_pop(out),
2110 }
2111 }
2112}
2113
2114fn with_suffix(base: &std::path::Path, suffix: &str) -> std::path::PathBuf {
2115 let mut s = base.as_os_str().to_owned();
2116 s.push(suffix);
2117 std::path::PathBuf::from(s)
2118}
2119
2120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2122pub enum AdaptiveError {
2123 TooManyProducers,
2128 TooManyConsumers,
2131 GrowthFailed,
2134}
2135
2136pub struct PinnedRing<'a> {
2143 parent: &'a AdaptiveRing,
2144 pinned_generation: u64,
2145 shape: RingShape,
2146 mpsc_rings: Arc<Vec<Arc<SpscRingCore>>>,
2150 mpmc_rings: Arc<Vec<Arc<SpscRingCore>>>,
2151 _not_sync: PhantomData<Cell<()>>,
2152}
2153
2154impl<'a> PinnedRing<'a> {
2155 pub fn shape(&self) -> RingShape { self.shape }
2157
2158 pub fn recv_signal(&self, shape: RingShape) -> &AtomicU64 {
2175 match shape {
2176 RingShape::Spsc => self.parent.spsc.head_signal(),
2177 RingShape::Mpsc => self.mpsc_rings[0].head_signal(),
2178 RingShape::Mpmc => self.mpmc_rings[0].head_signal(),
2179 RingShape::Vyukov => self.parent.vyukov.next_pop_signal(),
2180 }
2181 }
2182
2183 pub fn is_still_valid(&self) -> bool {
2187 self.parent.pin_generation.load(Ordering::Acquire) == self.pinned_generation
2188 }
2189
2190 pub fn spsc_try_push(&self, payload: &[u8]) -> Result<(), RingError> {
2193 self.parent.spsc.try_push(payload)
2194 }
2195
2196 pub fn spsc_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2198 self.parent.spsc.try_pop(out)
2199 }
2200
2201 pub fn mpsc_try_push(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
2203 let ring = self.mpsc_rings.get(producer_id)
2204 .ok_or(RingError::PayloadTooLarge)?;
2205 ring.try_push(payload)
2206 }
2207
2208 pub fn mpsc_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2211 self.parent.mpsc_pop_in(&self.mpsc_rings, out)
2212 }
2213
2214 pub fn mpmc_try_push(&self, producer_id: usize, payload: &[u8]) -> Result<(), RingError> {
2216 let ring = self.mpmc_rings.get(producer_id)
2217 .ok_or(RingError::PayloadTooLarge)?;
2218 ring.try_push(payload)
2219 }
2220
2221 pub fn mpmc_try_pop(&self, consumer_id: usize, out: &mut [u8]) -> Result<usize, RingError> {
2225 self.parent.mpmc_pop_in(&self.mpmc_rings, consumer_id, out)
2226 }
2227
2228 pub fn vyukov_try_push(&self, payload: &[u8]) -> Result<(), RingError> {
2230 self.parent.vyukov.try_push(payload)
2231 }
2232
2233 pub fn vyukov_try_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
2235 self.parent.vyukov.try_pop(out)
2236 }
2237
2238 pub fn stamped_try_push(
2244 &self,
2245 producer_id: usize,
2246 payload: &[u8],
2247 ) -> Result<(), RingError> {
2248 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2249 self.parent.stamped_send_inner(ord, self.shape, producer_id, payload)
2250 }
2251
2252 pub fn ordered_try_pop(
2260 &self,
2261 consumer_id: usize,
2262 out: &mut [u8],
2263 ) -> Result<usize, RingError> {
2264 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2265 self.parent
2266 .ordered_recv_inner(ord, self.shape, consumer_id, out)
2267 .map(|(n, _stamp)| n)
2268 }
2269
2270 pub fn ordered_try_pop_with_stamp(
2274 &self,
2275 consumer_id: usize,
2276 out: &mut [u8],
2277 ) -> Result<(usize, u64), RingError> {
2278 let ord = self.parent.ordering.as_ref().ok_or(RingError::NotStamped)?;
2279 self.parent.ordered_recv_inner(ord, self.shape, consumer_id, out)
2280 }
2281}
2282
2283pub struct PeekedSpscSlot<'a> {
2288 inner: crate::spsc_ring::PeekedSlot<'a>,
2289}
2290
2291impl<'a> PeekedSpscSlot<'a> {
2292 pub fn as_slice(&self) -> &[u8] { self.inner.as_slice() }
2293 pub fn len(&self) -> usize { self.inner.len() }
2294 pub fn is_empty(&self) -> bool { self.inner.is_empty() }
2295 pub fn confirm(self) { self.inner.confirm() }
2296}
2297
2298impl<'a> std::ops::Deref for PeekedSpscSlot<'a> {
2299 type Target = [u8];
2300 fn deref(&self) -> &[u8] { &self.inner }
2301}
2302
2303pub const ADAPTIVE_SPSC_PAYLOAD_BYTES: usize = SPSC_PAYLOAD_BYTES;
2306
2307pub const ADAPTIVE_VYUKOV_PAYLOAD_BYTES: usize = PAYLOAD_BYTES;
2310
2311#[derive(Debug, Clone, Copy)]
2319pub struct PolicyObservation {
2320 pub active_producers: usize,
2321 pub active_consumers: usize,
2322 pub current_shape: RingShape,
2323 pub since_last_morph: std::time::Duration,
2324 pub stamped: bool,
2330}
2331
2332pub trait RingShapePolicy: Send + Sync + 'static {
2340 fn decide(&self, observation: &PolicyObservation) -> Option<RingShape>;
2341}
2342
2343pub struct DefaultRingShapePolicy {
2359 pub hysteresis: std::time::Duration,
2360}
2361
2362impl Default for DefaultRingShapePolicy {
2363 fn default() -> Self {
2364 Self { hysteresis: std::time::Duration::from_millis(100) }
2365 }
2366}
2367
2368impl DefaultRingShapePolicy {
2369 pub fn target_shape(producers: usize, consumers: usize) -> Option<RingShape> {
2370 match (producers, consumers) {
2371 (0, _) | (_, 0) => None,
2372 (1, 1) => Some(RingShape::Spsc),
2373 (_, 1) => Some(RingShape::Mpsc),
2374 (_, _) => Some(RingShape::Mpmc),
2375 }
2376 }
2377}
2378
2379impl RingShapePolicy for DefaultRingShapePolicy {
2380 fn decide(&self, obs: &PolicyObservation) -> Option<RingShape> {
2381 if obs.since_last_morph < self.hysteresis {
2382 return None;
2383 }
2384 let target = Self::target_shape(obs.active_producers, obs.active_consumers)?;
2385 if target == obs.current_shape {
2386 None
2387 } else {
2388 Some(target)
2389 }
2390 }
2391}
2392
2393pub struct QosRingShapePolicy {
2406 pub qos: Arc<crate::qos_policy::QosPolicy>,
2407 pub hysteresis: std::time::Duration,
2408}
2409
2410impl QosRingShapePolicy {
2411 pub fn new(qos: Arc<crate::qos_policy::QosPolicy>) -> Self {
2412 Self { qos, hysteresis: std::time::Duration::from_millis(100) }
2413 }
2414}
2415
2416impl RingShapePolicy for QosRingShapePolicy {
2417 fn decide(&self, obs: &PolicyObservation) -> Option<RingShape> {
2418 if obs.since_last_morph < self.hysteresis {
2419 return None;
2420 }
2421 let target = match self.qos.ordering() {
2422 QosOrdering::GlobalFifo if !obs.stamped => Some(RingShape::Vyukov),
2423 _ => DefaultRingShapePolicy::target_shape(
2424 obs.active_producers,
2425 obs.active_consumers,
2426 ),
2427 }?;
2428 if target == obs.current_shape {
2429 None
2430 } else {
2431 Some(target)
2432 }
2433 }
2434}
2435
2436#[derive(Debug, Clone, Copy)]
2439pub struct OrderingPolicyObservation {
2440 pub inversions_per_sec: f64,
2444 pub current_mode: OrderingMode,
2446 pub declared: QosOrdering,
2448 pub active_producers: usize,
2449 pub active_consumers: usize,
2450 pub since_last_change: std::time::Duration,
2452}
2453
2454pub trait OrderingPolicy: Send + Sync + 'static {
2459 fn decide(&self, observation: &OrderingPolicyObservation) -> Option<OrderingMode>;
2460}
2461
2462pub struct DefaultOrderingPolicy {
2477 pub hysteresis: std::time::Duration,
2478 pub auto_order_threshold: Option<f64>,
2479}
2480
2481impl Default for DefaultOrderingPolicy {
2482 fn default() -> Self {
2483 Self {
2484 hysteresis: std::time::Duration::from_millis(100),
2485 auto_order_threshold: None,
2486 }
2487 }
2488}
2489
2490impl OrderingPolicy for DefaultOrderingPolicy {
2491 fn decide(&self, obs: &OrderingPolicyObservation) -> Option<OrderingMode> {
2492 if obs.since_last_change < self.hysteresis {
2493 return None;
2494 }
2495 match obs.declared {
2496 QosOrdering::GlobalFifo => {
2497 if obs.current_mode == OrderingMode::Unordered {
2498 Some(OrderingMode::MergeByStamp)
2499 } else {
2500 None
2501 }
2502 }
2503 QosOrdering::PerProducer => {
2504 match self.auto_order_threshold {
2505 Some(threshold) => {
2506 if obs.current_mode == OrderingMode::Unordered
2507 && obs.inversions_per_sec > threshold
2508 {
2509 Some(OrderingMode::MergeByStamp)
2510 } else {
2511 None
2512 }
2513 }
2514 None => {
2515 if obs.current_mode != OrderingMode::Unordered {
2516 Some(OrderingMode::Unordered)
2517 } else {
2518 None
2519 }
2520 }
2521 }
2522 }
2523 }
2524 }
2525}
2526
2527pub struct AdaptiveRingSidecar {
2535 handle: Option<std::thread::JoinHandle<()>>,
2536 stop: Arc<std::sync::atomic::AtomicBool>,
2537 morphs_triggered: Arc<std::sync::atomic::AtomicU64>,
2538 ordering_flips: Arc<std::sync::atomic::AtomicU64>,
2539}
2540
2541impl AdaptiveRingSidecar {
2542 pub fn spawn<P: RingShapePolicy>(
2545 ring: Arc<AdaptiveRing>,
2546 policy: P,
2547 scan_interval: std::time::Duration,
2548 ) -> Self {
2549 Self::spawn_gated(
2550 ring,
2551 policy,
2552 scan_interval,
2553 crate::policy_gate::GateConfig::default(),
2554 )
2555 }
2556
2557 pub fn spawn_gated<P: RingShapePolicy>(
2561 ring: Arc<AdaptiveRing>,
2562 policy: P,
2563 scan_interval: std::time::Duration,
2564 gate_cfg: crate::policy_gate::GateConfig,
2565 ) -> Self {
2566 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
2567 let morphs_triggered = Arc::new(std::sync::atomic::AtomicU64::new(0));
2568
2569 let stop_c = stop.clone();
2570 let morphs_c = morphs_triggered.clone();
2571 let handle = std::thread::spawn(move || {
2572 let mut last_morph = std::time::Instant::now();
2573 let mut gate = crate::policy_gate::ConfidenceGate::new(gate_cfg);
2574 while !stop_c.load(Ordering::Acquire) {
2575 let obs = PolicyObservation {
2576 active_producers: ring.active_producers(),
2577 active_consumers: ring.active_consumers(),
2578 current_shape: ring.current_shape(),
2579 since_last_morph: last_morph.elapsed(),
2580 stamped: ring.is_stamped(),
2581 };
2582 if let Some(new_shape) = gate
2586 .observe(policy.decide(&obs).map(|s| ring.contract_filtered_shape(s)))
2587 && ring.morph_shape(new_shape).is_ok()
2588 {
2589 last_morph = std::time::Instant::now();
2590 morphs_c.fetch_add(1, Ordering::Relaxed);
2591 }
2592 std::thread::sleep(scan_interval);
2593 }
2594 });
2595
2596 Self {
2597 handle: Some(handle),
2598 stop,
2599 morphs_triggered,
2600 ordering_flips: Arc::new(std::sync::atomic::AtomicU64::new(0)),
2601 }
2602 }
2603
2604 pub fn spawn_with_qos<P: RingShapePolicy, O: OrderingPolicy>(
2633 ring: Arc<AdaptiveRing>,
2634 shape_policy: P,
2635 ordering_policy: O,
2636 qos: Arc<crate::qos_policy::QosPolicy>,
2637 scan_interval: std::time::Duration,
2638 ) -> Self {
2639 Self::spawn_with_qos_core(
2640 ring,
2641 shape_policy,
2642 ordering_policy,
2643 qos,
2644 scan_interval,
2645 crate::policy_gate::GateConfig::default(),
2646 crate::policy_gate::GateConfig::enabled_with_arity(2),
2647 )
2648 }
2649
2650 pub fn spawn_with_qos_gated<P: RingShapePolicy, O: OrderingPolicy>(
2660 ring: Arc<AdaptiveRing>,
2661 shape_policy: P,
2662 ordering_policy: O,
2663 qos: Arc<crate::qos_policy::QosPolicy>,
2664 scan_interval: std::time::Duration,
2665 gate_cfg: crate::policy_gate::GateConfig,
2666 ) -> Self {
2667 Self::spawn_with_qos_core(
2668 ring,
2669 shape_policy,
2670 ordering_policy,
2671 qos,
2672 scan_interval,
2673 gate_cfg,
2674 gate_cfg,
2675 )
2676 }
2677
2678 fn spawn_with_qos_core<P: RingShapePolicy, O: OrderingPolicy>(
2679 ring: Arc<AdaptiveRing>,
2680 shape_policy: P,
2681 ordering_policy: O,
2682 qos: Arc<crate::qos_policy::QosPolicy>,
2683 scan_interval: std::time::Duration,
2684 shape_gate_cfg: crate::policy_gate::GateConfig,
2685 order_gate_cfg: crate::policy_gate::GateConfig,
2686 ) -> Self {
2687 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
2688 let morphs_triggered = Arc::new(std::sync::atomic::AtomicU64::new(0));
2689 let ordering_flips = Arc::new(std::sync::atomic::AtomicU64::new(0));
2690
2691 let stop_c = stop.clone();
2692 let morphs_c = morphs_triggered.clone();
2693 let flips_c = ordering_flips.clone();
2694 let handle = std::thread::spawn(move || {
2695 let mut last_morph = std::time::Instant::now();
2696 let mut last_flip = std::time::Instant::now();
2697 let mut last_inversions = ring.inversions();
2698 let mut last_scan = std::time::Instant::now();
2699 let mut shape_gate = crate::policy_gate::ConfidenceGate::new(shape_gate_cfg);
2700 let mut order_gate = crate::policy_gate::ConfidenceGate::new(order_gate_cfg);
2701 let mut last_peers = (0usize, 0usize);
2702 let mut first_scan = true;
2703 while !stop_c.load(Ordering::Acquire) {
2704 let obs = PolicyObservation {
2705 active_producers: ring.active_producers(),
2706 active_consumers: ring.active_consumers(),
2707 current_shape: ring.current_shape(),
2708 since_last_morph: last_morph.elapsed(),
2709 stamped: ring.is_stamped(),
2710 };
2711 let peers = (obs.active_producers, obs.active_consumers);
2712 if !first_scan && peers != last_peers {
2713 shape_gate.shock();
2714 order_gate.shock();
2715 }
2716 last_peers = peers;
2717 first_scan = false;
2718
2719 if let Some(new_shape) = shape_gate
2720 .observe(shape_policy.decide(&obs).map(|s| ring.contract_filtered_shape(s)))
2721 && ring.morph_shape(new_shape).is_ok()
2722 {
2723 last_morph = std::time::Instant::now();
2724 morphs_c.fetch_add(1, Ordering::Relaxed);
2725 }
2726
2727 if let Some(current_mode) = ring.ordering_mode() {
2728 ring.tick_drainer_epoch().ok();
2729
2730 let now_inversions = ring.inversions();
2731 let elapsed = last_scan.elapsed().as_secs_f64().max(1e-9);
2732 let rate = now_inversions
2733 .saturating_sub(last_inversions) as f64 / elapsed;
2734 last_inversions = now_inversions;
2735 last_scan = std::time::Instant::now();
2736
2737 let declared = qos.ordering();
2738 let ord_obs = OrderingPolicyObservation {
2739 inversions_per_sec: rate,
2740 current_mode,
2741 declared,
2742 active_producers: obs.active_producers,
2743 active_consumers: obs.active_consumers,
2744 since_last_change: last_flip.elapsed(),
2745 };
2746 let decision = ordering_policy
2747 .decide(&ord_obs)
2748 .filter(|m| *m != current_mode);
2749
2750 let is_auto_arm = declared == QosOrdering::PerProducer
2759 && decision == Some(OrderingMode::MergeByStamp);
2760 let gated = if is_auto_arm {
2761 order_gate.observe(decision)
2762 } else {
2763 decision
2764 };
2765 if let Some(new_mode) = gated
2766 && ring.set_ordering_mode(new_mode).is_ok()
2767 {
2768 last_flip = std::time::Instant::now();
2769 flips_c.fetch_add(1, Ordering::Relaxed);
2770 }
2771 }
2772 std::thread::sleep(scan_interval);
2773 }
2774 });
2775
2776 Self {
2777 handle: Some(handle),
2778 stop,
2779 morphs_triggered,
2780 ordering_flips,
2781 }
2782 }
2783
2784 pub fn morphs_triggered(&self) -> u64 {
2787 self.morphs_triggered.load(Ordering::Acquire)
2788 }
2789
2790 pub fn ordering_flips(&self) -> u64 {
2793 self.ordering_flips.load(Ordering::Acquire)
2794 }
2795
2796 pub fn shutdown(mut self) {
2798 self.stop.store(true, Ordering::Release);
2799 if let Some(h) = self.handle.take() {
2800 h.join().ok();
2801 }
2802 }
2803}
2804
2805impl Drop for AdaptiveRingSidecar {
2806 fn drop(&mut self) {
2807 self.stop.store(true, Ordering::Release);
2808 if let Some(h) = self.handle.take() {
2809 h.join().ok();
2810 }
2811 }
2812}
2813
2814#[cfg(test)]
2815mod tests {
2816 use super::*;
2817
2818 #[test]
2819 fn create_starts_in_spsc_shape() {
2820 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2821 assert_eq!(ring.current_shape(), RingShape::Spsc);
2822 assert_eq!(ring.pin_generation(), 0);
2823 }
2824
2825 #[test]
2826 fn adaptive_dispatch_round_trip_each_shape() {
2827 for shape in [RingShape::Spsc, RingShape::Mpsc, RingShape::Mpmc, RingShape::Vyukov] {
2828 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2829 ring.shape_tag.store(shape as u8, Ordering::Release);
2830 let payload = [0xCDu8; 56];
2834 ring.try_send(0, &payload).unwrap();
2835 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
2836 let n = ring.try_recv(0, &mut out).unwrap();
2837 assert!(n > 0, "shape {:?} delivered zero bytes", shape);
2838 assert_eq!(&out[..payload.len()], &payload[..]);
2839 }
2840 }
2841
2842 #[test]
2843 fn pin_captures_shape_and_generation() {
2844 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2845 let pinned = ring.pin_current_shape();
2846 assert_eq!(pinned.shape(), RingShape::Spsc);
2847 assert!(pinned.is_still_valid());
2848 let pinned = ring.pin_current_shape();
2850 assert!(pinned.is_still_valid());
2851 }
2852
2853 #[test]
2854 fn morph_invalidates_outstanding_pin() {
2855 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2856 let pinned = ring.pin_current_shape();
2857 assert!(pinned.is_still_valid());
2858
2859 ring.morph_to(RingShape::Mpsc).unwrap();
2860 assert!(!pinned.is_still_valid(),
2861 "pin must invalidate after morph_to");
2862 assert_eq!(ring.current_shape(), RingShape::Mpsc);
2863 }
2864
2865 #[test]
2866 fn morph_to_same_shape_is_no_op() {
2867 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2868 let gen_before = ring.pin_generation();
2869 ring.morph_to(RingShape::Spsc).unwrap();
2870 let gen_after = ring.pin_generation();
2871 assert_eq!(gen_before, gen_after,
2872 "morph_to(same shape) must not bump pin_generation");
2873 }
2874
2875 #[test]
2876 fn morph_preserves_in_flight_items_via_stale_walk() {
2877 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2878
2879 for i in 0..3u32 {
2881 let mut buf = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
2882 buf[..4].copy_from_slice(&i.to_le_bytes());
2883 ring.try_send(0, &buf).unwrap();
2884 }
2885
2886 ring.morph_to(RingShape::Mpsc).unwrap();
2889 assert_eq!(ring.current_shape(), RingShape::Mpsc);
2890 assert_eq!(ring.approx_len(), 3,
2891 "the stale backlog must stay visible through approx_len");
2892
2893 let mut buf = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
2896 buf[..4].copy_from_slice(&99u32.to_le_bytes());
2897 ring.try_send(0, &buf).unwrap();
2898
2899 let mut seen = Vec::new();
2900 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
2901 while ring.try_recv(0, &mut out).is_ok() {
2902 seen.push(u32::from_le_bytes(out[..4].try_into().unwrap()));
2903 }
2904 assert_eq!(seen, vec![0u32, 1, 2, 99],
2905 "stale backlog must drain before post-morph items");
2906 assert!(ring.is_empty());
2907 }
2908
2909 #[test]
2910 fn frame_round_trip_all_shapes() {
2911 use crate::frame_ring::FrameClass;
2912 for shape in [RingShape::Spsc, RingShape::Mpsc, RingShape::Mpmc, RingShape::Vyukov] {
2913 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2914 if shape != RingShape::Spsc {
2915 ring.morph_to(shape).unwrap();
2916 }
2917 let small = b"small inline payload".to_vec();
2918 let large = vec![0xABu8; 4000];
2919 assert_eq!(ring.send_frame(0, &small).unwrap(), FrameClass::Inline,
2920 "{shape:?} small should inline");
2921 assert_eq!(ring.send_frame(0, &large).unwrap(), FrameClass::Offset,
2922 "{shape:?} large should offset");
2923 let mut out = Vec::new();
2924 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Inline);
2925 assert_eq!(out, small, "{shape:?} small round-trip");
2926 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Offset);
2927 assert_eq!(out, large, "{shape:?} large round-trip");
2928 }
2929 }
2930
2931 #[test]
2932 fn frame_survives_morph() {
2933 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
2934 ring.send_frame(0, b"pre-morph small").unwrap();
2936 ring.send_frame(0, &vec![1u8; 3000]).unwrap();
2937 ring.morph_to(RingShape::Mpsc).unwrap();
2941 ring.send_frame(0, b"post-morph small").unwrap();
2942 ring.send_frame(0, &vec![2u8; 3000]).unwrap();
2943 let mut out = Vec::new();
2944 ring.recv_frame(0, &mut out).unwrap();
2945 assert_eq!(out, b"pre-morph small");
2946 ring.recv_frame(0, &mut out).unwrap();
2947 assert_eq!(out, vec![1u8; 3000]);
2948 ring.recv_frame(0, &mut out).unwrap();
2949 assert_eq!(out, b"post-morph small");
2950 ring.recv_frame(0, &mut out).unwrap();
2951 assert_eq!(out, vec![2u8; 3000]);
2952 }
2953
2954 #[test]
2955 fn frame_override_and_limits() {
2956 use crate::frame_ring::{FrameClass, LayoutHint};
2957 let ring = AdaptiveRing::create_anon(2, 2, 64).unwrap();
2958 let mut out = Vec::new();
2959 assert_eq!(ring.send_frame_as(0, b"tiny", LayoutHint::ForceOffset).unwrap(),
2961 FrameClass::Offset);
2962 assert_eq!(ring.recv_frame(0, &mut out).unwrap(), FrameClass::Offset);
2963 assert_eq!(out, b"tiny");
2964 let big = vec![0u8; AdaptiveRing::FRAME_INLINE_BUDGET + 1];
2966 assert_eq!(ring.send_frame_as(0, &big, LayoutHint::ForceInline).unwrap_err(),
2967 RingError::PayloadTooLarge);
2968 let at = vec![7u8; AdaptiveRing::FRAME_INLINE_BUDGET];
2970 assert_eq!(ring.send_frame(0, &at).unwrap(), FrameClass::Inline);
2971 ring.recv_frame(0, &mut out).unwrap();
2972 assert_eq!(out, at);
2973 }
2974
2975 #[test]
2976 fn frame_rejected_on_stamped_ring() {
2977 let ring = AdaptiveRing::create_anon(2, 2, 64)
2980 .unwrap()
2981 .with_ordering_stamps()
2982 .unwrap();
2983 assert_eq!(ring.send_frame(0, b"x").unwrap_err(), RingError::LayoutMismatch);
2984 let mut out = Vec::new();
2985 assert_eq!(ring.recv_frame(0, &mut out).unwrap_err(), RingError::LayoutMismatch);
2986 }
2987
2988 #[test]
2989 fn frame_vyukov_two_thread_mixed_size() {
2990 use std::sync::Arc;
2991 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtOrd};
2992 use std::thread;
2993
2994 const PER: u32 = 5_000;
2995 const PRODUCERS: u32 = 2;
2996 let total = (PER * PRODUCERS) as usize;
2997
2998 let ring = Arc::new(AdaptiveRing::create_anon(2, 2, 256).unwrap());
3004 ring.morph_to(RingShape::Vyukov).unwrap();
3005 let seen: Arc<Vec<AtomicBool>> =
3008 Arc::new((0..total).map(|_| AtomicBool::new(false)).collect());
3009 let received = Arc::new(AtomicUsize::new(0));
3010
3011 let mut prods = Vec::new();
3012 for p in 0..PRODUCERS {
3013 let ring = ring.clone();
3014 prods.push(thread::spawn(move || {
3015 for i in 0..PER {
3016 let id = p * PER + i;
3017 let len = (id as usize % 200) + 4; let mut payload = vec![0u8; len];
3019 payload[0..4].copy_from_slice(&id.to_le_bytes());
3020 for k in 4..len {
3021 payload[k] = id.wrapping_add(k as u32) as u8;
3022 }
3023 while ring.send_frame(p as usize, &payload).is_err() {
3024 std::hint::spin_loop();
3025 }
3026 }
3027 }));
3028 }
3029
3030 let mut cons = Vec::new();
3031 for c in 0..2usize {
3032 let ring = ring.clone();
3033 let seen = seen.clone();
3034 let received = received.clone();
3035 cons.push(thread::spawn(move || {
3036 let mut out = Vec::new();
3037 while received.load(AtOrd::Acquire) < total {
3038 if ring.recv_frame(c, &mut out).is_ok() {
3039 let id = u32::from_le_bytes(out[0..4].try_into().unwrap());
3040 let len = (id as usize % 200) + 4;
3041 assert_eq!(out.len(), len, "id {id} length");
3042 for k in 4..len {
3043 assert_eq!(out[k], id.wrapping_add(k as u32) as u8,
3044 "id {id} byte {k}");
3045 }
3046 let already = seen[id as usize].swap(true, AtOrd::AcqRel);
3047 assert!(!already, "id {id} delivered twice");
3048 received.fetch_add(1, AtOrd::AcqRel);
3049 } else {
3050 std::hint::spin_loop();
3051 }
3052 }
3053 }));
3054 }
3055
3056 for p in prods { p.join().unwrap(); }
3057 for c in cons { c.join().unwrap(); }
3058 assert_eq!(received.load(AtOrd::Acquire), total);
3059 assert!(seen.iter().all(|b| b.load(AtOrd::Acquire)),
3060 "every id delivered exactly once");
3061 }
3062
3063 #[test]
3064 fn second_morph_blocked_until_stale_backlog_drains() {
3065 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3066 ring.try_send(0, &[7u8; 8]).unwrap();
3067 ring.morph_to(RingShape::Mpsc).unwrap();
3068
3069 assert_eq!(ring.morph_to(RingShape::Mpmc).unwrap_err(),
3071 RingError::StaleBacklog);
3072
3073 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3074 ring.try_recv(0, &mut out).unwrap();
3075 ring.morph_to(RingShape::Mpmc).unwrap();
3077 assert_eq!(ring.current_shape(), RingShape::Mpmc);
3078 }
3079
3080 #[test]
3081 fn register_producer_grows_past_hint_and_recycles_slots() {
3082 let ring = AdaptiveRing::create_anon(3, 1, 64).unwrap();
3083 let id0 = ring.register_producer().unwrap();
3084 let id1 = ring.register_producer().unwrap();
3085 let id2 = ring.register_producer().unwrap();
3086 assert_eq!((id0, id1, id2), (0, 1, 2));
3087
3088 let id3 = ring.register_producer().unwrap();
3091 assert_eq!(id3, 3);
3092 assert_eq!(ring.published_producers(), 4);
3093 ring.try_send(id3, &7u64.to_le_bytes()).unwrap();
3094 let mut out = [0u8; 64];
3095 let _c = ring.register_consumer().unwrap();
3099 let n = ring.try_recv(0, &mut out).unwrap();
3100 assert!(n >= 8, "popped record too short: {n}");
3101 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 7);
3102
3103 ring.unregister_producer(id1);
3106 assert_eq!(ring.register_producer().unwrap(), 1);
3107
3108 let pinned = AdaptiveRing::create_anon(2, 1, 64)
3110 .unwrap()
3111 .with_contract(crate::ring_contract::RingContract::from_counts(2, 1));
3112 pinned.register_producer().unwrap();
3113 pinned.register_producer().unwrap();
3114 assert_eq!(pinned.register_producer().unwrap_err(),
3115 AdaptiveError::TooManyProducers);
3116 }
3117
3118 #[test]
3119 fn default_policy_target_shape_per_peer_count() {
3120 assert_eq!(DefaultRingShapePolicy::target_shape(0, 1), None);
3122 assert_eq!(DefaultRingShapePolicy::target_shape(1, 0), None);
3123 assert_eq!(DefaultRingShapePolicy::target_shape(0, 0), None);
3124 assert_eq!(DefaultRingShapePolicy::target_shape(1, 1), Some(RingShape::Spsc));
3126 assert_eq!(DefaultRingShapePolicy::target_shape(2, 1), Some(RingShape::Mpsc));
3128 assert_eq!(DefaultRingShapePolicy::target_shape(8, 1), Some(RingShape::Mpsc));
3129 assert_eq!(DefaultRingShapePolicy::target_shape(1, 2), Some(RingShape::Mpmc));
3131 assert_eq!(DefaultRingShapePolicy::target_shape(4, 4), Some(RingShape::Mpmc));
3132 }
3133
3134 #[test]
3135 fn default_policy_returns_none_during_hysteresis() {
3136 let policy = DefaultRingShapePolicy {
3137 hysteresis: std::time::Duration::from_secs(1),
3138 };
3139 let obs = PolicyObservation {
3140 active_producers: 4,
3141 active_consumers: 4,
3142 current_shape: RingShape::Spsc,
3143 since_last_morph: std::time::Duration::from_millis(50),
3144 stamped: false,
3145 };
3146 assert_eq!(policy.decide(&obs), None);
3148 }
3149
3150 #[test]
3151 fn default_policy_returns_target_after_hysteresis() {
3152 let policy = DefaultRingShapePolicy {
3153 hysteresis: std::time::Duration::from_millis(10),
3154 };
3155 let obs = PolicyObservation {
3156 active_producers: 4,
3157 active_consumers: 4,
3158 current_shape: RingShape::Spsc,
3159 since_last_morph: std::time::Duration::from_secs(1),
3160 stamped: false,
3161 };
3162 assert_eq!(policy.decide(&obs), Some(RingShape::Mpmc));
3163 }
3164
3165 #[test]
3166 fn default_policy_returns_none_when_target_equals_current() {
3167 let policy = DefaultRingShapePolicy::default();
3168 let obs = PolicyObservation {
3169 active_producers: 1,
3170 active_consumers: 1,
3171 current_shape: RingShape::Spsc,
3172 since_last_morph: std::time::Duration::from_secs(1),
3173 stamped: false,
3174 };
3175 assert_eq!(policy.decide(&obs), None);
3176 }
3177
3178 #[test]
3179 fn shape_tracks_peer_counts_and_sidecar_stays_idle() {
3180 let ring = Arc::new(AdaptiveRing::create_anon(4, 4, 64).unwrap());
3181 let policy = DefaultRingShapePolicy {
3182 hysteresis: std::time::Duration::from_millis(5),
3183 };
3184 let sidecar = AdaptiveRingSidecar::spawn(
3185 ring.clone(),
3186 policy,
3187 std::time::Duration::from_millis(10),
3188 );
3189
3190 let _p0 = ring.register_producer().unwrap();
3192 let _c0 = ring.register_consumer().unwrap();
3193 assert_eq!(ring.current_shape(), RingShape::Spsc);
3194
3195 let _p1 = ring.register_producer().unwrap();
3198 assert_eq!(ring.current_shape(), RingShape::Mpsc,
3199 "2nd producer registration must morph to MPSC immediately");
3200
3201 let _c1 = ring.register_consumer().unwrap();
3202 assert_eq!(ring.current_shape(), RingShape::Mpmc,
3203 "2nd consumer registration must morph to MPMC immediately");
3204
3205 std::thread::sleep(std::time::Duration::from_millis(60));
3208 assert_eq!(sidecar.morphs_triggered(), 0,
3209 "register-path morphs left the sidecar nothing to do");
3210
3211 ring.unregister_consumer(1);
3214 ring.unregister_producer(1);
3215 assert_eq!(ring.current_shape(), RingShape::Spsc,
3216 "unregister must morph back down automatically");
3217
3218 sidecar.shutdown();
3219 }
3220
3221 #[test]
3222 fn pinned_native_paths_match_adaptive_paths() {
3223 let ring = AdaptiveRing::create_anon(4, 4, 64).unwrap();
3224 let pinned = ring.pin_current_shape();
3225 assert_eq!(pinned.shape(), RingShape::Spsc);
3226
3227 let payload = [0xAAu8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3228 pinned.spsc_try_push(&payload).unwrap();
3229 let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
3230 let n = pinned.spsc_try_pop(&mut out).unwrap();
3231 assert_eq!(n, ADAPTIVE_SPSC_PAYLOAD_BYTES);
3232 assert_eq!(out, payload);
3233 assert!(pinned.is_still_valid());
3234 }
3235
3236 fn stamped_anon(
3241 max_producers: usize,
3242 max_consumers: usize,
3243 kind: StampKind,
3244 ) -> AdaptiveRing {
3245 AdaptiveRing::create_anon(max_producers, max_consumers, 64)
3246 .unwrap()
3247 .with_ordering_stamps_kind(kind)
3248 .unwrap()
3249 }
3250
3251 #[test]
3252 fn stamped_round_trip_strips_stamp_and_caps_payload() {
3253 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3254 assert!(ring.is_stamped());
3255 assert_eq!(ring.stamp_kind(), Some(StampKind::SharedCounter));
3256 assert_eq!(ring.ordering_mode(), Some(OrderingMode::Unordered));
3257
3258 let too_big = [0u8; STAMPED_PAYLOAD_BYTES + 1];
3260 assert_eq!(ring.try_send(0, &too_big).unwrap_err(),
3261 RingError::PayloadTooLarge);
3262
3263 let payload = [0xC3u8; STAMPED_PAYLOAD_BYTES];
3264 ring.try_send(0, &payload).unwrap();
3265 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3266 let n = ring.try_recv(0, &mut out).unwrap();
3267 assert_eq!(n, STAMPED_PAYLOAD_BYTES,
3268 "stamped recv returns payload bytes only");
3269 assert_eq!(out, payload, "the stamp must be stripped, not leak into the payload");
3270 }
3271
3272 #[test]
3273 fn unstamped_ring_rejects_ordering_calls() {
3274 let ring = AdaptiveRing::create_anon(2, 1, 64).unwrap();
3275 assert!(!ring.is_stamped());
3276 assert_eq!(ring.inversions(), 0);
3277 assert_eq!(ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap_err(),
3278 RingError::NotStamped);
3279 assert_eq!(ring.refresh_watermark(0).unwrap_err(), RingError::NotStamped);
3280 let pinned = ring.pin_current_shape();
3281 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3282 assert_eq!(pinned.ordered_try_pop(0, &mut out).unwrap_err(),
3283 RingError::NotStamped);
3284 assert_eq!(pinned.stamped_try_push(0, &[1u8; 8]).unwrap_err(),
3285 RingError::NotStamped);
3286 }
3287
3288 #[test]
3289 fn stamped_ring_rejects_vyukov_morph_and_vyukov_ring_rejects_stamps() {
3290 let ring = stamped_anon(2, 1, StampKind::Monotonic);
3291 assert_eq!(ring.morph_to(RingShape::Vyukov).unwrap_err(),
3292 RingError::LayoutMismatch);
3293
3294 let vyukov_first = AdaptiveRing::create_anon(2, 1, 64).unwrap();
3295 vyukov_first.morph_to(RingShape::Vyukov).unwrap();
3296 assert!(matches!(
3297 vyukov_first.with_ordering_stamps(),
3298 Err(RingError::LayoutMismatch)
3299 ));
3300 }
3301
3302 #[test]
3303 fn synthetic_interleave_fires_inversion_counter() {
3304 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3305 ring.morph_to(RingShape::Mpsc).unwrap();
3306
3307 ring.try_send(1, &1u64.to_le_bytes()).unwrap();
3312 ring.try_send(0, &2u64.to_le_bytes()).unwrap();
3313
3314 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3315 ring.try_recv(0, &mut out).unwrap();
3316 assert_eq!(ring.inversions(), 0, "first pop has no predecessor");
3317 ring.try_recv(0, &mut out).unwrap();
3318 assert_eq!(ring.inversions(), 1,
3319 "older-after-newer must count as one inversion");
3320 }
3321
3322 #[test]
3323 fn merge_mode_delivers_global_stamp_order() {
3324 let ring = stamped_anon(4, 1, StampKind::SharedCounter);
3325 ring.morph_to(RingShape::Mpsc).unwrap();
3326 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3327
3328 for i in 0..32u64 {
3331 let producer = (i % 4) as usize;
3332 ring.try_send(producer, &i.to_le_bytes()).unwrap();
3333 }
3334
3335 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3336 for expected in 0..32u64 {
3337 let n = ring.try_recv(0, &mut out).unwrap();
3338 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3339 let got = u64::from_le_bytes(out[..8].try_into().unwrap());
3340 assert_eq!(got, expected,
3341 "merge pop must deliver global push order");
3342 }
3343 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty);
3344 assert_eq!(ring.inversions(), 0,
3345 "merged pops must observe zero inversions");
3346 }
3347
3348 #[test]
3349 fn flag_flip_orders_backlog_retroactively_without_loss() {
3350 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3351 ring.morph_to(RingShape::Mpsc).unwrap();
3352
3353 for i in 0..16u64 {
3356 let producer = ((i + 1) % 2) as usize;
3357 ring.try_send(producer, &i.to_le_bytes()).unwrap();
3358 }
3359
3360 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3363 let mut popped = Vec::new();
3364 for _ in 0..2 {
3365 ring.try_recv(0, &mut out).unwrap();
3366 popped.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3367 }
3368 let inversions_before_flip = ring.inversions();
3369 assert!(inversions_before_flip > 0,
3370 "unordered interleave must show inversions before the flip");
3371
3372 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3375 let mut merged = Vec::new();
3376 while let Ok(_n) = ring.try_recv(0, &mut out) {
3377 merged.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3378 }
3379
3380 let mut all = popped.clone();
3382 all.extend(&merged);
3383 all.sort_unstable();
3384 assert_eq!(all, (0..16u64).collect::<Vec<_>>(),
3385 "no item may be lost across the mode flip");
3386 for pair in merged.windows(2) {
3389 assert!(pair[0] < pair[1],
3390 "post-flip pops must be globally ordered: {merged:?}");
3391 }
3392 assert_eq!(ring.inversions(), inversions_before_flip,
3393 "the flip itself and merged pops must add zero inversions");
3394 }
3395
3396 #[test]
3397 fn merge_strict_blocks_on_in_flight_stamp_then_releases() {
3398 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3399 ring.morph_to(RingShape::Mpsc).unwrap();
3400 ring.set_ordering_mode(OrderingMode::MergeStrict).unwrap();
3401 let region = ring.ordering_region().unwrap();
3402
3403 let stalled_stamp = region.next_stamp(1);
3407 ring.try_send(0, &42u64.to_le_bytes()).unwrap();
3409
3410 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3413 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3414 "strict merge must hold the candidate while a smaller stamp is in flight");
3415
3416 region.publish_watermark(1, stalled_stamp);
3422 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3423 "strict watermark gate must hold until the silent producer vouches");
3424 ring.refresh_watermark(1).unwrap();
3427 let n = ring.try_recv(0, &mut out).unwrap();
3428 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3429 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 42);
3430 }
3431
3432 #[test]
3433 fn merge_by_stamp_in_flight_gate_blocks_descheduled_producer() {
3434 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3439 ring.morph_to(RingShape::Mpsc).unwrap();
3440 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3441 let region = ring.ordering_region().unwrap();
3442
3443 let stalled = region.next_stamp(1); ring.try_send(0, &9u64.to_le_bytes()).unwrap();
3445
3446 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3447 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3448 "MergeByStamp must gate on in-flight stamps too");
3449 region.publish_watermark(1, stalled); let n = ring.try_recv(0, &mut out).unwrap();
3451 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3452 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 9);
3453 }
3454
3455 #[test]
3456 fn merge_strict_retired_producer_stops_gating() {
3457 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3458 ring.morph_to(RingShape::Mpsc).unwrap();
3459 ring.set_ordering_mode(OrderingMode::MergeStrict).unwrap();
3460
3461 ring.try_send(1, &1u64.to_le_bytes()).unwrap();
3465 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3466 ring.try_recv(0, &mut out).unwrap();
3467
3468 ring.try_send(0, &2u64.to_le_bytes()).unwrap();
3470 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(), RingError::Empty,
3471 "strict couples release to the slowest in-use producer");
3472
3473 ring.retire_producer(1).unwrap();
3476 let n = ring.try_recv(0, &mut out).unwrap();
3477 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3478 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 2);
3479 }
3480
3481 #[test]
3482 fn multi_consumer_merge_enforces_single_drainer() {
3483 let ring = stamped_anon(2, 2, StampKind::SharedCounter);
3484 ring.morph_to(RingShape::Mpmc).unwrap();
3485 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3486
3487 for i in 0..4u64 {
3488 ring.try_send((i % 2) as usize, &i.to_le_bytes()).unwrap();
3489 }
3490
3491 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3493 ring.try_recv(0, &mut out).unwrap();
3494 assert_eq!(ring.try_recv(1, &mut out).unwrap_err(),
3496 RingError::NotDrainer);
3497 assert!(ring.release_drainer(0).unwrap());
3499 let n = ring.try_recv(1, &mut out).unwrap();
3500 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3501 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 1,
3502 "the new drainer continues in global stamp order");
3503 assert_eq!(ring.try_recv(0, &mut out).unwrap_err(),
3505 RingError::NotDrainer);
3506 }
3507
3508 #[test]
3509 fn mode_flip_does_not_invalidate_pins() {
3510 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3511 ring.morph_to(RingShape::Mpsc).unwrap();
3512 let pinned = ring.pin_current_shape();
3513 assert!(pinned.is_still_valid());
3514
3515 pinned.stamped_try_push(1, &1u64.to_le_bytes()).unwrap();
3517 pinned.stamped_try_push(0, &2u64.to_le_bytes()).unwrap();
3518
3519 ring.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3523 assert!(pinned.is_still_valid(),
3524 "ordering-mode flips must not invalidate pins");
3525
3526 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3527 pinned.ordered_try_pop(0, &mut out).unwrap();
3528 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 1,
3529 "pinned merge pop must deliver stamp order");
3530 pinned.ordered_try_pop(0, &mut out).unwrap();
3531 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 2);
3532 }
3533
3534 #[test]
3535 fn stamped_items_survive_shape_morphs() {
3536 let ring = stamped_anon(2, 1, StampKind::SharedCounter);
3537 for i in 0..3u64 {
3538 ring.try_send(0, &i.to_le_bytes()).unwrap();
3539 }
3540 ring.morph_to(RingShape::Mpsc).unwrap();
3541 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3542 let mut got = Vec::new();
3543 while ring.try_recv(0, &mut out).is_ok() {
3544 got.push(u64::from_le_bytes(out[..8].try_into().unwrap()));
3545 }
3546 got.sort_unstable();
3547 assert_eq!(got, vec![0, 1, 2],
3548 "stamped slots must transfer intact across shape morphs");
3549 }
3550
3551 #[test]
3552 fn stamped_file_ring_open_adopts_creator_kind_and_shares_mode() {
3553 let mut prefix = std::env::temp_dir();
3554 prefix.push(format!(
3555 "subetha_stamped_open_{}_{}",
3556 std::process::id(),
3557 std::time::SystemTime::now()
3558 .duration_since(std::time::UNIX_EPOCH)
3559 .map(|d| d.as_nanos()).unwrap_or(0),
3560 ));
3561
3562 let creator = AdaptiveRing::create(&prefix, 2, 1, 64)
3563 .unwrap()
3564 .with_ordering_stamps_kind(StampKind::SharedCounter)
3565 .unwrap();
3566 creator.set_ordering_mode(OrderingMode::MergeByStamp).unwrap();
3567 creator.try_send(0, &7u64.to_le_bytes()).unwrap();
3568
3569 let opener = AdaptiveRing::open(&prefix, 2, 1, 64)
3570 .unwrap()
3571 .with_ordering_stamps()
3572 .unwrap();
3573 assert_eq!(opener.stamp_kind(), Some(StampKind::SharedCounter),
3574 "opener must adopt the creator's stamp kind");
3575 assert_eq!(opener.ordering_mode(), Some(OrderingMode::MergeByStamp),
3576 "the mode flag must be cross-process (region-resident)");
3577 assert!(matches!(
3579 AdaptiveRing::open(&prefix, 2, 1, 64)
3580 .unwrap()
3581 .with_ordering_stamps_kind(StampKind::Monotonic),
3582 Err(RingError::LayoutMismatch)
3583 ));
3584
3585 let mut out = [0u8; STAMPED_PAYLOAD_BYTES];
3586 let n = opener.try_recv(0, &mut out).unwrap();
3587 assert_eq!(n, STAMPED_PAYLOAD_BYTES);
3588 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 7);
3589
3590 drop(creator);
3591 drop(opener);
3592 for suffix in [".spsc.bin", ".mpsc.0.bin", ".mpsc.1.bin",
3593 ".mpmc.0.bin", ".mpmc.1.bin", ".vyukov.bin",
3594 ".ordering.bin"] {
3595 let mut p = prefix.as_os_str().to_owned();
3596 p.push(suffix);
3597 std::fs::remove_file(std::path::PathBuf::from(p)).ok();
3598 }
3599 }
3600
3601 #[test]
3602 fn qos_shape_policy_decision_matrix() {
3603 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3604 let policy = QosRingShapePolicy {
3605 qos: qos.clone(),
3606 hysteresis: std::time::Duration::from_millis(0),
3607 };
3608 let obs = |shape, stamped| PolicyObservation {
3609 active_producers: 2,
3610 active_consumers: 1,
3611 current_shape: shape,
3612 since_last_morph: std::time::Duration::from_secs(1),
3613 stamped,
3614 };
3615
3616 assert_eq!(policy.decide(&obs(RingShape::Spsc, false)),
3618 Some(RingShape::Mpsc));
3619 assert_eq!(policy.decide(&obs(RingShape::Mpsc, false)), None);
3620
3621 qos.set_ordering(crate::qos_policy::Ordering::GlobalFifo);
3623 assert_eq!(policy.decide(&obs(RingShape::Mpsc, false)),
3624 Some(RingShape::Vyukov));
3625 assert_eq!(policy.decide(&obs(RingShape::Vyukov, false)), None);
3626
3627 assert_eq!(policy.decide(&obs(RingShape::Spsc, true)),
3630 Some(RingShape::Mpsc));
3631 assert_eq!(policy.decide(&obs(RingShape::Mpsc, true)), None);
3632
3633 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3635 assert_eq!(policy.decide(&obs(RingShape::Vyukov, false)),
3636 Some(RingShape::Mpsc));
3637
3638 let cold = QosRingShapePolicy {
3640 qos: qos.clone(),
3641 hysteresis: std::time::Duration::from_secs(10),
3642 };
3643 let mut o = obs(RingShape::Spsc, false);
3644 o.since_last_morph = std::time::Duration::from_millis(1);
3645 assert_eq!(cold.decide(&o), None);
3646 }
3647
3648 #[test]
3649 fn default_ordering_policy_decision_matrix() {
3650 let obs = |mode, declared, rate, since_ms| OrderingPolicyObservation {
3651 inversions_per_sec: rate,
3652 current_mode: mode,
3653 declared,
3654 active_producers: 2,
3655 active_consumers: 1,
3656 since_last_change: std::time::Duration::from_millis(since_ms),
3657 };
3658 let declarative = DefaultOrderingPolicy {
3659 hysteresis: std::time::Duration::from_millis(0),
3660 auto_order_threshold: None,
3661 };
3662 assert_eq!(
3664 declarative.decide(&obs(
3665 OrderingMode::Unordered, QosOrdering::GlobalFifo, 0.0, 500)),
3666 Some(OrderingMode::MergeByStamp),
3667 );
3668 assert_eq!(
3669 declarative.decide(&obs(
3670 OrderingMode::MergeByStamp, QosOrdering::GlobalFifo, 0.0, 500)),
3671 None,
3672 );
3673 assert_eq!(
3675 declarative.decide(&obs(
3676 OrderingMode::MergeByStamp, QosOrdering::PerProducer, 0.0, 500)),
3677 Some(OrderingMode::Unordered),
3678 );
3679
3680 let auto = DefaultOrderingPolicy {
3681 hysteresis: std::time::Duration::from_millis(0),
3682 auto_order_threshold: Some(100.0),
3683 };
3684 assert_eq!(
3686 auto.decide(&obs(
3687 OrderingMode::Unordered, QosOrdering::PerProducer, 50.0, 500)),
3688 None,
3689 );
3690 assert_eq!(
3692 auto.decide(&obs(
3693 OrderingMode::Unordered, QosOrdering::PerProducer, 250.0, 500)),
3694 Some(OrderingMode::MergeByStamp),
3695 );
3696 assert_eq!(
3698 auto.decide(&obs(
3699 OrderingMode::MergeByStamp, QosOrdering::PerProducer, 0.0, 500)),
3700 None,
3701 );
3702
3703 let cold = DefaultOrderingPolicy {
3705 hysteresis: std::time::Duration::from_secs(10),
3706 auto_order_threshold: Some(1.0),
3707 };
3708 assert_eq!(
3709 cold.decide(&obs(
3710 OrderingMode::Unordered, QosOrdering::GlobalFifo, 1e6, 1)),
3711 None,
3712 );
3713 }
3714
3715 #[test]
3716 fn sidecar_spawn_with_qos_flips_merge_flag_on_declaration() {
3717 let ring = Arc::new(stamped_anon(2, 1, StampKind::SharedCounter));
3718 ring.morph_to(RingShape::Mpsc).unwrap();
3719 let _p0 = ring.register_producer().unwrap();
3720 let _p1 = ring.register_producer().unwrap();
3721 let _c0 = ring.register_consumer().unwrap();
3722
3723 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3724 let sidecar = AdaptiveRingSidecar::spawn_with_qos(
3725 ring.clone(),
3726 QosRingShapePolicy {
3727 qos: qos.clone(),
3728 hysteresis: std::time::Duration::from_millis(5),
3729 },
3730 DefaultOrderingPolicy {
3731 hysteresis: std::time::Duration::from_millis(5),
3732 auto_order_threshold: None,
3733 },
3734 qos.clone(),
3735 std::time::Duration::from_millis(10),
3736 );
3737
3738 qos.set_ordering(crate::qos_policy::Ordering::GlobalFifo);
3741 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
3742 while std::time::Instant::now() < deadline
3743 && ring.ordering_mode() != Some(OrderingMode::MergeByStamp)
3744 {
3745 std::thread::sleep(std::time::Duration::from_millis(10));
3746 }
3747 assert_eq!(ring.ordering_mode(), Some(OrderingMode::MergeByStamp),
3748 "sidecar must arm the merge flag on the GlobalFifo declaration");
3749 assert_eq!(ring.current_shape(), RingShape::Mpsc,
3750 "stamped ring must stay composed (no Vyukov morph)");
3751 assert!(sidecar.ordering_flips() >= 1);
3752
3753 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3755 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
3756 while std::time::Instant::now() < deadline
3757 && ring.ordering_mode() != Some(OrderingMode::Unordered)
3758 {
3759 std::thread::sleep(std::time::Duration::from_millis(10));
3760 }
3761 assert_eq!(ring.ordering_mode(), Some(OrderingMode::Unordered),
3762 "sidecar must disarm when the declaration is withdrawn");
3763 sidecar.shutdown();
3764 }
3765
3766 #[test]
3767 fn default_sidecar_gates_auto_arm_but_still_opens_on_sustained_inversions() {
3768 let ring = Arc::new(stamped_anon(2, 1, StampKind::SharedCounter));
3776 ring.morph_to(RingShape::Mpsc).unwrap();
3777 ring.register_producer().unwrap();
3778 ring.register_producer().unwrap();
3779 ring.register_consumer().unwrap();
3780 ring.set_ordering_mode(OrderingMode::Unordered).unwrap();
3781
3782 let qos = Arc::new(crate::qos_policy::QosPolicy::default());
3783 qos.set_ordering(crate::qos_policy::Ordering::PerProducer);
3784 let sidecar = AdaptiveRingSidecar::spawn_with_qos(
3785 ring.clone(),
3786 DefaultRingShapePolicy::default(),
3787 DefaultOrderingPolicy {
3788 hysteresis: std::time::Duration::from_millis(0),
3789 auto_order_threshold: Some(50.0),
3790 },
3791 qos.clone(),
3792 std::time::Duration::from_millis(5),
3793 );
3794
3795 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
3796 let stop_c = stop.clone();
3797 let r = ring.clone();
3798 let consumer = std::thread::spawn(move || {
3799 let mut out = [0u8; 64];
3800 while !stop_c.load(Ordering::Acquire) {
3801 r.try_recv(0, &mut out).ok();
3802 }
3803 });
3804
3805 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(6);
3806 let mut seq = 0u64;
3807 while std::time::Instant::now() < deadline
3808 && ring.ordering_mode() != Some(OrderingMode::MergeByStamp)
3809 {
3810 ring.try_send(0, &seq.to_le_bytes()).ok();
3811 ring.try_send(1, &seq.to_le_bytes()).ok();
3812 seq += 1;
3813 }
3814 stop.store(true, Ordering::Release);
3815 consumer.join().unwrap();
3816
3817 assert_eq!(ring.ordering_mode(), Some(OrderingMode::MergeByStamp),
3818 "the gated auto-arm must still commit under sustained inversions");
3819 assert_eq!(sidecar.ordering_flips(), 1,
3820 "the one-way auto-arm fires exactly once");
3821 sidecar.shutdown();
3822 }
3823}