Skip to main content

subetha_cxc/
ordering.rs

1//! Ordering substrate for [`AdaptiveRing`](crate::AdaptiveRing):
2//! push stamps, the cross-process ordering header, per-producer
3//! watermarks, and the single-drainer lease.
4//!
5//! The composed MPSC / MPMC shapes give per-producer FIFO only.
6//! This module is what turns global FIFO into a consumer-side
7//! discipline on those shapes: every push carries an 8-byte stamp
8//! in slot bytes `[0..8)`, and a consumer that k-way-merges ring
9//! heads by stamp delivers items in global stamp order without the
10//! Vyukov data structure's shared-CAS cost on the producer side.
11//!
12//! # Stamp sources
13//!
14//! - [`StampKind::Tsc`]: `rdtsc` per push (~20 cycles, zero
15//!   coherence traffic). Selected only when the invariant-TSC probe
16//!   passes: CPUID leaf `0x8000_0007` EDX bit 8, which both Intel
17//!   ("Invariant TSC available if 1", SDM CPUID reference) and AMD
18//!   ("TSC runs at constant rate with P/T states and does not stop
19//!   in deep C-states", APM `8000_0007h` EDX) define identically.
20//!   The probe first confirms the extended leaf exists via CPUID
21//!   `0x8000_0000`. Cross-core skew on one socket is nanoseconds;
22//!   the merge treats it as the documented approximation window.
23//! - [`StampKind::SharedCounter`]: `fetch_add` on a shared atom in
24//!   the ordering header. Exact total order, but every producer
25//!   pays the contended-cache-line cost the composed shapes
26//!   otherwise avoid. Opt-in for callers that need exactness and
27//!   accept the contention.
28//! - [`StampKind::Monotonic`]: system-wide monotonic clock
29//!   (`CLOCK_MONOTONIC` on unix, `QueryPerformanceCounter` on
30//!   Windows). The non-x86 fallback; also valid on x86.
31//!
32//! Per-producer stamp monotonicity is enforced at the stamp site:
33//! the issued stamp is `max(source_now, last_issued + 1)`, so a
34//! producer thread migrating across cores with slightly-skewed TSC
35//! reads still emits strictly increasing stamps.
36//!
37//! # The ordering region
38//!
39//! One small shared region per stamped ring, separate from the ring
40//! backings, holding the header below plus one cache line per
41//! producer slot:
42//!
43//! ```text
44//! +------------------------------------------------+
45//! | OrderingHeader (one cache line)                |
46//! |   magic: u64                                   |
47//! |   mode: AtomicU32 (0=Unordered, 1=MergeByStamp,|
48//! |         2=MergeStrict)                         |
49//! |   stamp_kind: u32 (0=Tsc, 1=SharedCounter,     |
50//! |         2=Monotonic)                           |
51//! |   inversions: AtomicU64                        |
52//! |   shared_stamp: AtomicU64 (counter mode)       |
53//! |   drainer_token: AtomicU64                     |
54//! |   drainer_heartbeat: AtomicU64                 |
55//! |   drainer_epoch: AtomicU64                     |
56//! +------------------------------------------------+
57//! | ProducerLine[0]: issued + watermark (64B)      |
58//! | ProducerLine[1]: ...                           |
59//! | ... max_producers lines ...                    |
60//! +------------------------------------------------+
61//! ```
62//!
63//! File locale: `<prefix>.ordering.bin`. ShmFs locale:
64//! `{prefix}_ordering` named region. Anon: in-process page. The
65//! region is MMF-resident on purpose: the ordered-switch flag must
66//! be visible to every process attached to the ring, unlike the
67//! process-local `shape_tag`.
68//!
69//! # Watermarks (MergeStrict)
70//!
71//! `ProducerLine.watermark` is the producer's last PUBLISHED stamp,
72//! stored with `Release` after the ring push. Items inside a ring
73//! are stamp-ordered per producer, so a non-empty ring's head bounds
74//! everything that producer has in flight. An EMPTY ring's producer
75//! may hold a stamped-but-unpublished item, bounded below by its
76//! watermark: any future item from producer `j` has stamp
77//! `> watermark[j]`. The strict release gate is therefore
78//! `candidate <= min(watermark[j])` over empty, in-use rings. Idle
79//! producers refresh their watermark (a heartbeat) so the gate does
80//! not couple consumer latency to producer silence forever.
81//!
82//! # Drainer lease
83//!
84//! With M concurrent consumers, "global FIFO delivery" is
85//! meaningless downstream - two concurrent pops race regardless of
86//! pop order - so merge mode implies ONE active drainer. The lease
87//! lives in the header (`drainer_token` + heartbeat + epoch) and
88//! follows the [`OwnerLease`](crate::OwnerLease) claim protocol
89//! (CAS-claim when free, heartbeat-grace takeover when the holder
90//! goes silent), embedded here so every locale - including Anon and
91//! ShmFs, which `OwnerLease`'s file backing cannot serve - gets the
92//! same mechanism from the same region.
93
94use 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
102/// Magic number identifying an ordering region. ASCII "ORDR" + version.
103pub const ORDERING_MAGIC: u64 = 0x4F52_4452_0000_0001;
104
105/// Payload bytes available per slot in stamped mode: the stamp
106/// costs 8 of the 64 Lamport slot bytes, leaving 56 - exactly the
107/// Vyukov payload size, since Vyukov spends the same 8 bytes on its
108/// per-slot sequence atom.
109pub const STAMPED_PAYLOAD_BYTES: usize = 56;
110
111/// Stamp width at the front of every stamped slot.
112pub const STAMP_BYTES: usize = 8;
113
114/// Freshness guard for TSC stamps during a merge pop: when at least
115/// one ring is empty, a candidate younger than this many cycles may
116/// be raced by a stamped-but-unpublished item from the empty ring's
117/// producer (the stamp-to-publish window). The merge re-peeks until
118/// the candidate ages past the guard. ~2-3us on contemporary cores;
119/// orders of magnitude above any producer's stamp-to-publish latency.
120pub const TSC_FRESHNESS_GUARD_CYCLES: u64 = 8192;
121
122/// Freshness guard for Monotonic stamps, in nanoseconds. Same role
123/// as [`TSC_FRESHNESS_GUARD_CYCLES`].
124pub const MONOTONIC_FRESHNESS_GUARD_NANOS: u64 = 2_000;
125
126/// How a stamped ring's consumer side interprets stamps.
127#[repr(u32)]
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum OrderingMode {
130    /// Existing partition pop. Stamps are read after the pop to
131    /// drive the inversion counter; ordering guarantee stays
132    /// per-producer FIFO.
133    Unordered = 0,
134    /// K-way min-stamp merge over the ring heads. Global FIFO
135    /// within the stamp source's skew window (freshness-guarded for
136    /// time-based stamps). Single active drainer.
137    MergeByStamp = 1,
138    /// As `MergeByStamp`, plus the per-producer watermark gate:
139    /// a candidate releases only once no empty in-use ring can
140    /// still produce a smaller stamp. Exact global FIFO for every
141    /// stamp kind, at the cost of slowest-producer latency coupling.
142    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/// Which clock the stamps come from. Fixed at region creation;
157/// openers read it from the header so every process attached to the
158/// ring stamps from the same source.
159#[repr(u32)]
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum StampKind {
162    /// `rdtsc` behind the invariant-TSC probe.
163    Tsc = 0,
164    /// `fetch_add` on the header's `shared_stamp` atom.
165    SharedCounter = 1,
166    /// System-wide monotonic clock in nanoseconds.
167    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    /// Whether stamps carry time semantics (enables the freshness
181    /// guard in `MergeByStamp`).
182    pub fn has_time_semantics(self) -> bool {
183        matches!(self, Self::Tsc | Self::Monotonic)
184    }
185
186    /// The freshness-guard window for this stamp kind, in the stamp
187    /// unit. `None` for counter stamps (no time semantics; exactness
188    /// comes from `MergeStrict`'s watermark gate instead).
189    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
198/// Pick the default stamp kind for this host: TSC when the
199/// invariant probe passes, the shared counter on x86 without an
200/// invariant TSC, and the monotonic clock everywhere else.
201pub 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/// Invariant-TSC probe: CPUID leaf `0x8000_0007` EDX bit 8, after
217/// confirming the leaf exists via CPUID `0x8000_0000` (the maximum
218/// extended function leaf, per the Intel SDM CPUID reference; AMD
219/// defines the same bit as TscInvariant in APM `8000_0007h` EDX).
220#[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/// AArch64's generic timer (`CNTVCT_EL0`) is constant-rate by
236/// architecture - the invariant property holds by construction.
237#[cfg(target_arch = "aarch64")]
238pub fn has_invariant_tsc() -> bool {
239    true
240}
241
242/// Non-x86 / non-aarch64 hosts have no architected counter; the
243/// probe is always false.
244#[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/// Raw TSC read. Callers go through the [`StampKind`] stamp
254/// plumbing; exposed for the merge pop's freshness-guard "now"
255/// reads.
256#[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/// AArch64: the virtual counter, EL0-readable, constant-rate,
270/// system-wide - the architected analog of the invariant TSC.
271#[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/// Counter frequency in Hz (`CNTFRQ_EL0`): converts cycle budgets
286/// to wall time on aarch64 (typically 24 MHz - 1 GHz, unlike the
287/// GHz-rate x86 TSC).
288#[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/// System-wide monotonic clock in nanoseconds. Comparable across
313/// processes on the same boot.
314#[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/// System-wide monotonic clock in nanoseconds via
324/// `QueryPerformanceCounter` (system-wide, comparable across
325/// processes on the same boot).
326#[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    // Split the conversion to avoid overflowing the intermediate
344    // product: whole seconds first, then the sub-second remainder.
345    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/// "Now" in the units of the given stamp kind. Counter stamps have
352/// no clock; callers never ask (the freshness guard is `None`).
353#[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/// Ordering header. One cache line at offset 0 of the region.
363#[repr(C, align(64))]
364pub struct OrderingHeader {
365    pub magic: u64,
366    /// Active [`OrderingMode`], cross-process visible. The ordered
367    /// switch is one `Release` store here; the in-flight backlog is
368    /// retroactively ordered because the stamps were already there.
369    pub mode: AtomicU32,
370    /// [`StampKind`] discriminant. Written once at creation; openers
371    /// adopt it.
372    pub stamp_kind: u32,
373    /// Cross-producer inversions observed at pop. The runtime signal
374    /// that converts the invisible ordering property into
375    /// "inversions/sec observed".
376    pub inversions: AtomicU64,
377    /// Stamp counter for [`StampKind::SharedCounter`].
378    pub shared_stamp: AtomicU64,
379    /// Drainer lease: `(pid << 32) | consumer_id`, 0 = unleased.
380    pub drainer_token: AtomicU64,
381    /// Drainer heartbeat: last `drainer_epoch` value the holder
382    /// confirmed liveness at.
383    pub drainer_heartbeat: AtomicU64,
384    /// Global epoch for heartbeat-grace takeover. Ticked by the
385    /// sidecar (or any caller); mirrors `OwnerLease::tick_epoch`.
386    pub drainer_epoch: AtomicU64,
387    _pad: [u8; 8],
388}
389
390/// Second header line: the drainer-lease GENERATION, alone on its
391/// own cache line. Bumped only on lease claim / takeover / release
392/// and on epoch ticks - all rare events - so a merge drainer's
393/// per-pop lease verification is one load of a line that is NEVER
394/// written in steady state (an L1 hit with zero coherence traffic),
395/// instead of loads on the first header line that every
396/// SharedCounter push fetch_adds. Measured on the Zen3 KVM guest:
397/// per-pop loads of that stamp-hot line cost a cache-to-cache
398/// transfer each (~130 ns) and tripled the merge rungs.
399#[repr(C, align(64))]
400pub struct LeaseGenLine {
401    pub lease_generation: AtomicU64,
402    _pad: [u8; 56],
403}
404
405/// Per-producer ordering state. One cache line per producer slot so
406/// one producer's stamp bookkeeping never invalidates a sibling's
407/// L1 line.
408#[repr(C, align(64))]
409pub struct ProducerLine {
410    /// Last ISSUED stamp (monotonicity floor: the next stamp is
411    /// `max(source_now, issued + 1)`).
412    pub issued: AtomicU64,
413    /// Last PUBLISHED stamp (the MergeStrict watermark). `Release`-
414    /// stored after the ring push; 0 = this producer slot has never
415    /// published or refreshed.
416    pub watermark: AtomicU64,
417    _pad: [u8; 48],
418}
419
420/// Total region size for `max_producers` producer slots.
421pub 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/// Backing-store owner for an ordering region; mirrors the ring
428/// backings' lifetime-extension pattern.
429#[allow(dead_code)]
430enum OrderingBacking {
431    /// In-process anonymous page.
432    Anon(MmapMut),
433    /// File-backed (cross-process via page cache).
434    File(File, MmapMut),
435    /// Named RAM-resident shared memory.
436    Shm(crate::shm_file::ShmFile),
437}
438
439/// The mapped ordering region: header + producer lines, in any of
440/// the three locales.
441pub 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    /// Anonymous in-process region, initialised.
495    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    /// File-backed region at `path`, initialised.
509    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    /// Open an existing file-backed region. Validates the magic and
531    /// adopts the creator's stamp kind; does NOT re-initialise, so
532    /// the live mode flag, counters, and watermarks survive the
533    /// attach.
534    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    /// Named-shm region, initialised. Mirrors the ring backings'
560    /// `create_from_shm` semantics (the creator initialises).
561    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    /// Stamp kind this region was created with.
582    pub fn stamp_kind(&self) -> StampKind { self.kind }
583
584    /// Number of producer lines.
585    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    /// The drainer-lease generation: bumped on every lease claim /
612    /// takeover / release and on every epoch tick, and NEVER written
613    /// otherwise. A merge drainer verifies its lease per pop with one
614    /// load of this quiet line (compared against a consumer-local
615    /// cache) and runs the full lease handshake only on change - the
616    /// per-pop path never touches the stamp-hot first header line.
617    #[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    /// Current ordering mode. One Acquire load.
627    pub fn mode(&self) -> OrderingMode {
628        OrderingMode::from_u32(self.header().mode.load(AtomOrd::Acquire))
629    }
630
631    /// Flip the ordering mode. Off->On is immediate and retroactive:
632    /// the in-flight backlog merges in stamp order because the
633    /// stamps were already in the slots. On->Off is immediate.
634    /// No drain, no data movement.
635    pub fn set_mode(&self, mode: OrderingMode) {
636        self.header().mode.store(mode as u32, AtomOrd::Release);
637    }
638
639    /// Cross-producer inversions observed since creation.
640    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    /// Issue the next stamp for `producer_id`: strictly increasing
649    /// per producer regardless of source skew.
650    ///
651    /// Two-phase: the producer RESERVES first (`issued = floor`, a
652    /// lower bound for the upcoming stamp, with the watermark still
653    /// behind), then reads the clock and stores the real stamp.
654    /// The reservation is what makes the merge's in-flight gate
655    /// airtight against preemption: from the very first store, any
656    /// merge candidate above the floor blocks until this push
657    /// publishes (or fails and finalizes the watermark) - even if
658    /// the producer is descheduled between the clock read and the
659    /// store, or between the stamp and the push. A fixed freshness
660    /// window cannot give that bound; deschedule latency is
661    /// unbounded.
662    #[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    /// Publish `stamp` as producer `producer_id`'s watermark. Called
680    /// after the ring push so an Acquire reader that sees the
681    /// watermark also sees the published slot. Also called when the
682    /// push returns `Full`: that stamp will never publish, so
683    /// advancing the watermark restores `issued == watermark` (the
684    /// "nothing in flight" state the MergeStrict gate keys on).
685    #[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    /// MergeStrict in-flight gate: `true` when producer
691    /// `producer_id` currently holds a stamped-but-unpublished item
692    /// whose stamp undercuts `candidate`. Producers stamp
693    /// immediately before pushing and finalize the watermark right
694    /// after (success or `Full`), so `issued != watermark` brackets
695    /// exactly the stamp-to-publish window.
696    #[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    /// Watermark heartbeat for an idle producer: advances the
707    /// watermark to a fresh stamp WITHOUT pushing, so MergeStrict
708    /// consumers stop waiting on this producer's silence. Only call
709    /// from the producer's own thread between pushes (never while a
710    /// stamped item is awaiting publish - the refresh would claim
711    /// "nothing below this stamp is in flight" while one is).
712    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            // Counter stamps: an idle producer cannot mint a fresh
718            // counter value without consuming one; bump the shared
719            // counter so the watermark is a real "nothing below
720            // this" bound.
721            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    /// Read producer `producer_id`'s watermark.
732    pub fn watermark(&self, producer_id: usize) -> u64 {
733        self.line(producer_id).watermark.load(AtomOrd::Acquire)
734    }
735
736    /// Read producer `producer_id`'s last issued stamp (or
737    /// reservation floor while a stamp is being issued).
738    pub fn issued(&self, producer_id: usize) -> u64 {
739        self.line(producer_id).issued.load(AtomOrd::Acquire)
740    }
741
742    /// Terminal producer retirement: publishes `u64::MAX` as the
743    /// slot's issued stamp + watermark, declaring "this producer
744    /// will never stamp again". MergeStrict consumers stop waiting
745    /// on the slot's silence permanently (any candidate passes its
746    /// watermark gate) and the in-flight gate reads it as clean.
747    /// A producer MUST NOT push after retiring its slot - the
748    /// monotonicity floor is saturated.
749    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    /// Seed this region's stamp state from another region so stamps
756    /// stay monotone across a backing swap (capacity morphs allocate
757    /// a fresh region; counter stamps would otherwise restart at 1).
758    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    // ---------------------------------------------------------------
782    // Drainer lease (OwnerLease claim protocol embedded in the
783    // header so all three locales share one mechanism).
784    // ---------------------------------------------------------------
785
786    /// Try to become (or confirm being) the active merge drainer.
787    /// Token layout: `(pid << 32) | consumer_id`, never 0.
788    ///
789    /// Succeeds when (a) unleased, (b) the caller already holds the
790    /// lease (heartbeat refreshed), or (c) the current holder's
791    /// heartbeat is more than `grace_epochs` behind the global
792    /// epoch (dead-drainer takeover).
793    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                // Holder fast path must be WRITE-FREE in steady state:
800                // the heartbeat shares a cache line with the shared
801                // stamp counter producers fetch_add on every push, so
802                // an unconditional store here forces that line
803                // exclusive per pop and collapses producer throughput.
804                // The epoch only advances on sidecar scans; refresh
805                // the heartbeat only when it actually moved.
806                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                // Ownership changed: invalidate every consumer's
828                // cached per-pop lease verification.
829                self.bump_lease_generation();
830                return true;
831            }
832            std::hint::spin_loop();
833        }
834    }
835
836    /// Voluntarily release the drainer lease. Returns `false` when
837    /// the caller did not hold it.
838    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    /// Current drainer token (0 = unleased).
849    pub fn current_drainer(&self) -> u64 {
850        self.header().drainer_token.load(AtomOrd::Acquire)
851    }
852
853    /// Refresh the drainer heartbeat. Returns `false` when the
854    /// caller no longer holds the lease.
855    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    /// Advance the global drainer epoch (caller-driven, typically
866    /// the sidecar's scan tick). A holder whose heartbeat falls more
867    /// than `grace_epochs` behind becomes preemptible. Also bumps the
868    /// lease generation so the holder's next pop re-runs the full
869    /// handshake and refreshes its heartbeat (the liveness proof).
870    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        // Header line + lease-generation line + producer lines.
887        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        // Holder fast path: no bump (the whole point - per-pop
898        // verification stays on the quiet line).
899        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        // The probe itself must execute on every host. On hosts where
911        // it passes, two TSC reads spaced by real work must advance.
912        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        // Drainer ids are (pid << 32) | tid; tid 0 on both here.
1037        let a = 100u64 << 32;
1038        let b = 200u64 << 32;
1039
1040        // Free -> A claims.
1041        assert!(region.try_acquire_drainer(a, 3));
1042        assert_eq!(region.current_drainer(), a);
1043        // A re-acquires (idempotent + heartbeat refresh).
1044        assert!(region.try_acquire_drainer(a, 3));
1045        // B cannot claim while A beats.
1046        assert!(!region.try_acquire_drainer(b, 3));
1047        // A releases; B claims.
1048        assert!(region.release_drainer(a));
1049        assert!(region.try_acquire_drainer(b, 3));
1050        // Stale-heartbeat takeover: tick past grace without B beating.
1051        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        // Beat from the deposed holder fails.
1056        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}