1use std::fs::{File, OpenOptions};
95use std::path::Path;
96use std::sync::atomic::{AtomicU32, AtomicU64, Ordering as AtomOrd};
97
98use memmap2::{MmapMut, MmapOptions};
99
100use crate::shared_ring::RingError;
101
102pub const ORDERING_MAGIC: u64 = 0x4F52_4452_0000_0001;
104
105pub const STAMPED_PAYLOAD_BYTES: usize = 56;
110
111pub const STAMP_BYTES: usize = 8;
113
114pub const TSC_FRESHNESS_GUARD_CYCLES: u64 = 8192;
121
122pub const MONOTONIC_FRESHNESS_GUARD_NANOS: u64 = 2_000;
125
126#[repr(u32)]
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum OrderingMode {
130 Unordered = 0,
134 MergeByStamp = 1,
138 MergeStrict = 2,
143}
144
145impl OrderingMode {
146 fn from_u32(tag: u32) -> Self {
147 match tag {
148 0 => Self::Unordered,
149 1 => Self::MergeByStamp,
150 2 => Self::MergeStrict,
151 _ => panic!("OrderingHeader.mode corrupted: {tag}"),
152 }
153 }
154}
155
156#[repr(u32)]
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum StampKind {
162 Tsc = 0,
164 SharedCounter = 1,
166 Monotonic = 2,
168}
169
170impl StampKind {
171 fn from_u32(tag: u32) -> Option<Self> {
172 match tag {
173 0 => Some(Self::Tsc),
174 1 => Some(Self::SharedCounter),
175 2 => Some(Self::Monotonic),
176 _ => None,
177 }
178 }
179
180 pub fn has_time_semantics(self) -> bool {
183 matches!(self, Self::Tsc | Self::Monotonic)
184 }
185
186 pub fn freshness_guard(self) -> Option<u64> {
190 match self {
191 Self::Tsc => Some(TSC_FRESHNESS_GUARD_CYCLES),
192 Self::Monotonic => Some(MONOTONIC_FRESHNESS_GUARD_NANOS),
193 Self::SharedCounter => None,
194 }
195 }
196}
197
198pub fn default_stamp_kind() -> StampKind {
202 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
203 {
204 if has_invariant_tsc() {
205 StampKind::Tsc
206 } else {
207 StampKind::SharedCounter
208 }
209 }
210 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
211 {
212 StampKind::Monotonic
213 }
214}
215
216#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
221pub fn has_invariant_tsc() -> bool {
222 #[cfg(target_arch = "x86_64")]
223 use core::arch::x86_64::__cpuid;
224 #[cfg(target_arch = "x86")]
225 use core::arch::x86::__cpuid;
226
227 let max_extended = __cpuid(0x8000_0000).eax;
228 if max_extended < 0x8000_0007 {
229 return false;
230 }
231 let power = __cpuid(0x8000_0007);
232 (power.edx & (1 << 8)) != 0
233}
234
235#[cfg(target_arch = "aarch64")]
238pub fn has_invariant_tsc() -> bool {
239 true
240}
241
242#[cfg(not(any(
245 target_arch = "x86",
246 target_arch = "x86_64",
247 target_arch = "aarch64"
248)))]
249pub fn has_invariant_tsc() -> bool {
250 false
251}
252
253#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
257#[inline]
258pub fn read_tsc() -> u64 {
259 #[cfg(target_arch = "x86_64")]
260 unsafe {
261 core::arch::x86_64::_rdtsc()
262 }
263 #[cfg(target_arch = "x86")]
264 unsafe {
265 core::arch::x86::_rdtsc()
266 }
267}
268
269#[cfg(target_arch = "aarch64")]
272#[inline]
273pub fn read_tsc() -> u64 {
274 let v: u64;
275 unsafe {
276 core::arch::asm!(
277 "mrs {v}, cntvct_el0",
278 v = out(reg) v,
279 options(nomem, nostack, preserves_flags),
280 );
281 }
282 v
283}
284
285#[cfg(target_arch = "aarch64")]
289#[inline]
290pub fn counter_frequency_hz() -> u64 {
291 let v: u64;
292 unsafe {
293 core::arch::asm!(
294 "mrs {v}, cntfrq_el0",
295 v = out(reg) v,
296 options(nomem, nostack, preserves_flags),
297 );
298 }
299 v
300}
301
302#[cfg(not(any(
303 target_arch = "x86",
304 target_arch = "x86_64",
305 target_arch = "aarch64"
306)))]
307#[inline]
308pub fn read_tsc() -> u64 {
309 monotonic_nanos()
310}
311
312#[cfg(unix)]
315#[inline]
316pub fn monotonic_nanos() -> u64 {
317 let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
318 let rc = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
319 assert_eq!(rc, 0, "clock_gettime(CLOCK_MONOTONIC) failed");
320 (ts.tv_sec as u64).wrapping_mul(1_000_000_000).wrapping_add(ts.tv_nsec as u64)
321}
322
323#[cfg(windows)]
327#[inline]
328pub fn monotonic_nanos() -> u64 {
329 use windows_sys::Win32::System::Performance::{
330 QueryPerformanceCounter, QueryPerformanceFrequency,
331 };
332 use std::sync::OnceLock;
333 static FREQ: OnceLock<i64> = OnceLock::new();
334 let freq = *FREQ.get_or_init(|| {
335 let mut f: i64 = 0;
336 let ok = unsafe { QueryPerformanceFrequency(&mut f) };
337 assert!(ok != 0 && f > 0, "QueryPerformanceFrequency failed");
338 f
339 });
340 let mut count: i64 = 0;
341 let ok = unsafe { QueryPerformanceCounter(&mut count) };
342 assert!(ok != 0, "QueryPerformanceCounter failed");
343 let secs = (count as u64) / (freq as u64);
346 let rem = (count as u64) % (freq as u64);
347 secs.wrapping_mul(1_000_000_000)
348 .wrapping_add(rem.wrapping_mul(1_000_000_000) / (freq as u64))
349}
350
351#[inline]
354pub(crate) fn stamp_now(kind: StampKind) -> u64 {
355 match kind {
356 StampKind::Tsc => read_tsc(),
357 StampKind::Monotonic => monotonic_nanos(),
358 StampKind::SharedCounter => 0,
359 }
360}
361
362#[repr(C, align(64))]
364pub struct OrderingHeader {
365 pub magic: u64,
366 pub mode: AtomicU32,
370 pub stamp_kind: u32,
373 pub inversions: AtomicU64,
377 pub shared_stamp: AtomicU64,
379 pub drainer_token: AtomicU64,
381 pub drainer_heartbeat: AtomicU64,
384 pub drainer_epoch: AtomicU64,
387 _pad: [u8; 8],
388}
389
390#[repr(C, align(64))]
400pub struct LeaseGenLine {
401 pub lease_generation: AtomicU64,
402 _pad: [u8; 56],
403}
404
405#[repr(C, align(64))]
409pub struct ProducerLine {
410 pub issued: AtomicU64,
413 pub watermark: AtomicU64,
417 _pad: [u8; 48],
418}
419
420pub const fn ordering_region_size(max_producers: usize) -> usize {
422 std::mem::size_of::<OrderingHeader>()
423 + std::mem::size_of::<LeaseGenLine>()
424 + max_producers * std::mem::size_of::<ProducerLine>()
425}
426
427#[allow(dead_code)]
430enum OrderingBacking {
431 Anon(MmapMut),
433 File(File, MmapMut),
435 Shm(crate::shm_file::ShmFile),
437}
438
439pub struct OrderingRegion {
442 _backing: OrderingBacking,
443 raw_ptr: *mut u8,
444 max_producers: usize,
445 kind: StampKind,
446}
447
448unsafe impl Send for OrderingRegion {}
449unsafe impl Sync for OrderingRegion {}
450
451unsafe fn init_ordering_layout(ptr: *mut u8, max_producers: usize, kind: StampKind) {
452 let header_ptr = ptr as *mut OrderingHeader;
453 unsafe {
454 std::ptr::write(header_ptr, OrderingHeader {
455 magic: ORDERING_MAGIC,
456 mode: AtomicU32::new(OrderingMode::Unordered as u32),
457 stamp_kind: kind as u32,
458 inversions: AtomicU64::new(0),
459 shared_stamp: AtomicU64::new(0),
460 drainer_token: AtomicU64::new(0),
461 drainer_heartbeat: AtomicU64::new(0),
462 drainer_epoch: AtomicU64::new(0),
463 _pad: [0; 8],
464 });
465 }
466 let gen_ptr = unsafe {
467 ptr.add(std::mem::size_of::<OrderingHeader>()) as *mut LeaseGenLine
468 };
469 unsafe {
470 std::ptr::write(gen_ptr, LeaseGenLine {
471 lease_generation: AtomicU64::new(0),
472 _pad: [0; 56],
473 });
474 }
475 let lines_base = unsafe {
476 ptr.add(std::mem::size_of::<OrderingHeader>()
477 + std::mem::size_of::<LeaseGenLine>())
478 };
479 for i in 0..max_producers {
480 let line_ptr = unsafe {
481 lines_base.add(i * std::mem::size_of::<ProducerLine>()) as *mut ProducerLine
482 };
483 unsafe {
484 std::ptr::write(line_ptr, ProducerLine {
485 issued: AtomicU64::new(0),
486 watermark: AtomicU64::new(0),
487 _pad: [0; 48],
488 });
489 }
490 }
491}
492
493impl OrderingRegion {
494 pub fn create_anon(max_producers: usize, kind: StampKind) -> Result<Self, RingError> {
496 let total = ordering_region_size(max_producers);
497 let mut mmap = MmapOptions::new().len(total).map_anon()?;
498 unsafe { init_ordering_layout(mmap.as_mut_ptr(), max_producers, kind) };
499 let raw_ptr = mmap.as_mut_ptr();
500 Ok(Self {
501 _backing: OrderingBacking::Anon(mmap),
502 raw_ptr,
503 max_producers,
504 kind,
505 })
506 }
507
508 pub fn create(
510 path: impl AsRef<Path>,
511 max_producers: usize,
512 kind: StampKind,
513 ) -> Result<Self, RingError> {
514 let total = ordering_region_size(max_producers);
515 let file = OpenOptions::new()
516 .read(true).write(true).create(true).truncate(true)
517 .open(path.as_ref())?;
518 file.set_len(total as u64)?;
519 let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
520 unsafe { init_ordering_layout(mmap.as_mut_ptr(), max_producers, kind) };
521 let raw_ptr = mmap.as_mut_ptr();
522 Ok(Self {
523 _backing: OrderingBacking::File(file, mmap),
524 raw_ptr,
525 max_producers,
526 kind,
527 })
528 }
529
530 pub fn open(
535 path: impl AsRef<Path>,
536 max_producers: usize,
537 ) -> Result<Self, RingError> {
538 let total = ordering_region_size(max_producers);
539 let file = OpenOptions::new().read(true).write(true).open(path.as_ref())?;
540 if (file.metadata()?.len() as usize) < total {
541 return Err(RingError::LayoutMismatch);
542 }
543 let mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
544 let header = unsafe { &*(mmap.as_ptr() as *const OrderingHeader) };
545 if header.magic != ORDERING_MAGIC {
546 return Err(RingError::LayoutMismatch);
547 }
548 let kind = StampKind::from_u32(header.stamp_kind)
549 .ok_or(RingError::LayoutMismatch)?;
550 let raw_ptr = mmap.as_ptr() as *mut u8;
551 Ok(Self {
552 _backing: OrderingBacking::File(file, mmap),
553 raw_ptr,
554 max_producers,
555 kind,
556 })
557 }
558
559 pub fn create_shm(
562 shm: crate::shm_file::ShmFile,
563 max_producers: usize,
564 kind: StampKind,
565 ) -> Result<Self, RingError> {
566 let total = ordering_region_size(max_producers);
567 let mut shm = shm;
568 if shm.len() < total {
569 return Err(RingError::LayoutMismatch);
570 }
571 let raw_ptr = shm.as_mut_slice().as_mut_ptr();
572 unsafe { init_ordering_layout(raw_ptr, max_producers, kind) };
573 Ok(Self {
574 _backing: OrderingBacking::Shm(shm),
575 raw_ptr,
576 max_producers,
577 kind,
578 })
579 }
580
581 pub fn stamp_kind(&self) -> StampKind { self.kind }
583
584 pub fn max_producers(&self) -> usize { self.max_producers }
586
587 pub(crate) fn header(&self) -> &OrderingHeader {
588 unsafe { &*(self.raw_ptr as *const OrderingHeader) }
589 }
590
591 pub(crate) fn line(&self, producer_id: usize) -> &ProducerLine {
592 assert!(producer_id < self.max_producers,
593 "producer_id {producer_id} out of range (max {})", self.max_producers);
594 let lines_base = unsafe {
595 self.raw_ptr.add(std::mem::size_of::<OrderingHeader>()
596 + std::mem::size_of::<LeaseGenLine>())
597 };
598 unsafe {
599 &*(lines_base.add(producer_id * std::mem::size_of::<ProducerLine>())
600 as *const ProducerLine)
601 }
602 }
603
604 fn lease_gen_line(&self) -> &LeaseGenLine {
605 unsafe {
606 &*(self.raw_ptr.add(std::mem::size_of::<OrderingHeader>())
607 as *const LeaseGenLine)
608 }
609 }
610
611 #[inline]
618 pub fn lease_generation(&self) -> u64 {
619 self.lease_gen_line().lease_generation.load(AtomOrd::Acquire)
620 }
621
622 fn bump_lease_generation(&self) {
623 self.lease_gen_line().lease_generation.fetch_add(1, AtomOrd::AcqRel);
624 }
625
626 pub fn mode(&self) -> OrderingMode {
628 OrderingMode::from_u32(self.header().mode.load(AtomOrd::Acquire))
629 }
630
631 pub fn set_mode(&self, mode: OrderingMode) {
636 self.header().mode.store(mode as u32, AtomOrd::Release);
637 }
638
639 pub fn inversions(&self) -> u64 {
641 self.header().inversions.load(AtomOrd::Relaxed)
642 }
643
644 pub(crate) fn record_inversion(&self) {
645 self.header().inversions.fetch_add(1, AtomOrd::Relaxed);
646 }
647
648 #[inline]
663 pub(crate) fn next_stamp(&self, producer_id: usize) -> u64 {
664 let line = self.line(producer_id);
665 let floor = line.issued.load(AtomOrd::Relaxed) + 1;
666 line.issued.store(floor, AtomOrd::Release);
667 let raw = match self.kind {
668 StampKind::Tsc => read_tsc(),
669 StampKind::Monotonic => monotonic_nanos(),
670 StampKind::SharedCounter => {
671 self.header().shared_stamp.fetch_add(1, AtomOrd::Relaxed) + 1
672 }
673 };
674 let stamp = raw.max(floor);
675 line.issued.store(stamp, AtomOrd::Release);
676 stamp
677 }
678
679 #[inline]
686 pub(crate) fn publish_watermark(&self, producer_id: usize, stamp: u64) {
687 self.line(producer_id).watermark.store(stamp, AtomOrd::Release);
688 }
689
690 #[inline]
697 pub(crate) fn in_flight_below(&self, producer_id: usize, candidate: u64) -> bool {
698 let line = self.line(producer_id);
699 let issued = line.issued.load(AtomOrd::Acquire);
700 if issued >= candidate {
701 return false;
702 }
703 issued != line.watermark.load(AtomOrd::Acquire)
704 }
705
706 pub fn refresh_watermark(&self, producer_id: usize) {
713 let line = self.line(producer_id);
714 let raw = match self.kind {
715 StampKind::Tsc => read_tsc(),
716 StampKind::Monotonic => monotonic_nanos(),
717 StampKind::SharedCounter => {
722 self.header().shared_stamp.fetch_add(1, AtomOrd::Relaxed) + 1
723 }
724 };
725 let floor = line.issued.load(AtomOrd::Relaxed) + 1;
726 let stamp = raw.max(floor);
727 line.issued.store(stamp, AtomOrd::Release);
728 line.watermark.store(stamp, AtomOrd::Release);
729 }
730
731 pub fn watermark(&self, producer_id: usize) -> u64 {
733 self.line(producer_id).watermark.load(AtomOrd::Acquire)
734 }
735
736 pub fn issued(&self, producer_id: usize) -> u64 {
739 self.line(producer_id).issued.load(AtomOrd::Acquire)
740 }
741
742 pub fn retire_producer(&self, producer_id: usize) {
750 let line = self.line(producer_id);
751 line.issued.store(u64::MAX, AtomOrd::Release);
752 line.watermark.store(u64::MAX, AtomOrd::Release);
753 }
754
755 pub fn seed_from(&self, other: &OrderingRegion) {
759 self.header().shared_stamp.store(
760 other.header().shared_stamp.load(AtomOrd::Acquire),
761 AtomOrd::Release,
762 );
763 self.header().inversions.store(
764 other.header().inversions.load(AtomOrd::Relaxed),
765 AtomOrd::Relaxed,
766 );
767 let n = self.max_producers.min(other.max_producers);
768 for i in 0..n {
769 self.line(i).issued.store(
770 other.line(i).issued.load(AtomOrd::Relaxed),
771 AtomOrd::Relaxed,
772 );
773 self.line(i).watermark.store(
774 other.line(i).watermark.load(AtomOrd::Acquire),
775 AtomOrd::Release,
776 );
777 }
778 self.set_mode(other.mode());
779 }
780
781 pub fn try_acquire_drainer(&self, token: u64, grace_epochs: u64) -> bool {
794 assert!(token != 0, "drainer token 0 is reserved for unleased");
795 let header = self.header();
796 loop {
797 let cur = header.drainer_token.load(AtomOrd::Acquire);
798 if cur == token {
799 let global = header.drainer_epoch.load(AtomOrd::Acquire);
807 if header.drainer_heartbeat.load(AtomOrd::Relaxed) != global {
808 header.drainer_heartbeat.store(global, AtomOrd::Release);
809 }
810 return true;
811 }
812 let can_claim = if cur == 0 {
813 true
814 } else {
815 let beat = header.drainer_heartbeat.load(AtomOrd::Acquire);
816 let global = header.drainer_epoch.load(AtomOrd::Acquire);
817 global.saturating_sub(beat) > grace_epochs
818 };
819 if !can_claim {
820 return false;
821 }
822 if header.drainer_token.compare_exchange(
823 cur, token, AtomOrd::AcqRel, AtomOrd::Acquire,
824 ).is_ok() {
825 let global = header.drainer_epoch.load(AtomOrd::Acquire);
826 header.drainer_heartbeat.store(global, AtomOrd::Release);
827 self.bump_lease_generation();
830 return true;
831 }
832 std::hint::spin_loop();
833 }
834 }
835
836 pub fn release_drainer(&self, token: u64) -> bool {
839 let released = self.header().drainer_token
840 .compare_exchange(token, 0, AtomOrd::AcqRel, AtomOrd::Acquire)
841 .is_ok();
842 if released {
843 self.bump_lease_generation();
844 }
845 released
846 }
847
848 pub fn current_drainer(&self) -> u64 {
850 self.header().drainer_token.load(AtomOrd::Acquire)
851 }
852
853 pub fn drainer_beat(&self, token: u64) -> bool {
856 let header = self.header();
857 if header.drainer_token.load(AtomOrd::Acquire) != token {
858 return false;
859 }
860 let global = header.drainer_epoch.load(AtomOrd::Acquire);
861 header.drainer_heartbeat.store(global, AtomOrd::Release);
862 true
863 }
864
865 pub fn tick_drainer_epoch(&self) -> u64 {
871 let epoch = self.header().drainer_epoch.fetch_add(1, AtomOrd::AcqRel) + 1;
872 self.bump_lease_generation();
873 epoch
874 }
875}
876
877#[cfg(test)]
878mod tests {
879 use super::*;
880
881 #[test]
882 fn header_is_one_cache_line_and_lines_are_padded() {
883 assert_eq!(std::mem::size_of::<OrderingHeader>(), 64);
884 assert_eq!(std::mem::size_of::<LeaseGenLine>(), 64);
885 assert_eq!(std::mem::size_of::<ProducerLine>(), 64);
886 assert_eq!(ordering_region_size(4), 64 + 64 + 4 * 64);
888 }
889
890 #[test]
891 fn lease_generation_bumps_on_claim_release_and_tick_only() {
892 let region = OrderingRegion::create_anon(2, StampKind::SharedCounter).unwrap();
893 let g0 = region.lease_generation();
894 assert!(region.try_acquire_drainer(7, 3));
895 let g1 = region.lease_generation();
896 assert!(g1 > g0, "claim must bump the generation");
897 assert!(region.try_acquire_drainer(7, 3));
900 assert_eq!(region.lease_generation(), g1);
901 region.tick_drainer_epoch();
902 let g2 = region.lease_generation();
903 assert!(g2 > g1, "epoch tick must bump so the holder re-beats");
904 assert!(region.release_drainer(7));
905 assert!(region.lease_generation() > g2, "release must bump");
906 }
907
908 #[test]
909 fn probe_runs_without_fault_and_tsc_reads_advance() {
910 let invariant = has_invariant_tsc();
913 if invariant {
914 let a = read_tsc();
915 let mut spin = 0u64;
916 for i in 0..10_000u64 { spin = spin.wrapping_add(i); }
917 std::hint::black_box(spin);
918 let b = read_tsc();
919 assert!(b > a, "TSC must advance across real work: {a} -> {b}");
920 }
921 }
922
923 #[test]
924 fn monotonic_clock_advances() {
925 let a = monotonic_nanos();
926 std::thread::sleep(std::time::Duration::from_millis(2));
927 let b = monotonic_nanos();
928 assert!(b > a, "monotonic clock must advance: {a} -> {b}");
929 }
930
931 #[test]
932 fn default_kind_matches_probe_chain() {
933 let kind = default_stamp_kind();
934 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
935 {
936 if has_invariant_tsc() {
937 assert_eq!(kind, StampKind::Tsc);
938 } else {
939 assert_eq!(kind, StampKind::SharedCounter);
940 }
941 }
942 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
943 assert_eq!(kind, StampKind::Monotonic);
944 }
945
946 #[test]
947 fn stamps_strictly_increase_per_producer_every_kind() {
948 for kind in [StampKind::Tsc, StampKind::SharedCounter, StampKind::Monotonic] {
949 let region = OrderingRegion::create_anon(2, kind).unwrap();
950 let mut last = 0u64;
951 for _ in 0..10_000 {
952 let s = region.next_stamp(0);
953 assert!(s > last, "{kind:?} stamp must strictly increase: {last} -> {s}");
954 last = s;
955 }
956 }
957 }
958
959 #[test]
960 fn counter_stamps_are_globally_unique_across_producers() {
961 let region = OrderingRegion::create_anon(4, StampKind::SharedCounter).unwrap();
962 let mut seen = std::collections::HashSet::new();
963 for p in 0..4 {
964 for _ in 0..100 {
965 assert!(seen.insert(region.next_stamp(p)),
966 "counter stamps must never repeat");
967 }
968 }
969 }
970
971 #[test]
972 fn watermark_publishes_and_refreshes() {
973 let region = OrderingRegion::create_anon(2, StampKind::Monotonic).unwrap();
974 assert_eq!(region.watermark(0), 0);
975 let s = region.next_stamp(0);
976 region.publish_watermark(0, s);
977 assert_eq!(region.watermark(0), s);
978 region.refresh_watermark(0);
979 assert!(region.watermark(0) > s, "refresh must advance the watermark");
980 }
981
982 #[test]
983 fn mode_flips_round_trip() {
984 let region = OrderingRegion::create_anon(1, StampKind::Monotonic).unwrap();
985 assert_eq!(region.mode(), OrderingMode::Unordered);
986 region.set_mode(OrderingMode::MergeByStamp);
987 assert_eq!(region.mode(), OrderingMode::MergeByStamp);
988 region.set_mode(OrderingMode::MergeStrict);
989 assert_eq!(region.mode(), OrderingMode::MergeStrict);
990 region.set_mode(OrderingMode::Unordered);
991 assert_eq!(region.mode(), OrderingMode::Unordered);
992 }
993
994 #[test]
995 fn file_region_open_validates_and_adopts_kind() {
996 let p = std::env::temp_dir().join(format!(
997 "subetha-ordering-open-{}-{}.bin",
998 std::process::id(),
999 std::time::SystemTime::now()
1000 .duration_since(std::time::UNIX_EPOCH)
1001 .map(|d| d.as_nanos()).unwrap_or(0),
1002 ));
1003 {
1004 let creator =
1005 OrderingRegion::create(&p, 3, StampKind::SharedCounter).unwrap();
1006 creator.set_mode(OrderingMode::MergeByStamp);
1007 let s = creator.next_stamp(1);
1008 creator.publish_watermark(1, s);
1009 }
1010 let opened = OrderingRegion::open(&p, 3).unwrap();
1011 assert_eq!(opened.stamp_kind(), StampKind::SharedCounter,
1012 "opener must adopt the creator's stamp kind");
1013 assert_eq!(opened.mode(), OrderingMode::MergeByStamp,
1014 "open must not re-initialise the live mode flag");
1015 assert!(opened.watermark(1) > 0,
1016 "open must not wipe watermarks");
1017 std::fs::remove_file(&p).ok();
1018 }
1019
1020 #[test]
1021 fn open_rejects_garbage() {
1022 let p = std::env::temp_dir().join(format!(
1023 "subetha-ordering-garbage-{}.bin", std::process::id(),
1024 ));
1025 std::fs::write(&p, vec![0u8; ordering_region_size(2)]).unwrap();
1026 assert!(matches!(
1027 OrderingRegion::open(&p, 2),
1028 Err(RingError::LayoutMismatch)
1029 ));
1030 std::fs::remove_file(&p).ok();
1031 }
1032
1033 #[test]
1034 fn drainer_lease_claim_refresh_release_takeover() {
1035 let region = OrderingRegion::create_anon(1, StampKind::Monotonic).unwrap();
1036 let a = 100u64 << 32;
1038 let b = 200u64 << 32;
1039
1040 assert!(region.try_acquire_drainer(a, 3));
1042 assert_eq!(region.current_drainer(), a);
1043 assert!(region.try_acquire_drainer(a, 3));
1045 assert!(!region.try_acquire_drainer(b, 3));
1047 assert!(region.release_drainer(a));
1049 assert!(region.try_acquire_drainer(b, 3));
1050 for _ in 0..5 { region.tick_drainer_epoch(); }
1052 assert!(region.try_acquire_drainer(a, 3),
1053 "stale drainer must be preemptible after grace epochs");
1054 assert_eq!(region.current_drainer(), a);
1055 assert!(!region.drainer_beat(b));
1057 assert!(region.drainer_beat(a));
1058 }
1059
1060 #[test]
1061 fn seed_from_carries_counter_and_watermarks() {
1062 let old = OrderingRegion::create_anon(2, StampKind::SharedCounter).unwrap();
1063 for _ in 0..50 { old.next_stamp(0); }
1064 let w = old.next_stamp(1);
1065 old.publish_watermark(1, w);
1066 old.set_mode(OrderingMode::MergeStrict);
1067
1068 let fresh = OrderingRegion::create_anon(2, StampKind::SharedCounter).unwrap();
1069 fresh.seed_from(&old);
1070 assert_eq!(fresh.mode(), OrderingMode::MergeStrict);
1071 assert_eq!(fresh.watermark(1), w);
1072 let next = fresh.next_stamp(0);
1073 assert!(next > w, "seeded counter must continue past the old region's stamps");
1074 }
1075}