Skip to main content

frozen_core/memmap/
mod.rs

1//! Memory-mapped typed slot storage with per-slot concurrency and epoch durability
2//!
3//! ## Example
4//!
5//! ```
6//! use frozen_core::memmap::{MemMap, MemMapCfg};
7//! use std::time::Duration;
8//!
9//! let dir = tempfile::tempdir().unwrap();
10//! let path = dir.path().join("memmap_demo.bin");
11//!
12//! let cfg = MemMapCfg {
13//!     module_id: 1,
14//!     path,
15//!     version: 1,
16//!     initial_t: 512,
17//!     sync_interval: Some(Duration::from_millis(50)),
18//!     page_size: None,
19//! };
20//!
21//! let memmap = MemMap::<u64>::new(cfg).unwrap();
22//! assert_eq!(memmap.length(), 512 * std::mem::size_of::<u64>());
23//!
24//! let (ticket, _) = memmap.write(0, |v| *v = 0xDEAD_BEEF_CAFE_BABE).unwrap();
25//! ticket.wait().unwrap();
26//! assert!(ticket.is_durable());
27//!
28//! assert_eq!(memmap.read(0, |v| *v).unwrap(), 0xDEAD_BEEF_CAFE_BABE);
29//! assert!(memmap.delete().is_ok());
30//! ```
31
32use crate::{
33    ack::{AckTicket, Completion, SyncTrigger, TEpoch},
34    error::{BindModule, ErrCode, FrozenError, FrozenResult},
35    file::{File, FileCfg},
36    hints,
37};
38use core::marker::PhantomData;
39use std::sync::{Arc, Condvar, Mutex, RwLock, atomic};
40
41mod interface;
42mod page_size;
43
44pub use page_size::PageSize;
45
46#[cfg(any(target_os = "linux", target_os = "macos"))]
47mod posix;
48
49#[cfg(target_os = "windows")]
50mod windows;
51
52pub(in crate::memmap) use interface::MemMapInterface;
53
54#[cfg(any(target_os = "linux", target_os = "macos"))]
55pub(in crate::memmap) type PlatformMemMap = posix::POSIXMemMap;
56
57#[cfg(target_os = "windows")]
58pub(in crate::memmap) type PlatformMemMap = windows::WINMemMap;
59
60/// Error codes for [`MemMap`] module
61pub(in crate::memmap) mod err {
62    use super::{ErrCode, FrozenError, FrozenResult};
63
64    /// Domain Id for [`MemMap`] is **16**
65    const ERRDOMAIN: u8 = 0x10;
66
67    /// internal fuck up (hault and catch fire)
68    pub const HCF: ErrCode = ErrCode::new(0x02, "hault and catch fire");
69
70    /// unknown error (fallback)
71    pub const UNK: ErrCode = ErrCode::new(0x04, "unknown error");
72
73    /// no more memory available
74    pub const NMM: ErrCode = ErrCode::new(0x06, "not enough memory available on the device");
75
76    /// syncing error
77    pub const SYN: ErrCode = ErrCode::new(0x08, "failed to sync/flush data to storage device");
78
79    /// no write/read perm
80    pub const PRM: ErrCode = ErrCode::new(0x0A, "missing permissions for IO");
81
82    /// flush_tx error (unable to spawn)
83    pub const FXE: ErrCode = ErrCode::new(0x10, "unable to spawn flush_tx");
84
85    /// type `T` is zero sized
86    pub const ZRO: ErrCode = ErrCode::new(0x0C, "type T must not be zero sized");
87
88    /// type `T` implements drop
89    pub const DRP: ErrCode = ErrCode::new(0x12, "type T must not implement `Drop`");
90
91    /// type `T` is not 8 bytes aligned
92    pub const ALN: ErrCode = ErrCode::new(0x14, "type T must be 8-bytes aligned");
93
94    /// `size_of::<T>()` is not multiple of 8
95    pub const SZE: ErrCode = ErrCode::new(0x16, "`size_of::<T>()` must be multiple of 8 bytes");
96
97    /// type `T` is invalid
98    /// (e.g. `size_of::<T>()` exceeds page size)
99    pub const INV: ErrCode =
100        ErrCode::new(0x18, "`size_of::<T>()` must not exceed system page size");
101
102    /// parent directory does not exist
103    pub const DIR: ErrCode = ErrCode::new(0x1A, "directory does not exist");
104
105    /// invalid path to file
106    pub const PTH: ErrCode = ErrCode::new(0x1C, "invalid path to file");
107
108    /// file already exists
109    pub const EXS: ErrCode = ErrCode::new(0x1E, "file already exists");
110
111    /// file does not exist
112    pub const NEX: ErrCode = ErrCode::new(0x20, "file does not exist");
113
114    /// unable to grow memory-mapped file
115    pub const GRW: ErrCode = ErrCode::new(0x22, "unable to grow memory-mapped file");
116
117    /// state conflict
118    pub const CAS: ErrCode = ErrCode::new(0x24, "compare and swap check failed");
119
120    #[inline]
121    pub(in crate::memmap) fn raw_error<R, E: std::fmt::Display>(
122        code: ErrCode,
123        error: E,
124    ) -> FrozenResult<R> {
125        let err = FrozenError::unbound_raw(ERRDOMAIN, code, error);
126        Err(err)
127    }
128
129    #[inline]
130    pub(in crate::memmap) fn default_error<R>(code: ErrCode) -> FrozenResult<R> {
131        let err = FrozenError::unbound(ERRDOMAIN, code, "");
132        Err(err)
133    }
134
135    #[inline]
136    pub(in crate::memmap) fn make_raw_error<E: std::fmt::Display>(
137        code: ErrCode,
138        error: E,
139    ) -> FrozenError {
140        FrozenError::unbound_raw(ERRDOMAIN, code, error)
141    }
142}
143
144/// Configurations for [`MemMap`]
145///
146/// ## Example
147///
148/// ```
149/// use frozen_core::memmap::MemMapCfg;
150/// use std::time::Duration;
151///
152/// let cfg = MemMapCfg {
153///     module_id: 1,
154///     path: std::path::PathBuf::from("test.db"),
155///     version: 1,
156///     initial_t: 512,
157///     sync_interval: Some(Duration::from_millis(0x64)),
158///     page_size: None,
159/// };
160///
161/// assert_eq!(cfg.version, 1);
162/// assert_eq!(cfg.initial_t, 512);
163/// assert_eq!(cfg.sync_interval, Some(Duration::from_millis(0x64)));
164/// assert_eq!(cfg.page_size, None);
165/// ```
166#[derive(Debug, Clone)]
167pub struct MemMapCfg {
168    /// Identifier used while error propagation
169    pub module_id: u8,
170
171    /// Base path for/of the file
172    ///
173    /// ## Notes
174    ///
175    /// - The caller must make sure that the path represents a file and all the parent directories
176    ///   included in the path do exist
177    /// - The actual file created on disk is derived by appending the [`version`](Self::version) to
178    ///   the base file name (e.g. `data.bin` with version `1` becomes `data_1.bin`)
179    pub path: std::path::PathBuf,
180
181    /// Schema or layout version of the underlying memory-mapped file
182    ///
183    /// Because the binary layout on disk is constructed according to the initial type `T` and
184    /// structural parameters, [`MemMap`] strictly requires type invariants across a file's lifetime
185    ///
186    /// When an application evolves its data structures or migrates `T`, incrementing `version`
187    /// ensures a new versioned backing file (`<name>_<ver>`) is created on disk without corrupting
188    /// or clashing with previously persisted data
189    pub version: u32,
190
191    /// Number of `T` elements to pre-allocate on fs when [`MemMap`] is initialized
192    ///
193    /// Initial file length will be `core::mem::size_of::<T>() * initial_t` (bytes)
194    pub initial_t: usize,
195
196    /// Optional interval for the background sync thread
197    ///
198    /// If `Some(duration)`, a background worker thread is spawned which flushes dirty mapped pages
199    /// at this interval; and if `None`, background sync is disabled and durability advances via
200    /// manual sync or forced tickets
201    pub sync_interval: Option<std::time::Duration>,
202
203    /// Configured page size unit for file allocation and growth
204    ///
205    /// Must be a power of 2 and at least 4096 bytes ([`PageSize::K4`]).
206    /// If `None`, defaults to the platform/host page size ([`PlatformMemMap::page_size()`]).
207    pub page_size: Option<PageSize>,
208}
209
210impl MemMapCfg {
211    /// Computes the versioned file path by appending `_{version}` to the base file stem
212    ///
213    /// Preserves file extension if present (e.g., `data.bin` with version `1` becomes `data_1.bin`)
214    #[inline]
215    fn versioned_path(&self) -> std::path::PathBuf {
216        let parent = self.path.parent();
217        let stem = self.path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
218        let extension = self.path.extension().and_then(|e| e.to_str());
219
220        let versioned_filename = match extension {
221            Some(ext) if !ext.is_empty() => format!("{}_{}.{}", stem, self.version, ext),
222            _ => format!("{}_{}", stem, self.version),
223        };
224
225        match parent {
226            Some(p) if !p.as_os_str().is_empty() => p.join(versioned_filename),
227            _ => std::path::PathBuf::from(versioned_filename),
228        }
229    }
230}
231
232const WRITER_BIT: u32 = 0x8000_0000;
233const L1_CONTENTION: usize = 0x10;
234const L2_CONTENTION: usize = 0x20;
235
236/// Table of reader-writer spinlocks per slot for memory-mapped items
237#[derive(Debug)]
238struct SlotLocks {
239    locks: Box<[atomic::AtomicU32]>,
240}
241
242impl SlotLocks {
243    fn new(total_slots: usize) -> Self {
244        let mut slots = Vec::with_capacity(total_slots);
245        for _ in 0..total_slots {
246            slots.push(atomic::AtomicU32::new(0));
247        }
248
249        Self { locks: slots.into_boxed_slice() }
250    }
251
252    fn grow(&mut self, additional_slots: usize) {
253        if additional_slots == 0 {
254            return;
255        }
256
257        let total = self.locks.len() + additional_slots;
258        let mut slots = Vec::with_capacity(total);
259        for val in self.locks.iter() {
260            slots.push(atomic::AtomicU32::new(val.load(atomic::Ordering::Relaxed)));
261        }
262        for _ in 0..additional_slots {
263            slots.push(atomic::AtomicU32::new(0));
264        }
265
266        self.locks = slots.into_boxed_slice();
267    }
268
269    #[inline(always)]
270    fn read_lock(&self, index: usize) -> SlotReadGuard<'_> {
271        let val = &self.locks[index];
272        let mut spins = 0;
273
274        loop {
275            let state = val.load(atomic::Ordering::Relaxed);
276            if hints::likely(state & WRITER_BIT == 0)
277                && val
278                    .compare_exchange_weak(
279                        state,
280                        state + 1,
281                        atomic::Ordering::Acquire,
282                        atomic::Ordering::Relaxed,
283                    )
284                    .is_ok()
285            {
286                return SlotReadGuard(val);
287            }
288
289            if hints::likely(spins < L1_CONTENTION) {
290                std::hint::spin_loop();
291            } else if spins < L2_CONTENTION {
292                std::thread::yield_now();
293            } else {
294                let ns = 0x30 << (spins - L2_CONTENTION).min(0x0A);
295                std::thread::sleep(std::time::Duration::from_nanos(ns));
296            }
297
298            spins += 1;
299        }
300    }
301
302    #[inline(always)]
303    fn write_lock(&self, index: usize) -> SlotWriteGuard<'_> {
304        let val = &self.locks[index];
305        let mut spins = 0;
306
307        loop {
308            if val
309                .compare_exchange_weak(
310                    0,
311                    WRITER_BIT,
312                    atomic::Ordering::Acquire,
313                    atomic::Ordering::Relaxed,
314                )
315                .is_ok()
316            {
317                return SlotWriteGuard(val);
318            }
319
320            if hints::likely(spins < L1_CONTENTION) {
321                std::hint::spin_loop();
322            } else if spins < L2_CONTENTION {
323                std::thread::yield_now();
324            } else {
325                let ns = 0x30 << (spins - L2_CONTENTION).min(0x0A);
326                std::thread::sleep(std::time::Duration::from_nanos(ns));
327            }
328
329            spins += 1;
330        }
331    }
332}
333
334/// RAII read guard for a slot lock
335struct SlotReadGuard<'a>(&'a atomic::AtomicU32);
336
337impl Drop for SlotReadGuard<'_> {
338    #[inline(always)]
339    fn drop(&mut self) {
340        self.0.fetch_sub(1, atomic::Ordering::Release);
341    }
342}
343
344/// RAII write guard for a slot lock
345struct SlotWriteGuard<'a>(&'a atomic::AtomicU32);
346
347impl Drop for SlotWriteGuard<'_> {
348    #[inline(always)]
349    fn drop(&mut self) {
350        self.0.store(0, atomic::Ordering::Release);
351    }
352}
353
354/// Internal shared state for [`MemMap`]
355#[derive(Debug)]
356struct MemMapInner {
357    cfg: MemMapCfg,
358    page_size: PageSize,
359    mmap: RwLock<Option<PlatformMemMap>>,
360    slot_locks: RwLock<SlotLocks>,
361    length: atomic::AtomicUsize,
362    completion: Arc<Completion>,
363    sync_condvar: Condvar,
364    sync_mutex: Mutex<bool>,
365    shutdown: atomic::AtomicBool,
366    is_parked: atomic::AtomicBool,
367}
368
369impl SyncTrigger for MemMapInner {
370    fn trigger_sync(&self) -> FrozenResult<()> {
371        if self.cfg.sync_interval.is_some() {
372            {
373                let mut guard = self.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
374                *guard = true;
375            }
376
377            self.sync_condvar.notify_one();
378            Ok(())
379        } else {
380            self.sync_internal()
381        }
382    }
383}
384
385impl MemMapInner {
386    /// Synchronizes dirty pages to storage if current epoch exceeds durable epoch
387    fn sync_mmap_if_needed(&self, mmap: &PlatformMemMap, sync_len: usize) -> FrozenResult<()> {
388        let target_epoch = self.completion.read_current_epoch();
389        let durable_epoch = self.completion.read_durable_epoch();
390
391        if target_epoch <= durable_epoch {
392            return Ok(());
393        }
394
395        let sync_res = mmap.sync(sync_len);
396        match sync_res {
397            Ok(()) => {
398                self.completion.mark_epoch_as_durable(target_epoch);
399                self.completion.del_err();
400                self.completion.notify_all_listeners();
401
402                Ok(())
403            }
404            Err(e) => {
405                let e = e.with_module(self.cfg.module_id);
406                self.completion.set_err(e.clone());
407                self.completion.notify_all_listeners();
408
409                Err(e)
410            }
411        }
412    }
413
414    fn sync_internal(&self) -> FrozenResult<()> {
415        let guard = self.mmap.read().unwrap_or_else(|e| e.into_inner());
416        let mmap = match guard.as_ref() {
417            Some(m) => m,
418            None => return Ok(()),
419        };
420
421        let length = self.length.load(atomic::Ordering::Acquire);
422        self.sync_mmap_if_needed(mmap, length)
423    }
424}
425
426/// Custom memory-mapped typed array storage with slot-level locking and epoch durability
427///
428/// `MemMap<T>` maps an on-disk [`File`] into process virtual address space as a contiguous array of
429/// fixed-size slots of type `T`
430///
431/// It provides,
432///
433/// - **Slot Synchronization**: Per-slot reader-writer spinlocks (`SlotLocks`) protecting each index
434///   against concurrent write-write and read-write hazards with backoff spin/yield/sleep stages
435/// - **Epoch Durability**: Monotonically advancing write epoch counter incremented on each
436///   mutation, tracked via [`AckTicket`] and synchronized in the background by a dedicated worker
437/// - **Process Isolation**: Advisory kernel-level locking (`flock`/`LockFileEx`) ensuring exclusive
438///   file ownership by a single running instance
439///
440/// ## Constraints
441///
442/// - `T` must be sized and cloneable (`T: Sized + Clone`)
443/// - `T` must be 8-byte aligned (`core::mem::align_of::<T>() == 8`)
444/// - `size_of::<T>()` must be non-zero, a multiple of 8, and must not exceed the host page size
445/// - `T` must not implement `Drop` (`!core::mem::needs_drop::<T>()`)
446/// - Type layout is immutable across file lifetime; schema changes require bumping
447///   [`MemMapCfg::version`]
448///
449/// ## Example
450///
451/// ```
452/// use frozen_core::memmap::{MemMap, MemMapCfg};
453/// use std::time::Duration;
454///
455/// let dir = tempfile::tempdir().unwrap();
456/// let cfg = MemMapCfg {
457///     module_id: 1,
458///     path: dir.path().join("storage.bin"),
459///     version: 1,
460///     initial_t: 512,
461///     sync_interval: Some(Duration::from_millis(100)),
462///     page_size: None,
463/// };
464///
465/// let memmap = MemMap::<u64>::new(cfg).unwrap();
466/// assert_eq!(memmap.length(), 512 * std::mem::size_of::<u64>());
467///
468/// let (ticket, _) = memmap.write(0, |v| *v = 12345u64).unwrap();
469/// assert_eq!(memmap.read(0, |v| *v).unwrap(), 12345u64);
470/// assert!(memmap.delete().is_ok());
471/// ```
472pub struct MemMap<T: Sized + Clone> {
473    cfg: MemMapCfg,
474    file: Option<File>,
475    inner: Arc<MemMapInner>,
476    bg_thread: Option<std::thread::JoinHandle<()>>,
477    _marker: PhantomData<T>,
478}
479
480impl<T> core::fmt::Debug for MemMap<T>
481where
482    T: Sized + Clone,
483{
484    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
485        f.debug_struct("MemMap").field("cfg", &self.cfg).field("inner", &self.inner).finish()
486    }
487}
488
489unsafe impl<T> Send for MemMap<T> where T: Sized + Clone {}
490unsafe impl<T> Sync for MemMap<T> where T: Sized + Clone {}
491
492impl<T> MemMap<T>
493where
494    T: Sized + Clone,
495{
496    fn validate_t(cfg: &MemMapCfg) -> FrozenResult<PageSize> {
497        let mid = cfg.module_id;
498        if core::mem::needs_drop::<T>() {
499            return err::default_error(err::DRP).with_module(mid);
500        }
501
502        let size = core::mem::size_of::<T>();
503        if size == 0 {
504            return err::default_error(err::ZRO).with_module(mid);
505        }
506
507        if size % 8 != 0 {
508            return err::default_error(err::SZE).with_module(mid);
509        }
510
511        let align = core::mem::align_of::<T>();
512        if align != 8 {
513            return err::default_error(err::ALN).with_module(mid);
514        }
515
516        let page_size = match cfg.page_size {
517            Some(ps) => ps.validate_platform().with_module(mid)?,
518            None => PlatformMemMap::page_size().with_module(mid)?,
519        };
520
521        if size > page_size.as_usize() {
522            return err::default_error(err::INV).with_module(mid);
523        }
524
525        if page_size.as_usize() % size != 0 {
526            return err::raw_error(
527                err::INV,
528                format!("page size {page_size} must be divisible by size_of::<T>() ({size})"),
529            )
530            .with_module(mid);
531        }
532
533        if cfg.initial_t == 0 {
534            return err::default_error(err::INV).with_module(mid);
535        }
536
537        let initial_bytes = match cfg.initial_t.checked_mul(size) {
538            Some(b) => b,
539            None => {
540                return err::raw_error(err::INV, "initial_t calculation overflowed usize")
541                    .with_module(mid);
542            }
543        };
544
545        #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
546        {
547            if initial_bytes < 4096 || initial_bytes % 4096 != 0 {
548                return err::raw_error(
549                    err::INV,
550                    format!(
551                        "initial allocation (initial_t * size_of::<T>() = {initial_bytes} bytes) must be at least 4096 and a multiple of 4096"
552                    ),
553                )
554                .with_module(mid);
555            }
556        }
557
558        #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
559        {
560            if initial_bytes % page_size.as_usize() != 0 {
561                return err::raw_error(
562                    err::INV,
563                    format!(
564                        "initial allocation (initial_t * size_of::<T>() = {initial_bytes} bytes) must be an exact multiple of page size ({page_size})"
565                    ),
566                )
567                .with_module(mid);
568            }
569        }
570
571        Ok(page_size)
572    }
573
574    fn validate_path(cfg: &MemMapCfg) -> FrozenResult<()> {
575        let mid = cfg.module_id;
576
577        if cfg.path.as_os_str().is_empty() {
578            return err::default_error(err::PTH).with_module(mid);
579        }
580
581        let stem = cfg.path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
582        if stem.is_empty() {
583            return err::default_error(err::PTH).with_module(mid);
584        }
585
586        if cfg.path.exists() && cfg.path.is_dir() {
587            return err::default_error(err::PTH).with_module(mid);
588        }
589
590        if let Some(parent) = cfg.path.parent() {
591            if !parent.as_os_str().is_empty() && (!parent.exists() || !parent.is_dir()) {
592                return err::default_error(err::DIR).with_module(mid);
593            }
594        }
595
596        Ok(())
597    }
598
599    fn file_cfg(cfg: &MemMapCfg) -> FileCfg {
600        FileCfg {
601            module_id: cfg.module_id,
602            path: cfg.versioned_path(),
603            buffer_size: core::mem::size_of::<T>(),
604            initial_available_buffers: cfg.initial_t,
605            sync_interval: None,
606        }
607    }
608
609    fn from_components(
610        cfg: MemMapCfg,
611        mmap: PlatformMemMap,
612        file: File,
613        page_size: PageSize,
614    ) -> FrozenResult<Self> {
615        let length = mmap.length();
616        let completion = Arc::new(Completion::default());
617        let sync_interval = cfg.sync_interval;
618        let total_slots = length / core::mem::size_of::<T>();
619        let slot_locks = SlotLocks::new(total_slots);
620
621        let inner = Arc::new(MemMapInner {
622            cfg: cfg.clone(),
623            page_size,
624            mmap: RwLock::new(Some(mmap)),
625            slot_locks: RwLock::new(slot_locks),
626            length: atomic::AtomicUsize::new(length),
627            completion: completion.clone(),
628            sync_condvar: Condvar::new(),
629            sync_mutex: Mutex::new(false),
630            shutdown: atomic::AtomicBool::new(false),
631            is_parked: atomic::AtomicBool::new(false),
632        });
633
634        let weak_inner: std::sync::Weak<dyn SyncTrigger> =
635            Arc::downgrade(&inner) as std::sync::Weak<dyn SyncTrigger>;
636        completion.set_sync_trigger(weak_inner);
637
638        let bg_thread = if let Some(interval) = sync_interval {
639            let worker_inner = Arc::clone(&inner);
640            let handle = std::thread::Builder::new().name("frozen-mmap-sync-worker".into()).spawn(
641                move || {
642                    let mut guard =
643                        worker_inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
644
645                    while !worker_inner.shutdown.load(atomic::Ordering::Acquire) {
646                        if *guard {
647                            *guard = false;
648                            drop(guard);
649
650                            let _ = worker_inner.sync_internal();
651                            guard =
652                                worker_inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
653                            continue;
654                        }
655
656                        let target_epoch = worker_inner.completion.read_current_epoch();
657                        let durable_epoch = worker_inner.completion.read_durable_epoch();
658
659                        if target_epoch == durable_epoch {
660                            worker_inner.is_parked.store(true, atomic::Ordering::Release);
661
662                            if worker_inner.completion.read_current_epoch() != durable_epoch {
663                                worker_inner.is_parked.store(false, atomic::Ordering::Release);
664                                continue;
665                            }
666
667                            let new_guard = worker_inner
668                                .sync_condvar
669                                .wait(guard)
670                                .unwrap_or_else(|e| e.into_inner());
671                            guard = new_guard;
672                            worker_inner.is_parked.store(false, atomic::Ordering::Release);
673
674                            if worker_inner.shutdown.load(atomic::Ordering::Acquire) || *guard {
675                                continue;
676                            }
677
678                            if worker_inner.completion.read_current_epoch()
679                                == worker_inner.completion.read_durable_epoch()
680                            {
681                                continue;
682                            }
683
684                            let (new_guard, _) = worker_inner
685                                .sync_condvar
686                                .wait_timeout(guard, interval)
687                                .unwrap_or_else(|e| e.into_inner());
688                            guard = new_guard;
689                        } else {
690                            worker_inner.is_parked.store(false, atomic::Ordering::Release);
691
692                            let (new_guard, _) = worker_inner
693                                .sync_condvar
694                                .wait_timeout(guard, interval)
695                                .unwrap_or_else(|e| e.into_inner());
696                            guard = new_guard;
697                        }
698
699                        if worker_inner.shutdown.load(atomic::Ordering::Acquire) {
700                            break;
701                        }
702
703                        *guard = false;
704                        drop(guard);
705
706                        let _ = worker_inner.sync_internal();
707                        guard = worker_inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
708                    }
709                },
710            );
711
712            let handle = match handle {
713                Ok(h) => h,
714                Err(spawn_err) => {
715                    let mut mmap_guard = inner.mmap.write().unwrap_or_else(|e| e.into_inner());
716                    let mut err = err::make_raw_error(err::FXE, spawn_err);
717                    if let Some(m) = mmap_guard.take() {
718                        if let Err(unmap_err) = m.unmap() {
719                            err.add_suppressed(unmap_err);
720                        }
721                    }
722                    return Err(err.with_module(cfg.module_id));
723                }
724            };
725
726            Some(handle)
727        } else {
728            None
729        };
730
731        Ok(Self { cfg, file: Some(file), inner, bg_thread, _marker: PhantomData })
732    }
733
734    /// Creates and memory-maps a new [`File`] at `cfg.versioned_path()`
735    ///
736    /// Allocates an underlying file with capacity for `initial_t * size_of::<T>()` bytes, acquires
737    /// an exclusive advisory lock on the descriptor, establishes the memory mapping, and spawns the
738    /// background worker for periodic durability flushing
739    ///
740    /// ## Constraints
741    ///
742    /// - Returns [`err::EXS`] if the versioned file already exists on the filesystem; Use
743    ///   [`MemMap::open`] or [`MemMap::open_or_create`] instead
744    /// - Returns [`err::PTH`] or [`err::DIR`] if the base path or its parent directory is invalid
745    /// - Enforces all structural type invariants for `T` (`align == 8`, `size % 8 == 0`, `size <=
746    ///   page_size`, no `Drop`)
747    /// - An exclusive advisory lock is acquired on creation; concurrent callers on the same file
748    ///   path fail
749    ///
750    /// ## Example
751    ///
752    /// ```
753    /// use frozen_core::memmap::{MemMap, MemMapCfg};
754    /// use std::time::Duration;
755    ///
756    /// let dir = tempfile::tempdir().unwrap();
757    /// let cfg = MemMapCfg {
758    ///     module_id: 1,
759    ///     path: dir.path().join("new_memmap.bin"),
760    ///     version: 1,
761    ///     initial_t: 512,
762    ///     sync_interval: Some(Duration::from_millis(100)),
763    ///     page_size: None,
764    /// };
765    ///
766    /// let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
767    /// assert_eq!(mmap.length(), std::mem::size_of::<u64>() * 512);
768    ///
769    /// // Creating another file at the same path fails
770    /// assert!(MemMap::<u64>::new(cfg).is_err());
771    /// ```
772    pub fn new(cfg: MemMapCfg) -> FrozenResult<Self> {
773        let page_size = Self::validate_t(&cfg)?;
774        Self::validate_path(&cfg)?;
775
776        let vpath = cfg.versioned_path();
777        if vpath.exists() {
778            return err::default_error(err::EXS).with_module(cfg.module_id);
779        }
780
781        let file_cfg = Self::file_cfg(&cfg);
782        let file = match File::new(file_cfg) {
783            Ok(f) => f,
784            Err(e) if e.reason == err::EXS.reason => {
785                return err::default_error(err::EXS).with_module(cfg.module_id);
786            }
787            Err(e) => return Err(e),
788        };
789
790        let mmap = match PlatformMemMap::new(file.fd(), file.length()) {
791            Ok(m) => m,
792            Err(mut e) => {
793                if let Err(del_err) = file.delete() {
794                    e.add_suppressed(del_err);
795                }
796
797                return Err(e.with_module(cfg.module_id));
798            }
799        };
800
801        Self::from_components(cfg, mmap, file, page_size)
802    }
803
804    /// Opens an existing memory-mapped [`File`] at `cfg.versioned_path()`
805    ///
806    /// Opens the underlying versioned file, verifies its length matches the requested layout,
807    /// acquires an exclusive advisory lock on the descriptor, maps its memory region, and spawns
808    /// the background flush worker
809    ///
810    /// ## Constraints
811    ///
812    /// - Returns [`err::NEX`] if the backing file does not exist on disk
813    /// - Returns [`err::PTH`] if the path resolves to a directory
814    /// - Fails with advisory lock error if another handle currently owns the exclusive lock
815    /// - Type `T` invariants must strictly match the binary layout originally written to the file
816    ///
817    /// ## Example
818    ///
819    /// ```
820    /// use frozen_core::memmap::{MemMap, MemMapCfg};
821    /// use std::time::Duration;
822    ///
823    /// let dir = tempfile::tempdir().unwrap();
824    /// let cfg = MemMapCfg {
825    ///     module_id: 1,
826    ///     path: dir.path().join("open_memmap.bin"),
827    ///     version: 1,
828    ///     initial_t: 512,
829    ///     sync_interval: Some(Duration::from_millis(100)),
830    ///     page_size: None,
831    /// };
832    ///
833    /// {
834    ///     let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
835    ///     assert_eq!(mmap.length(), std::mem::size_of::<u64>() * 512);
836    /// }
837    ///
838    /// let opened = MemMap::<u64>::open(cfg).unwrap();
839    /// assert_eq!(opened.length(), std::mem::size_of::<u64>() * 512);
840    /// ```
841    pub fn open(cfg: MemMapCfg) -> FrozenResult<Self> {
842        let page_size = Self::validate_t(&cfg)?;
843        Self::validate_path(&cfg)?;
844
845        let vpath = cfg.versioned_path();
846        if !vpath.exists() {
847            return err::default_error(err::NEX).with_module(cfg.module_id);
848        }
849        if vpath.is_dir() {
850            return err::default_error(err::PTH).with_module(cfg.module_id);
851        }
852
853        let file_cfg = Self::file_cfg(&cfg);
854        let file = File::open(file_cfg)?;
855
856        let mmap = PlatformMemMap::new(file.fd(), file.length()).with_module(cfg.module_id)?;
857
858        Self::from_components(cfg, mmap, file, page_size)
859    }
860
861    /// Opens an existing memory-mapped [`File`] or creates a new one if it does not yet exist
862    ///
863    /// Idempotent entrypoint that transparently delegates to either open or creation semantics
864    /// based on filesystem presence, maintaining consistent advisory locking and background
865    /// flushing
866    ///
867    /// ## Constraints
868    ///
869    /// - Returns [`err::PTH`] if the resolved path points to an existing directory
870    /// - Returns [`err::DIR`] if parent directory does not exist
871    /// - Fails if another handle currently owns the advisory lock on the target file
872    /// - All structural `T` invariants (`align`, `size`, `needs_drop`) are strictly validated
873    ///
874    /// ## Example
875    ///
876    /// ```
877    /// use frozen_core::memmap::{MemMap, MemMapCfg};
878    /// use std::time::Duration;
879    ///
880    /// let dir = tempfile::tempdir().unwrap();
881    /// let cfg = MemMapCfg {
882    ///     module_id: 1,
883    ///     path: dir.path().join("open_or_create_memmap.bin"),
884    ///     version: 1,
885    ///     initial_t: 512,
886    ///     sync_interval: Some(Duration::from_millis(100)),
887    ///     page_size: None,
888    /// };
889    ///
890    /// {
891    ///     let mmap = MemMap::<u64>::open_or_create(cfg.clone()).unwrap();
892    ///     assert_eq!(mmap.length(), std::mem::size_of::<u64>() * 512);
893    /// }
894    ///
895    /// let reopened = MemMap::<u64>::open_or_create(cfg).unwrap();
896    /// assert_eq!(reopened.length(), std::mem::size_of::<u64>() * 512);
897    /// ```
898    pub fn open_or_create(cfg: MemMapCfg) -> FrozenResult<Self> {
899        let page_size = Self::validate_t(&cfg)?;
900        Self::validate_path(&cfg)?;
901
902        let vpath = cfg.versioned_path();
903        if vpath.exists() && vpath.is_dir() {
904            return err::default_error(err::PTH).with_module(cfg.module_id);
905        }
906
907        let file_cfg = Self::file_cfg(&cfg);
908        let file = File::open_or_create(file_cfg)?;
909
910        let mmap = PlatformMemMap::new(file.fd(), file.length()).with_module(cfg.module_id)?;
911
912        Self::from_components(cfg, mmap, file, page_size)
913    }
914
915    /// Returns the system memory page size queried from kernel settings
916    ///
917    /// ## Example
918    ///
919    /// ```
920    /// use frozen_core::memmap::{MemMap, PageSize};
921    ///
922    /// let page_size = MemMap::<u64>::page_size().unwrap();
923    /// assert!(page_size.as_usize() >= 4096);
924    /// assert!(page_size.is_power_of_two());
925    /// ```
926    #[inline]
927    pub fn page_size() -> FrozenResult<PageSize> {
928        PlatformMemMap::page_size()
929    }
930
931    /// Returns the length of the memory mapping in bytes
932    ///
933    /// Corresponds to `initial_t * size_of::<T>()`
934    ///
935    /// ## Example
936    ///
937    /// ```
938    /// use frozen_core::memmap::{MemMap, MemMapCfg};
939    /// use std::time::Duration;
940    ///
941    /// let dir = tempfile::tempdir().unwrap();
942    /// let cfg = MemMapCfg {
943    ///     module_id: 1,
944    ///     path: dir.path().join("length_demo.bin"),
945    ///     version: 1,
946    ///     initial_t: 512,
947    ///     sync_interval: Some(Duration::from_millis(100)),
948    ///     page_size: None,
949    /// };
950    ///
951    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
952    /// assert_eq!(mmap.length(), 512 * std::mem::size_of::<u64>());
953    /// ```
954    #[inline]
955    pub fn length(&self) -> usize {
956        self.inner.length.load(atomic::Ordering::Acquire)
957    }
958
959    /// Dynamically grows the memory-mapped file by `pages` additional virtual memory pages
960    ///
961    /// ## Semantics
962    ///
963    /// - Halts all concurrent read and write operations across all slots for the duration of the
964    ///   growth
965    /// - Halts background durability flushing and manual sync operations
966    /// - If uncommitted dirty writes exist (`current_epoch > durable_epoch`), performs a durability
967    ///   sync before modifying file boundaries
968    /// - Explicitly unmaps the current virtual memory mapping
969    /// - Zero-extends the backing file on disk safely via [`File::grow`] and synchronizes file
970    ///   metadata in units of the configured page size
971    /// - Establishes a new memory mapping over the enlarged file range
972    /// - Dynamically expands per-slot spinlocks to accommodate new slots
973    /// - Updates tracked mapping length and advances durability epoch
974    ///
975    /// ## Crash Durability & Safety
976    ///
977    /// - Existing payload data is flushed to persistent storage before file extension, ensuring no
978    ///   data loss
979    /// - Disk block pre-allocation (`fallocate` / `f_preallocate`) precedes logical size expansion;
980    ///   out-of-space failures leave existing data untouched
981    /// - If file growth fails, the original memory mapping is automatically restored
982    ///
983    /// ## Example
984    ///
985    /// ```
986    /// use frozen_core::memmap::{MemMap, MemMapCfg};
987    /// use std::time::Duration;
988    ///
989    /// let dir = tempfile::tempdir().unwrap();
990    /// let cfg = MemMapCfg {
991    ///     module_id: 1,
992    ///     path: dir.path().join("grow_demo.bin"),
993    ///     version: 1,
994    ///     initial_t: 512,
995    ///     sync_interval: None,
996    ///     page_size: None,
997    /// };
998    ///
999    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1000    /// assert_eq!(mmap.length(), 512 * std::mem::size_of::<u64>());
1001    /// mmap.write(0, |v| *v = 111u64).unwrap();
1002    ///
1003    /// mmap.grow(1).unwrap();
1004    /// assert_eq!(mmap.length(), (512 * std::mem::size_of::<u64>()) + MemMap::<u64>::page_size().unwrap().as_usize());
1005    /// assert_eq!(mmap.read(0, |v| *v).unwrap(), 111u64);
1006    /// assert_eq!(mmap.read(512, |v| *v).unwrap(), 0u64);
1007    /// ```
1008    pub fn grow(&self, pages: usize) -> FrozenResult<()> {
1009        let mid = self.cfg.module_id;
1010        if pages == 0 {
1011            return Ok(());
1012        }
1013
1014        let page_size = self.inner.page_size.as_usize();
1015        let bytes_to_add = match pages.checked_mul(page_size) {
1016            Some(bytes) => bytes,
1017            None => {
1018                return err::raw_error(err::GRW, "file growth calculation overflowed usize")
1019                    .with_module(mid);
1020            }
1021        };
1022
1023        let size = core::mem::size_of::<T>();
1024        let additional_slots = bytes_to_add / size;
1025
1026        let file = match self.file.as_ref() {
1027            Some(f) => f,
1028            None => return err::default_error(err::INV).with_module(mid),
1029        };
1030
1031        // Halt all read and write ops by taking exclusive write lock on slot_locks
1032        let mut slot_locks_guard = self.inner.slot_locks.write().unwrap_or_else(|e| e.into_inner());
1033
1034        // Halt all sync ops (including background sync worker) by taking exclusive write lock on mmap
1035        let mut mmap_guard = self.inner.mmap.write().unwrap_or_else(|e| e.into_inner());
1036
1037        let mmap = match mmap_guard.as_ref() {
1038            Some(m) => m,
1039            None => return err::default_error(err::INV).with_module(mid),
1040        };
1041
1042        let curr_len = self.inner.length.load(atomic::Ordering::Acquire);
1043        let new_length = match curr_len.checked_add(bytes_to_add) {
1044            Some(len) => len,
1045            None => {
1046                return err::raw_error(err::GRW, "file growth calculation overflowed usize")
1047                    .with_module(mid);
1048            }
1049        };
1050
1051        // Pre-growth sync: only if needed (curr_epoch > durable_epoch)
1052        self.inner.sync_mmap_if_needed(mmap, curr_len)?;
1053
1054        let old_mmap = match mmap_guard.take() {
1055            Some(m) => m,
1056            None => return err::default_error(err::INV).with_module(mid),
1057        };
1058
1059        if let Err(e) = old_mmap.unmap().with_module(mid) {
1060            if let Ok(restored) = PlatformMemMap::new(file.fd(), curr_len).with_module(mid) {
1061                *mmap_guard = Some(restored);
1062            }
1063            return Err(e);
1064        }
1065
1066        if let Err(e) = file.grow(additional_slots).with_module(mid) {
1067            if let Ok(restored) = PlatformMemMap::new(file.fd(), curr_len).with_module(mid) {
1068                *mmap_guard = Some(restored);
1069            }
1070            return Err(e);
1071        }
1072
1073        // Flush new file metadata to disk for crash safety
1074        if let Err(e) = file.sync().with_module(mid) {
1075            if let Ok(restored) = PlatformMemMap::new(file.fd(), new_length).with_module(mid) {
1076                *mmap_guard = Some(restored);
1077                self.inner.length.store(new_length, atomic::Ordering::Release);
1078                slot_locks_guard.grow(additional_slots);
1079            }
1080            return Err(e);
1081        }
1082
1083        let new_mmap = match PlatformMemMap::new(file.fd(), new_length).with_module(mid) {
1084            Ok(m) => m,
1085            Err(e) => {
1086                if let Ok(restored) = PlatformMemMap::new(file.fd(), curr_len).with_module(mid) {
1087                    *mmap_guard = Some(restored);
1088                }
1089                return Err(e);
1090            }
1091        };
1092
1093        *mmap_guard = Some(new_mmap);
1094        slot_locks_guard.grow(additional_slots);
1095        self.inner.length.store(new_length, atomic::Ordering::Release);
1096
1097        self.inner.completion.increment_current_epoch();
1098
1099        Ok(())
1100    }
1101
1102    /// Returns a reference to the active [`MemMapCfg`] configuration
1103    ///
1104    /// ## Example
1105    ///
1106    /// ```
1107    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1108    /// use std::time::Duration;
1109    ///
1110    /// let dir = tempfile::tempdir().unwrap();
1111    /// let cfg = MemMapCfg {
1112    ///     module_id: 1,
1113    ///     path: dir.path().join("cfg_demo.bin"),
1114    ///     version: 2,
1115    ///     initial_t: 512,
1116    ///     sync_interval: Some(Duration::from_millis(100)),
1117    ///     page_size: None,
1118    /// };
1119    ///
1120    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1121    /// assert_eq!(mmap.cfg().version, 2);
1122    /// assert_eq!(mmap.cfg().initial_t, 512);
1123    /// ```
1124    #[inline]
1125    pub fn cfg(&self) -> &MemMapCfg {
1126        &self.cfg
1127    }
1128
1129    /// Fetch the latest assigned durability epoch
1130    ///
1131    /// The current epoch monotonically increments on each [`MemMap::write`] call
1132    ///
1133    /// ## Example
1134    ///
1135    /// ```
1136    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1137    /// use std::time::Duration;
1138    ///
1139    /// let dir = tempfile::tempdir().unwrap();
1140    /// let cfg = MemMapCfg {
1141    ///     module_id: 1,
1142    ///     path: dir.path().join("epoch_demo.bin"),
1143    ///     version: 1,
1144    ///     initial_t: 512,
1145    ///     sync_interval: Some(Duration::from_millis(100)),
1146    ///     page_size: None,
1147    /// };
1148    ///
1149    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1150    /// assert_eq!(mmap.current_epoch(), 0);
1151    /// mmap.write(0, |v| *v = 999u64).unwrap();
1152    /// assert_eq!(mmap.current_epoch(), 1);
1153    /// ```
1154    #[inline]
1155    pub fn current_epoch(&self) -> TEpoch {
1156        self.inner.completion.read_current_epoch()
1157    }
1158
1159    /// Fetch the latest confirmed durable epoch flushed to persistent storage
1160    ///
1161    /// ## Example
1162    ///
1163    /// ```
1164    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1165    /// use std::time::Duration;
1166    ///
1167    /// let dir = tempfile::tempdir().unwrap();
1168    /// let cfg = MemMapCfg {
1169    ///     module_id: 1,
1170    ///     path: dir.path().join("durable_demo.bin"),
1171    ///     version: 1,
1172    ///     initial_t: 512,
1173    ///     sync_interval: Some(Duration::from_millis(100)),
1174    ///     page_size: None,
1175    /// };
1176    ///
1177    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1178    /// let (ticket, _) = mmap.write(0, |v| *v = 100u64).unwrap();
1179    /// mmap.sync().unwrap();
1180    /// assert_eq!(mmap.durable_epoch(), ticket.epoch());
1181    /// ```
1182    #[inline]
1183    pub fn durable_epoch(&self) -> TEpoch {
1184        self.inner.completion.read_durable_epoch()
1185    }
1186
1187    /// Synchronize dirty pages and file metadata to stable storage
1188    ///
1189    /// Performs a synchronous two-phase flush,
1190    ///
1191    /// - Flushes modified virtual memory pages to host cache via [`PlatformMemMap::sync`]
1192    /// - Advances the durable epoch to the current write epoch and awakens all pending listeners
1193    ///
1194    /// Returns `Ok(())` immediately if no writes have occurred since the last sync
1195    ///
1196    /// ## Example
1197    ///
1198    /// ```
1199    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1200    ///
1201    /// let dir = tempfile::tempdir().unwrap();
1202    /// let cfg = MemMapCfg {
1203    ///     module_id: 1,
1204    ///     path: dir.path().join("sync_demo.bin"),
1205    ///     version: 1,
1206    ///     initial_t: 512,
1207    ///     sync_interval: None,
1208    ///     page_size: None,
1209    /// };
1210    ///
1211    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1212    /// let (ticket, _) = mmap.write(1, |v| *v = 0xCAFE_BABE).unwrap();
1213    /// assert!(!ticket.is_durable());
1214    ///
1215    /// mmap.sync().unwrap();
1216    /// assert!(ticket.is_durable());
1217    /// ```
1218    #[inline]
1219    pub fn sync(&self) -> FrozenResult<()> {
1220        self.inner.sync_internal()
1221    }
1222
1223    /// Locks slot `index` for reading and invokes `f` with a reference to `T`
1224    ///
1225    /// ## Constraints
1226    ///
1227    /// - `index * size_of::<T>()` must not overflow `usize`, otherwise returns [`err::INV`]
1228    /// - Reading past the mapped memory region returns [`err::HCF`]
1229    ///
1230    /// ## Zero-Copy & Safety
1231    ///
1232    /// Callback `f` is invoked with `&T` directly referencing mapped memory under lock
1233    ///
1234    /// The higher-ranked lifetime bound (`F: FnOnce(&T) -> R`) ensures references cannot
1235    /// escape or outlive the locked callback scope
1236    ///
1237    /// ## Example
1238    ///
1239    /// ```
1240    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1241    /// use std::time::Duration;
1242    ///
1243    /// let dir = tempfile::tempdir().unwrap();
1244    /// let cfg = MemMapCfg {
1245    ///     module_id: 1,
1246    ///     path: dir.path().join("read_test.bin"),
1247    ///     version: 1,
1248    ///     initial_t: 512,
1249    ///     sync_interval: Some(Duration::from_millis(100)),
1250    ///     page_size: None,
1251    /// };
1252    ///
1253    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1254    /// mmap.write(0, |v| *v = 42u64).unwrap();
1255    /// assert_eq!(mmap.read(0, |v| *v).unwrap(), 42u64);
1256    /// ```
1257    #[inline(always)]
1258    pub fn read<R, F>(&self, index: usize, f: F) -> FrozenResult<R>
1259    where
1260        F: FnOnce(&T) -> R,
1261    {
1262        let mid = self.cfg.module_id;
1263        let size = core::mem::size_of::<T>();
1264
1265        let offset = match index.checked_mul(size) {
1266            Some(off) => off,
1267            None => return err::default_error(err::INV).with_module(mid),
1268        };
1269
1270        let slot_locks_guard = self.inner.slot_locks.read().unwrap_or_else(|e| e.into_inner());
1271
1272        if hints::unlikely(offset.checked_add(size).is_none_or(|end| end > self.length())) {
1273            return err::default_error(err::HCF).with_module(mid);
1274        }
1275
1276        let _slot_guard = slot_locks_guard.read_lock(index);
1277
1278        let guard = self.inner.mmap.read().unwrap_or_else(|e| e.into_inner());
1279        let mmap = match guard.as_ref() {
1280            Some(m) => m,
1281            None => return err::default_error(err::INV).with_module(mid),
1282        };
1283
1284        let ptr = unsafe { mmap.as_ptr::<T>(offset) };
1285        let target_ref = unsafe { &*ptr };
1286        let res = f(target_ref);
1287
1288        drop(guard);
1289        drop(_slot_guard);
1290        drop(slot_locks_guard);
1291
1292        Ok(res)
1293    }
1294
1295    /// Atomically locks `N` consecutive slots and invokes `f` with a reference to `Target`
1296    ///
1297    /// Requires `size_of::<Target>() == N * size_of::<T>()` and compatible alignment
1298    ///
1299    /// ## Constraints
1300    ///
1301    /// - `size_of::<Target>()` must not be zero ([`err::ZRO`])
1302    /// - `size_of::<Target>()` must be an exact multiple of `size_of::<T>()` ([`err::SZE`])
1303    /// - `align_of::<Target>()` must not exceed `align_of::<T>()` ([`err::ALN`])
1304    /// - Slot index and byte offset calculations must not overflow `usize` ([`err::INV`])
1305    /// - Target slot range `start..start + N` must not exceed mapping bounds ([`err::HCF`])
1306    ///
1307    /// ## Deadlock Freedom
1308    ///
1309    /// Read spinlocks for slots `start..start + N` are acquired in strictly ascending order
1310    ///
1311    /// ## Example
1312    ///
1313    /// ```
1314    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1315    /// use std::time::Duration;
1316    ///
1317    /// #[derive(Clone, Copy)]
1318    /// #[repr(C)]
1319    /// struct Page {
1320    ///     header: u64,
1321    ///     payload: [u64; 3],
1322    /// }
1323    ///
1324    /// let dir = tempfile::tempdir().unwrap();
1325    /// let cfg = MemMapCfg {
1326    ///     module_id: 1,
1327    ///     path: dir.path().join("read_exact_demo.bin"),
1328    ///     version: 1,
1329    ///     initial_t: 512,
1330    ///     sync_interval: Some(Duration::from_millis(100)),
1331    ///     page_size: None,
1332    /// };
1333    ///
1334    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1335    /// mmap.write(0, |v| *v = 0xAA).unwrap();
1336    /// mmap.write(1, |v| *v = 0xBB).unwrap();
1337    /// mmap.write(2, |v| *v = 0xCC).unwrap();
1338    /// mmap.write(3, |v| *v = 0xDD).unwrap();
1339    ///
1340    /// let header = mmap.read_exact(0, |page: &Page| page.header).unwrap();
1341    /// assert_eq!(header, 0xAA);
1342    /// ```
1343    #[inline(always)]
1344    pub fn read_exact<Target, R, F>(&self, start: usize, f: F) -> FrozenResult<R>
1345    where
1346        F: FnOnce(&Target) -> R,
1347    {
1348        let mid = self.cfg.module_id;
1349        let slot_size = core::mem::size_of::<T>();
1350        let target_size = core::mem::size_of::<Target>();
1351
1352        if hints::unlikely(target_size == 0) {
1353            return err::default_error(err::ZRO).with_module(mid);
1354        }
1355
1356        if hints::unlikely(target_size % slot_size != 0) {
1357            return err::default_error(err::SZE).with_module(mid);
1358        }
1359
1360        if hints::unlikely(core::mem::align_of::<Target>() > core::mem::align_of::<T>()) {
1361            return err::default_error(err::ALN).with_module(mid);
1362        }
1363
1364        let n = target_size / slot_size;
1365
1366        let end_slot = match start.checked_add(n) {
1367            Some(end) => end,
1368            None => return err::default_error(err::INV).with_module(mid),
1369        };
1370
1371        let offset = match start.checked_mul(slot_size) {
1372            Some(off) => off,
1373            None => return err::default_error(err::INV).with_module(mid),
1374        };
1375
1376        let end_offset = match end_slot.checked_mul(slot_size) {
1377            Some(end) => end,
1378            None => return err::default_error(err::INV).with_module(mid),
1379        };
1380
1381        let slot_locks_guard = self.inner.slot_locks.read().unwrap_or_else(|e| e.into_inner());
1382
1383        if hints::unlikely(end_offset > self.length()) {
1384            return err::default_error(err::HCF).with_module(mid);
1385        }
1386
1387        let mut slot_guards = Vec::with_capacity(n);
1388        for slot in start..end_slot {
1389            slot_guards.push(slot_locks_guard.read_lock(slot));
1390        }
1391
1392        let guard = self.inner.mmap.read().unwrap_or_else(|e| e.into_inner());
1393        let mmap = match guard.as_ref() {
1394            Some(m) => m,
1395            None => return err::default_error(err::INV).with_module(mid),
1396        };
1397
1398        let ptr = unsafe { mmap.as_ptr::<Target>(offset) };
1399        if hints::unlikely((ptr as usize) % core::mem::align_of::<Target>() != 0) {
1400            return err::default_error(err::ALN).with_module(mid);
1401        }
1402
1403        let target_ref = unsafe { &*ptr };
1404        let res = f(target_ref);
1405
1406        drop(guard);
1407        drop(slot_guards);
1408        drop(slot_locks_guard);
1409
1410        Ok(res)
1411    }
1412
1413    /// Locks slot `index` for writing and invokes `f` with a mutable reference to `T`
1414    ///
1415    /// Returns a tuple `(AckTicket, R)` containing the durability acknowledgement ticket
1416    /// and the result returned by callback `f`
1417    ///
1418    /// ## Constraints
1419    ///
1420    /// - `index * size_of::<T>()` must not overflow `usize`, otherwise returns [`err::INV`]
1421    /// - Writing past the mapped memory region returns [`err::HCF`]
1422    ///
1423    /// ## Deadlock & Safety
1424    ///
1425    /// - Exclusive writer spinlock for slot `index` is held during closure execution
1426    /// - If the closure unwinds / panics, the spinlock is cleanly released via RAII
1427    ///   and the durability epoch counter is not incremented
1428    ///
1429    /// ## Example
1430    ///
1431    /// ```
1432    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1433    /// use std::time::Duration;
1434    ///
1435    /// let dir = tempfile::tempdir().unwrap();
1436    /// let cfg = MemMapCfg {
1437    ///     module_id: 1,
1438    ///     path: dir.path().join("write_test.bin"),
1439    ///     version: 1,
1440    ///     initial_t: 512,
1441    ///     sync_interval: Some(Duration::from_millis(100)),
1442    ///     page_size: None,
1443    /// };
1444    ///
1445    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1446    /// let (ticket, _) = mmap.write(2, |v| *v = 100u64).unwrap();
1447    /// assert_eq!(mmap.read(2, |v| *v).unwrap(), 100u64);
1448    /// ```
1449    #[inline(always)]
1450    pub fn write<R, F>(&self, index: usize, f: F) -> FrozenResult<(AckTicket, R)>
1451    where
1452        F: FnOnce(&mut T) -> R,
1453    {
1454        let mid = self.cfg.module_id;
1455        let size = core::mem::size_of::<T>();
1456
1457        let offset = match index.checked_mul(size) {
1458            Some(off) => off,
1459            None => return err::default_error(err::INV).with_module(mid),
1460        };
1461
1462        let slot_locks_guard = self.inner.slot_locks.read().unwrap_or_else(|e| e.into_inner());
1463
1464        if hints::unlikely(offset.checked_add(size).is_none_or(|end| end > self.length())) {
1465            return err::default_error(err::HCF).with_module(mid);
1466        }
1467
1468        let _slot_guard = slot_locks_guard.write_lock(index);
1469
1470        let guard = self.inner.mmap.read().unwrap_or_else(|e| e.into_inner());
1471        let mmap = match guard.as_ref() {
1472            Some(m) => m,
1473            None => return err::default_error(err::INV).with_module(mid),
1474        };
1475
1476        let ptr = unsafe { mmap.as_mut_ptr::<T>(offset) };
1477        let res = f(unsafe { &mut *ptr });
1478        drop(guard);
1479        drop(_slot_guard);
1480        drop(slot_locks_guard);
1481
1482        let epoch = self.inner.completion.increment_current_epoch();
1483
1484        if self.inner.is_parked.load(atomic::Ordering::Acquire) {
1485            let _guard = self.inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
1486            if self.inner.is_parked.swap(false, atomic::Ordering::AcqRel) {
1487                self.inner.sync_condvar.notify_one();
1488            }
1489        }
1490
1491        Ok((AckTicket::new(epoch, self.inner.completion.clone()), res))
1492    }
1493
1494    /// Atomically locks `N` consecutive slots and invokes `f` with mutable access to `Target`
1495    ///
1496    /// Requires `size_of::<Target>() == N * size_of::<T>()` and compatible alignment
1497    ///
1498    /// Returns a tuple `(AckTicket, R)` containing the durability acknowledgement ticket and the
1499    /// result returned by callback `f`
1500    ///
1501    /// ## Constraints
1502    ///
1503    /// - `size_of::<Target>()` must not be zero ([`err::ZRO`])
1504    /// - `size_of::<Target>()` must be an exact multiple of `size_of::<T>()` ([`err::SZE`])
1505    /// - `align_of::<Target>()` must not exceed `align_of::<T>()` ([`err::ALN`])
1506    /// - Slot index and byte offset calculations must not overflow `usize` ([`err::INV`])
1507    /// - Target slot range `start..start + N` must not exceed mapping bounds ([`err::HCF`])
1508    ///
1509    /// ## Deadlock Freedom
1510    ///
1511    /// Exclusive write spinlocks for slots `start..start + N` are acquired in strictly ascending
1512    /// order
1513    ///
1514    /// ## Example
1515    ///
1516    /// ```
1517    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1518    /// use std::time::Duration;
1519    ///
1520    /// #[repr(C)]
1521    /// struct Page {
1522    ///     header: u64,
1523    ///     payload: [u64; 3],
1524    /// }
1525    ///
1526    /// let dir = tempfile::tempdir().unwrap();
1527    /// let cfg = MemMapCfg {
1528    ///     module_id: 1,
1529    ///     path: dir.path().join("write_exact_demo.bin"),
1530    ///     version: 1,
1531    ///     initial_t: 512,
1532    ///     sync_interval: Some(Duration::from_millis(100)),
1533    ///     page_size: None,
1534    /// };
1535    ///
1536    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1537    /// let (ticket, _) = mmap
1538    ///     .write_exact(0, |page: &mut Page| {
1539    ///         page.header = 0xAA;
1540    ///         page.payload[0] = 0xBB;
1541    ///     })
1542    ///     .unwrap();
1543    ///
1544    /// assert_eq!(mmap.read(0, |v| *v).unwrap(), 0xAA);
1545    /// assert_eq!(mmap.read(1, |v| *v).unwrap(), 0xBB);
1546    /// assert_eq!(ticket.epoch(), 1);
1547    /// ```
1548    #[inline(always)]
1549    pub fn write_exact<Target, R, F>(&self, start: usize, f: F) -> FrozenResult<(AckTicket, R)>
1550    where
1551        F: FnOnce(&mut Target) -> R,
1552    {
1553        let mid = self.cfg.module_id;
1554        let slot_size = core::mem::size_of::<T>();
1555        let target_size = core::mem::size_of::<Target>();
1556
1557        if hints::unlikely(target_size == 0) {
1558            return err::default_error(err::ZRO).with_module(mid);
1559        }
1560
1561        if hints::unlikely(target_size % slot_size != 0) {
1562            return err::default_error(err::SZE).with_module(mid);
1563        }
1564
1565        if hints::unlikely(core::mem::align_of::<Target>() > core::mem::align_of::<T>()) {
1566            return err::default_error(err::ALN).with_module(mid);
1567        }
1568
1569        let n = target_size / slot_size;
1570
1571        let end_slot = match start.checked_add(n) {
1572            Some(end) => end,
1573            None => return err::default_error(err::INV).with_module(mid),
1574        };
1575
1576        let offset = match start.checked_mul(slot_size) {
1577            Some(off) => off,
1578            None => return err::default_error(err::INV).with_module(mid),
1579        };
1580
1581        let end_offset = match end_slot.checked_mul(slot_size) {
1582            Some(end) => end,
1583            None => return err::default_error(err::INV).with_module(mid),
1584        };
1585
1586        let slot_locks_guard = self.inner.slot_locks.read().unwrap_or_else(|e| e.into_inner());
1587
1588        if hints::unlikely(end_offset > self.length()) {
1589            return err::default_error(err::HCF).with_module(mid);
1590        }
1591
1592        let mut slot_guards = Vec::with_capacity(n);
1593        for slot in start..end_slot {
1594            slot_guards.push(slot_locks_guard.write_lock(slot));
1595        }
1596
1597        let guard = self.inner.mmap.read().unwrap_or_else(|e| e.into_inner());
1598        let mmap = match guard.as_ref() {
1599            Some(m) => m,
1600            None => return err::default_error(err::INV).with_module(mid),
1601        };
1602
1603        let ptr = unsafe { mmap.as_mut_ptr::<Target>(offset) };
1604        if hints::unlikely((ptr as usize) % core::mem::align_of::<Target>() != 0) {
1605            return err::default_error(err::ALN).with_module(mid);
1606        }
1607
1608        let target_ref = unsafe { &mut *ptr };
1609        let res = f(target_ref);
1610
1611        drop(guard);
1612        drop(slot_guards);
1613        drop(slot_locks_guard);
1614
1615        let epoch = self.inner.completion.increment_current_epoch();
1616
1617        if self.inner.is_parked.load(atomic::Ordering::Acquire) {
1618            let _guard = self.inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
1619            if self.inner.is_parked.swap(false, atomic::Ordering::AcqRel) {
1620                self.inner.sync_condvar.notify_one();
1621            }
1622        }
1623
1624        Ok((AckTicket::new(epoch, self.inner.completion.clone()), res))
1625    }
1626
1627    #[cfg(test)]
1628    pub(crate) fn is_parked(&self) -> bool {
1629        self.inner.is_parked.load(atomic::Ordering::Acquire)
1630    }
1631
1632    /// Deletes the [`MemMap`] and underlying [`File`] entry from the storage device
1633    ///
1634    /// Consumes `self` by value to release resources in deterministic order,
1635    ///
1636    /// - Signals the background flush worker to stop and waits for its thread join
1637    /// - Unmaps the active memory mapping from virtual address space
1638    /// - Closes and unlinks the backing versioned file from the filesystem
1639    ///
1640    /// ## Constraints
1641    ///
1642    /// - Consumes `self` by value; after deletion, the instance can no longer be accessed
1643    /// - Returns [`err::INV`] if the underlying file handle was already released
1644    ///
1645    /// ## Example
1646    ///
1647    /// ```
1648    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1649    /// use std::time::Duration;
1650    ///
1651    /// let dir = tempfile::tempdir().unwrap();
1652    /// let path = dir.path().join("delete_demo.bin");
1653    /// let versioned_path = dir.path().join("delete_demo_1.bin");
1654    /// let cfg = MemMapCfg {
1655    ///     module_id: 1,
1656    ///     path,
1657    ///     version: 1,
1658    ///     initial_t: 512,
1659    ///     sync_interval: Some(Duration::from_millis(100)),
1660    ///     page_size: None,
1661    /// };
1662    ///
1663    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1664    /// assert!(versioned_path.exists());
1665    ///
1666    /// mmap.delete().unwrap();
1667    /// assert!(!versioned_path.exists());
1668    /// ```
1669    pub fn delete(mut self) -> FrozenResult<()> {
1670        let mid = self.cfg.module_id;
1671        self.inner.shutdown.store(true, atomic::Ordering::Release);
1672        {
1673            let mut guard = self.inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
1674            *guard = true;
1675        }
1676        self.inner.sync_condvar.notify_all();
1677
1678        if let Some(handle) = self.bg_thread.take() {
1679            let _ = handle.join();
1680        }
1681
1682        let mut guard = self.inner.mmap.write().unwrap_or_else(|e| e.into_inner());
1683        let unmap_res =
1684            if let Some(mmap) = guard.take() { mmap.unmap().with_module(mid) } else { Ok(()) };
1685
1686        let file = match self.file.take() {
1687            Some(f) => f,
1688            None => {
1689                unmap_res?;
1690                return err::default_error(err::INV).with_module(mid);
1691            }
1692        };
1693
1694        let delete_res = file.delete().with_module(mid);
1695
1696        match (unmap_res, delete_res) {
1697            (Err(mut unmap_err), Err(del_err)) => {
1698                unmap_err.add_suppressed(del_err);
1699                Err(unmap_err)
1700            }
1701            (Err(unmap_err), Ok(())) => Err(unmap_err),
1702            (Ok(()), Err(del_err)) => Err(del_err),
1703            (Ok(()), Ok(())) => Ok(()),
1704        }
1705    }
1706}
1707
1708impl<T> MemMap<T>
1709where
1710    T: Sized + Clone + PartialEq,
1711{
1712    /// Conditionally writes `val` into slot `index` if and only if the current value matches
1713    /// `expected`
1714    ///
1715    /// ## Semantics
1716    ///
1717    /// Acquires the exclusive slot lock for `index` and verifies that the current slot value equals
1718    /// `expected`; if equal, writes `val`, advances the epoch, and returns an
1719    /// [`AckTicket`]. If the current value differs from `expected`, the operation immediately
1720    /// aborts without modifying the slot, returning [`err::CAS`] so the caller can re-read and
1721    /// retry
1722    ///
1723    /// ## Constraints
1724    ///
1725    /// - `index * size_of::<T>()` must not overflow `usize`, otherwise returns [`err::INV`]
1726    /// - Writing past the mapped memory region returns [`err::HCF`]
1727    /// - Returns [`err::CAS`] if `expected != current`
1728    ///
1729    /// ## Example
1730    ///
1731    /// ```
1732    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1733    /// use std::time::Duration;
1734    ///
1735    /// let dir = tempfile::tempdir().unwrap();
1736    /// let cfg = MemMapCfg {
1737    ///     module_id: 1,
1738    ///     path: dir.path().join("cas_demo.bin"),
1739    ///     version: 1,
1740    ///     initial_t: 512,
1741    ///     sync_interval: Some(Duration::from_millis(100)),
1742    ///     page_size: None,
1743    /// };
1744    ///
1745    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1746    /// mmap.write(0, |v| *v = 100u64).unwrap();
1747    ///
1748    /// // Successful CAS: 100 -> 200
1749    /// let ticket = mmap.compare_and_write(&100u64, &200u64, 0).unwrap();
1750    /// assert_eq!(mmap.read(0, |v| *v).unwrap(), 200u64);
1751    ///
1752    /// // Failed CAS: expected 100, but slot is 200
1753    /// assert!(mmap.compare_and_write(&100u64, &300u64, 0).is_err());
1754    /// assert_eq!(mmap.read(0, |v| *v).unwrap(), 200u64);
1755    /// ```
1756    pub fn compare_and_write(
1757        &self,
1758        expected: &T,
1759        val: &T,
1760        index: usize,
1761    ) -> FrozenResult<AckTicket> {
1762        let mid = self.cfg.module_id;
1763        let size = core::mem::size_of::<T>();
1764
1765        let offset = match index.checked_mul(size) {
1766            Some(off) => off,
1767            None => return err::default_error(err::INV).with_module(mid),
1768        };
1769
1770        let slot_locks_guard = self.inner.slot_locks.read().unwrap_or_else(|e| e.into_inner());
1771
1772        if hints::unlikely(offset.checked_add(size).is_none_or(|end| end > self.length())) {
1773            return err::default_error(err::HCF).with_module(mid);
1774        }
1775
1776        let _slot_guard = slot_locks_guard.write_lock(index);
1777
1778        let guard = self.inner.mmap.read().unwrap_or_else(|e| e.into_inner());
1779        let mmap = match guard.as_ref() {
1780            Some(m) => m,
1781            None => return err::default_error(err::INV).with_module(mid),
1782        };
1783
1784        let ptr = unsafe { mmap.as_mut_ptr::<T>(offset) };
1785        let current = unsafe { ptr.read() };
1786        if current != *expected {
1787            drop(guard);
1788            drop(_slot_guard);
1789            drop(slot_locks_guard);
1790            return err::raw_error(
1791                err::CAS,
1792                format!("state conflict at index {}: slot data has changed", index),
1793            )
1794            .with_module(mid);
1795        }
1796
1797        unsafe { core::ptr::write(ptr, val.clone()) };
1798        drop(guard);
1799        drop(_slot_guard);
1800        drop(slot_locks_guard);
1801
1802        let epoch = self.inner.completion.increment_current_epoch();
1803
1804        if self.inner.is_parked.load(atomic::Ordering::Acquire) {
1805            let _guard = self.inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
1806            if self.inner.is_parked.swap(false, atomic::Ordering::AcqRel) {
1807                self.inner.sync_condvar.notify_one();
1808            }
1809        }
1810
1811        Ok(AckTicket::new(epoch, self.inner.completion.clone()))
1812    }
1813
1814    /// Creates a new [`MemMapTransaction`] context for grouping multiple conditional writes into a
1815    /// single atomic epoch
1816    ///
1817    /// ## Overview
1818    ///
1819    /// `MemMapTransaction` allows grouping multiple slot mutations into a single atomic operation:
1820    ///
1821    /// - **Atomicity**: All writes succeed or fail together
1822    /// - **Optimistic Concurrency Control (CAS)**: Each write asserts that its target slot
1823    ///   currently matches `expected`; if any slot differs at commit time, all changes abort and no
1824    ///   slots are modified
1825    /// - **Single Epoch**: All committed writes share a single durability epoch and durable window
1826    /// - **Deadlock Freedom**: Slot locks are acquired in strictly ascending index order
1827    ///
1828    /// ## Example
1829    ///
1830    /// ```
1831    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1832    /// use std::time::Duration;
1833    ///
1834    /// let dir = tempfile::tempdir().unwrap();
1835    /// let cfg = MemMapCfg {
1836    ///     module_id: 1,
1837    ///     path: dir.path().join("tx_demo.bin"),
1838    ///     version: 1,
1839    ///     initial_t: 512,
1840    ///     sync_interval: Some(Duration::from_millis(100)),
1841    ///     page_size: None,
1842    /// };
1843    ///
1844    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1845    ///
1846    /// let mut tx = mmap.new_tx();
1847    /// tx.write(&0u64, &10u64, 0).unwrap();
1848    /// tx.write(&0u64, &20u64, 1).unwrap();
1849    ///
1850    /// let ticket = tx.commit().unwrap();
1851    /// assert_eq!(mmap.read(0, |v| *v).unwrap(), 10u64);
1852    /// assert_eq!(mmap.read(1, |v| *v).unwrap(), 20u64);
1853    /// ```
1854    #[inline]
1855    pub fn new_tx(&self) -> MemMapTransaction<'_, T> {
1856        MemMapTransaction { memmap: self, ops: Vec::new() }
1857    }
1858}
1859
1860/// Context for grouping multiple conditional writes into a single atomic operation
1861///
1862/// Multi-slot mutations are staged in `MemMapTransaction` with optimistic concurrency
1863/// preconditions. At [`commit`](Self::commit) time, all target slot locks are acquired in strictly
1864/// ascending order, each slot is verified against its `expected` value, and if all match, all
1865/// updates are applied under a single epoch
1866///
1867/// If any slot fails verification, the transaction aborts with zero mutations, returning
1868/// [`err::CAS`] with the index of the first conflicting slot so the caller can retry
1869///
1870/// ## Example
1871///
1872/// ```
1873/// use frozen_core::memmap::{MemMap, MemMapCfg};
1874/// use std::time::Duration;
1875///
1876/// let dir = tempfile::tempdir().unwrap();
1877/// let cfg = MemMapCfg {
1878///     module_id: 1,
1879///     path: dir.path().join("tx_struct_demo.bin"),
1880///     version: 1,
1881///     initial_t: 512,
1882///     sync_interval: Some(Duration::from_millis(100)),
1883///     page_size: None,
1884/// };
1885///
1886/// let mmap = MemMap::<u64>::new(cfg).unwrap();
1887/// let mut tx = mmap.new_tx();
1888/// tx.write(&0u64, &42u64, 0).unwrap();
1889/// tx.write(&0u64, &84u64, 2).unwrap();
1890///
1891/// let ticket = tx.commit().unwrap();
1892/// assert_eq!(mmap.read(0, |v| *v).unwrap(), 42u64);
1893/// assert_eq!(mmap.read(2, |v| *v).unwrap(), 84u64);
1894/// ```
1895#[derive(Debug)]
1896pub struct MemMapTransaction<'a, T: Sized + Clone + PartialEq> {
1897    memmap: &'a MemMap<T>,
1898    ops: Vec<(usize, T, T)>,
1899}
1900
1901impl<T> MemMapTransaction<'_, T>
1902where
1903    T: Sized + Clone + PartialEq,
1904{
1905    /// Stages a conditional write of `new` at `index` requiring `expected` as the current slot
1906    /// value
1907    ///
1908    /// ## Requirements
1909    ///
1910    /// - Writes must be staged in strictly increasing index order (`index > previous_index`)
1911    /// - Duplicate indices in the same transaction return an error
1912    /// - `index` must not exceed mapping bounds
1913    ///
1914    /// ## Example
1915    ///
1916    /// ```
1917    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1918    /// use std::time::Duration;
1919    ///
1920    /// let dir = tempfile::tempdir().unwrap();
1921    /// let cfg = MemMapCfg {
1922    ///     module_id: 1,
1923    ///     path: dir.path().join("tx_write_demo.bin"),
1924    ///     version: 1,
1925    ///     initial_t: 512,
1926    ///     sync_interval: Some(Duration::from_millis(100)),
1927    ///     page_size: None,
1928    /// };
1929    ///
1930    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1931    /// let mut tx = mmap.new_tx();
1932    /// tx.write(&0u64, &111u64, 0).unwrap();
1933    /// tx.write(&0u64, &222u64, 1).unwrap();
1934    ///
1935    /// // Staging an out-of-order or duplicate index fails
1936    /// assert!(tx.write(&0u64, &333u64, 1).is_err());
1937    /// assert!(tx.write(&0u64, &333u64, 0).is_err());
1938    /// ```
1939    #[inline(always)]
1940    pub fn write(&mut self, expected: &T, new: &T, index: usize) -> FrozenResult<&mut Self> {
1941        let mid = self.memmap.cfg.module_id;
1942        if let Some(err) = self.memmap.inner.completion.get_err() {
1943            return Err(err);
1944        }
1945
1946        if let Some((last_idx, _, _)) = self.ops.last() {
1947            if index <= *last_idx {
1948                return err::raw_error(
1949                    err::HCF,
1950                    "tx writes must be strictly ordered, with no more then single ops on given index",
1951                )
1952                .with_module(mid);
1953            }
1954        }
1955
1956        let size = core::mem::size_of::<T>();
1957        let offset = match index.checked_mul(size) {
1958            Some(off) => off,
1959            None => return err::default_error(err::INV).with_module(mid),
1960        };
1961
1962        if hints::unlikely(offset.checked_add(size).is_none_or(|end| end > self.memmap.length())) {
1963            return err::default_error(err::HCF).with_module(mid);
1964        }
1965
1966        self.ops.push((index, expected.clone(), new.clone()));
1967        Ok(self)
1968    }
1969
1970    /// Commits all staged writes atomically under a single durability epoch
1971    ///
1972    /// Acquires exclusive locks for all affected slots in order; if all slots match their expected
1973    /// values, updates are applied, the durable epoch advances once, and an [`AckTicket`] is
1974    /// returned
1975    ///
1976    /// If any slot differs from `expected`, all locks are released without modifying any data, and
1977    /// [`err::CAS`] is returned with context identifying the conflicting slot index
1978    ///
1979    /// ## Example
1980    ///
1981    /// ```
1982    /// use frozen_core::memmap::{MemMap, MemMapCfg};
1983    /// use std::time::Duration;
1984    ///
1985    /// let dir = tempfile::tempdir().unwrap();
1986    /// let cfg = MemMapCfg {
1987    ///     module_id: 1,
1988    ///     path: dir.path().join("tx_commit_demo.bin"),
1989    ///     version: 1,
1990    ///     initial_t: 512,
1991    ///     sync_interval: Some(Duration::from_millis(100)),
1992    ///     page_size: None,
1993    /// };
1994    ///
1995    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
1996    /// let mut tx = mmap.new_tx();
1997    /// tx.write(&0u64, &100u64, 0).unwrap();
1998    /// tx.write(&0u64, &200u64, 1).unwrap();
1999    ///
2000    /// let ticket = tx.commit().unwrap();
2001    /// assert_eq!(mmap.read(0, |v| *v).unwrap(), 100u64);
2002    /// assert_eq!(mmap.read(1, |v| *v).unwrap(), 200u64);
2003    /// ```
2004    pub fn commit(self) -> FrozenResult<AckTicket> {
2005        let mid = self.memmap.cfg.module_id;
2006        if self.ops.is_empty() {
2007            return err::raw_error(err::INV, "empty transaction").with_module(mid);
2008        }
2009
2010        if let Some(err) = self.memmap.inner.completion.get_err() {
2011            return Err(err);
2012        }
2013
2014        let size = core::mem::size_of::<T>();
2015        let slot_locks_guard =
2016            self.memmap.inner.slot_locks.read().unwrap_or_else(|e| e.into_inner());
2017        let current_length = self.memmap.length();
2018
2019        // Verify bounds under slot_locks read guard
2020        for (index, _, _) in &self.ops {
2021            let offset = match index.checked_mul(size) {
2022                Some(off) => off,
2023                None => return err::default_error(err::INV).with_module(mid),
2024            };
2025            if hints::unlikely(offset.checked_add(size).is_none_or(|end| end > current_length)) {
2026                return err::default_error(err::HCF).with_module(mid);
2027            }
2028        }
2029
2030        // Acquire all slot write locks in strictly ascending index order
2031        let mut slot_guards = Vec::with_capacity(self.ops.len());
2032        for (index, _, _) in &self.ops {
2033            slot_guards.push(slot_locks_guard.write_lock(*index));
2034        }
2035
2036        let guard = self.memmap.inner.mmap.read().unwrap_or_else(|e| e.into_inner());
2037        let mmap = match guard.as_ref() {
2038            Some(m) => m,
2039            None => return err::default_error(err::INV).with_module(mid),
2040        };
2041
2042        // Phase 1: State Precondition Verification (CAS)
2043        for (index, expected, _) in &self.ops {
2044            let offset = *index * size;
2045            let ptr = unsafe { mmap.as_ptr::<T>(offset) };
2046            let current = unsafe { ptr.read() };
2047            if current != *expected {
2048                drop(guard);
2049                drop(slot_guards);
2050                drop(slot_locks_guard);
2051                return err::raw_error(
2052                    err::CAS,
2053                    format!("state conflict at index {}: slot data has changed", index),
2054                )
2055                .with_module(mid);
2056            }
2057        }
2058
2059        // Phase 2: Apply mutations
2060        for (index, _, new) in self.ops {
2061            let offset = index * size;
2062            let ptr = unsafe { mmap.as_mut_ptr::<T>(offset) };
2063            unsafe { core::ptr::write(ptr, new) };
2064        }
2065
2066        drop(guard);
2067        drop(slot_guards);
2068        drop(slot_locks_guard);
2069
2070        let epoch = self.memmap.inner.completion.increment_current_epoch();
2071
2072        if self.memmap.inner.is_parked.load(atomic::Ordering::Acquire) {
2073            let _guard = self.memmap.inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
2074            if self.memmap.inner.is_parked.swap(false, atomic::Ordering::AcqRel) {
2075                self.memmap.inner.sync_condvar.notify_one();
2076            }
2077        }
2078
2079        Ok(AckTicket::new(epoch, self.memmap.inner.completion.clone()))
2080    }
2081
2082    /// Returns the number of staged operations in this transaction
2083    ///
2084    /// ## Example
2085    ///
2086    /// ```
2087    /// use frozen_core::memmap::{MemMap, MemMapCfg};
2088    ///
2089    /// let dir = tempfile::tempdir().unwrap();
2090    /// let cfg = MemMapCfg {
2091    ///     module_id: 1,
2092    ///     path: dir.path().join("tx_len_demo.bin"),
2093    ///     version: 1,
2094    ///     initial_t: 512,
2095    ///     sync_interval: None,
2096    ///     page_size: None,
2097    /// };
2098    ///
2099    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
2100    /// let mut tx = mmap.new_tx();
2101    /// assert_eq!(tx.len(), 0);
2102    /// tx.write(&0u64, &10u64, 0).unwrap();
2103    /// assert_eq!(tx.len(), 1);
2104    /// ```
2105    #[inline]
2106    pub fn len(&self) -> usize {
2107        self.ops.len()
2108    }
2109
2110    /// Returns `true` if no operations have been staged in this transaction
2111    ///
2112    /// ## Example
2113    ///
2114    /// ```
2115    /// use frozen_core::memmap::{MemMap, MemMapCfg};
2116    ///
2117    /// let dir = tempfile::tempdir().unwrap();
2118    /// let cfg = MemMapCfg {
2119    ///     module_id: 1,
2120    ///     path: dir.path().join("tx_empty_demo.bin"),
2121    ///     version: 1,
2122    ///     initial_t: 512,
2123    ///     sync_interval: None,
2124    ///     page_size: None,
2125    /// };
2126    ///
2127    /// let mmap = MemMap::<u64>::new(cfg).unwrap();
2128    /// let tx = mmap.new_tx();
2129    /// assert!(tx.is_empty());
2130    /// ```
2131    #[inline]
2132    pub fn is_empty(&self) -> bool {
2133        self.ops.is_empty()
2134    }
2135}
2136
2137impl<T> Drop for MemMap<T>
2138where
2139    T: Sized + Clone,
2140{
2141    fn drop(&mut self) {
2142        self.inner.shutdown.store(true, atomic::Ordering::Release);
2143        {
2144            let mut guard = self.inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
2145            *guard = true;
2146        }
2147        self.inner.sync_condvar.notify_all();
2148
2149        if let Some(handle) = self.bg_thread.take() {
2150            let _ = handle.join();
2151        }
2152
2153        let _ = self.inner.sync_internal();
2154    }
2155}
2156
2157#[cfg(test)]
2158mod tests {
2159    use super::*;
2160    use std::time::Duration;
2161
2162    const MID: u8 = 0;
2163    const INIT_T: usize = 512;
2164
2165    #[derive(Debug, Clone)]
2166    #[repr(C, align(8))]
2167    struct ValidT {
2168        a: u64,
2169        b: u64,
2170    }
2171
2172    #[derive(Debug, Clone)]
2173    #[repr(C)]
2174    struct BadAlignT {
2175        a: u32,
2176        b: u32,
2177    }
2178
2179    #[derive(Debug, Clone)]
2180    #[repr(C)]
2181    struct BadSizeT {
2182        a: [u8; 0x0C],
2183    }
2184
2185    #[derive(Debug, Clone)]
2186    #[repr(C, align(8))]
2187    struct DropT(u64);
2188
2189    impl Drop for DropT {
2190        fn drop(&mut self) {}
2191    }
2192
2193    #[derive(Debug, Clone)]
2194    #[repr(C, align(8))]
2195    struct ZstT;
2196
2197    #[derive(Debug, Clone)]
2198    #[repr(C, align(8))]
2199    struct OversizedT {
2200        data: [u8; 0x20000],
2201    }
2202
2203    fn tmp_cfg() -> (tempfile::TempDir, MemMapCfg) {
2204        let dir = tempfile::tempdir().unwrap();
2205        let path = dir.path().join("tmp_memmap");
2206        let cfg = MemMapCfg {
2207            module_id: MID,
2208            path,
2209            version: 1,
2210            initial_t: INIT_T,
2211            sync_interval: Some(Duration::from_millis(0x64)),
2212            page_size: None,
2213        };
2214
2215        (dir, cfg)
2216    }
2217
2218    #[test]
2219    fn ok_validate_t() {
2220        let (_dir, cfg) = tmp_cfg();
2221        assert!(MemMap::<u64>::validate_t(&cfg).is_ok());
2222        let mut cfg_valid = cfg.clone();
2223        cfg_valid.initial_t = 256;
2224        assert!(MemMap::<ValidT>::validate_t(&cfg_valid).is_ok());
2225    }
2226
2227    #[test]
2228    fn err_validate_t_invariants() {
2229        let (_dir, cfg) = tmp_cfg();
2230        assert_eq!(MemMap::<DropT>::validate_t(&cfg).unwrap_err().reason, err::DRP.reason);
2231        assert_eq!(MemMap::<BadAlignT>::validate_t(&cfg).unwrap_err().reason, err::ALN.reason);
2232        assert_eq!(MemMap::<ZstT>::validate_t(&cfg).unwrap_err().reason, err::ZRO.reason);
2233        assert_eq!(MemMap::<BadSizeT>::validate_t(&cfg).unwrap_err().reason, err::SZE.reason);
2234        assert_eq!(MemMap::<OversizedT>::validate_t(&cfg).unwrap_err().reason, err::INV.reason);
2235
2236        let mut zero_cfg = cfg.clone();
2237        zero_cfg.initial_t = 0;
2238        assert_eq!(MemMap::<u64>::validate_t(&zero_cfg).unwrap_err().reason, err::INV.reason);
2239
2240        let mut unaligned_cfg = cfg.clone();
2241        unaligned_cfg.initial_t = 511;
2242        assert_eq!(MemMap::<u64>::validate_t(&unaligned_cfg).unwrap_err().reason, err::INV.reason);
2243    }
2244
2245    #[test]
2246    fn ok_new_sets_file_sync_interval_none() {
2247        let (_dir, cfg) = tmp_cfg();
2248
2249        let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
2250        assert_eq!(mmap.length(), core::mem::size_of::<u64>() * INIT_T);
2251        assert_eq!(mmap.cfg().sync_interval, Some(Duration::from_millis(0x64)));
2252    }
2253
2254    #[test]
2255    fn err_new_when_file_exists() {
2256        let (_dir, cfg) = tmp_cfg();
2257        let _mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
2258        let err = MemMap::<u64>::new(cfg).unwrap_err();
2259        assert_eq!(err.reason, err::EXS.reason);
2260    }
2261
2262    #[test]
2263    fn err_new_when_t_invalid() {
2264        let (_dir, cfg) = tmp_cfg();
2265        assert_eq!(MemMap::<DropT>::new(cfg.clone()).unwrap_err().reason, err::DRP.reason);
2266        assert!(!cfg.versioned_path().exists());
2267    }
2268
2269    #[test]
2270    fn ok_open() {
2271        let (_dir, cfg) = tmp_cfg();
2272        {
2273            let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
2274            assert_eq!(mmap.length(), core::mem::size_of::<u64>() * INIT_T);
2275        }
2276
2277        let opened = MemMap::<u64>::open(cfg).unwrap();
2278        assert_eq!(opened.length(), core::mem::size_of::<u64>() * INIT_T);
2279    }
2280
2281    #[test]
2282    fn err_open_when_file_missing() {
2283        let (_dir, cfg) = tmp_cfg();
2284        let err = MemMap::<u64>::open(cfg).unwrap_err();
2285        assert_eq!(err.reason, err::NEX.reason);
2286    }
2287
2288    #[test]
2289    fn ok_open_or_create() {
2290        let (_dir, cfg) = tmp_cfg();
2291        {
2292            let mmap = MemMap::<u64>::open_or_create(cfg.clone()).unwrap();
2293            assert_eq!(mmap.length(), core::mem::size_of::<u64>() * INIT_T);
2294        }
2295
2296        let reopened = MemMap::<u64>::open_or_create(cfg).unwrap();
2297        assert_eq!(reopened.length(), core::mem::size_of::<u64>() * INIT_T);
2298    }
2299
2300    #[test]
2301    fn err_path_when_dir_missing() {
2302        let dir = tempfile::tempdir().unwrap();
2303        let non_existent_dir = dir.path().join("missing_dir_xyz");
2304        let cfg = MemMapCfg {
2305            module_id: MID,
2306            path: non_existent_dir.join("file.bin"),
2307            version: 1,
2308            initial_t: INIT_T,
2309            sync_interval: Some(Duration::from_millis(0x64)),
2310            page_size: None,
2311        };
2312
2313        let err_new = MemMap::<u64>::new(cfg.clone()).unwrap_err();
2314        assert_eq!(err_new.reason, err::DIR.reason);
2315
2316        let err_open = MemMap::<u64>::open(cfg.clone()).unwrap_err();
2317        assert_eq!(err_open.reason, err::DIR.reason);
2318
2319        let err_open_or_create = MemMap::<u64>::open_or_create(cfg).unwrap_err();
2320        assert_eq!(err_open_or_create.reason, err::DIR.reason);
2321    }
2322
2323    #[test]
2324    fn err_path_when_parent_is_not_dir() {
2325        let dir = tempfile::tempdir().unwrap();
2326        let file_path = dir.path().join("regular_file");
2327        std::fs::write(&file_path, b"test").unwrap();
2328
2329        let cfg = MemMapCfg {
2330            module_id: MID,
2331            path: file_path.join("subfile.bin"),
2332            version: 1,
2333            initial_t: INIT_T,
2334            sync_interval: Some(Duration::from_millis(0x64)),
2335            page_size: None,
2336        };
2337
2338        let err_new = MemMap::<u64>::new(cfg.clone()).unwrap_err();
2339        assert_eq!(err_new.reason, err::DIR.reason);
2340
2341        let err_open = MemMap::<u64>::open(cfg.clone()).unwrap_err();
2342        assert_eq!(err_open.reason, err::DIR.reason);
2343
2344        let err_open_or_create = MemMap::<u64>::open_or_create(cfg).unwrap_err();
2345        assert_eq!(err_open_or_create.reason, err::DIR.reason);
2346    }
2347
2348    #[test]
2349    fn err_path_when_empty() {
2350        let cfg = MemMapCfg {
2351            module_id: MID,
2352            path: std::path::PathBuf::from(""),
2353            version: 1,
2354            initial_t: INIT_T,
2355            sync_interval: Some(Duration::from_millis(0x64)),
2356            page_size: None,
2357        };
2358
2359        let err_new = MemMap::<u64>::new(cfg.clone()).unwrap_err();
2360        assert_eq!(err_new.reason, err::PTH.reason);
2361
2362        let err_open = MemMap::<u64>::open(cfg.clone()).unwrap_err();
2363        assert_eq!(err_open.reason, err::PTH.reason);
2364
2365        let err_open_or_create = MemMap::<u64>::open_or_create(cfg).unwrap_err();
2366        assert_eq!(err_open_or_create.reason, err::PTH.reason);
2367    }
2368
2369    #[test]
2370    fn err_path_when_is_directory() {
2371        let dir = tempfile::tempdir().unwrap();
2372        let cfg = MemMapCfg {
2373            module_id: MID,
2374            path: dir.path().to_path_buf(),
2375            version: 1,
2376            initial_t: INIT_T,
2377            sync_interval: Some(Duration::from_millis(0x64)),
2378            page_size: None,
2379        };
2380
2381        let err_new = MemMap::<u64>::new(cfg.clone()).unwrap_err();
2382        assert_eq!(err_new.reason, err::PTH.reason);
2383
2384        let err_open = MemMap::<u64>::open(cfg.clone()).unwrap_err();
2385        assert_eq!(err_open.reason, err::PTH.reason);
2386
2387        let err_open_or_create = MemMap::<u64>::open_or_create(cfg).unwrap_err();
2388        assert_eq!(err_open_or_create.reason, err::PTH.reason);
2389    }
2390
2391    #[test]
2392    fn err_path_when_no_filename() {
2393        let cfg = MemMapCfg {
2394            module_id: MID,
2395            path: std::path::PathBuf::from("/"),
2396            version: 1,
2397            initial_t: INIT_T,
2398            sync_interval: Some(Duration::from_millis(0x64)),
2399            page_size: None,
2400        };
2401
2402        let err_new = MemMap::<u64>::new(cfg.clone()).unwrap_err();
2403        assert_eq!(err_new.reason, err::PTH.reason);
2404
2405        let err_open = MemMap::<u64>::open(cfg.clone()).unwrap_err();
2406        assert_eq!(err_open.reason, err::PTH.reason);
2407
2408        let err_open_or_create = MemMap::<u64>::open_or_create(cfg).unwrap_err();
2409        assert_eq!(err_open_or_create.reason, err::PTH.reason);
2410    }
2411
2412    #[test]
2413    fn ok_delete() {
2414        let (_dir, cfg) = tmp_cfg();
2415        let path = cfg.versioned_path();
2416        let mmap = MemMap::<u64>::new(cfg).unwrap();
2417        assert!(path.exists());
2418        assert!(mmap.delete().is_ok());
2419        assert!(!path.exists());
2420    }
2421
2422    #[test]
2423    fn ok_versioned_path_resolution() {
2424        let cfg = MemMapCfg {
2425            module_id: MID,
2426            path: std::path::PathBuf::from("data.bin"),
2427            version: 1,
2428            initial_t: 2,
2429            sync_interval: Some(Duration::from_millis(0x64)),
2430            page_size: None,
2431        };
2432        assert_eq!(cfg.versioned_path(), std::path::PathBuf::from("data_1.bin"));
2433
2434        let cfg = MemMapCfg {
2435            module_id: MID,
2436            path: std::path::PathBuf::from("datastore"),
2437            version: 2,
2438            initial_t: 2,
2439            sync_interval: Some(Duration::from_millis(0x64)),
2440            page_size: None,
2441        };
2442        assert_eq!(cfg.versioned_path(), std::path::PathBuf::from("datastore_2"));
2443
2444        let cfg = MemMapCfg {
2445            module_id: MID,
2446            path: std::path::PathBuf::from("archive.tar.gz"),
2447            version: 3,
2448            initial_t: 2,
2449            sync_interval: Some(Duration::from_millis(0x64)),
2450            page_size: None,
2451        };
2452        assert_eq!(cfg.versioned_path(), std::path::PathBuf::from("archive.tar_3.gz"));
2453
2454        let cfg = MemMapCfg {
2455            module_id: MID,
2456            path: std::path::PathBuf::from("/var/data/store.db"),
2457            version: 0x0A,
2458            initial_t: 2,
2459            sync_interval: Some(Duration::from_millis(0x64)),
2460            page_size: None,
2461        };
2462        assert_eq!(cfg.versioned_path(), std::path::PathBuf::from("/var/data/store_10.db"));
2463
2464        let cfg = MemMapCfg {
2465            module_id: MID,
2466            path: std::path::PathBuf::from("dir/sub/file.dat"),
2467            version: 0,
2468            initial_t: 2,
2469            sync_interval: Some(Duration::from_millis(0x64)),
2470            page_size: None,
2471        };
2472        assert_eq!(cfg.versioned_path(), std::path::PathBuf::from("dir/sub/file_0.dat"));
2473
2474        let cfg = MemMapCfg {
2475            module_id: MID,
2476            path: std::path::PathBuf::from(".env"),
2477            version: 5,
2478            initial_t: 2,
2479            sync_interval: Some(Duration::from_millis(0x64)),
2480            page_size: None,
2481        };
2482        assert_eq!(cfg.versioned_path(), std::path::PathBuf::from(".env_5"));
2483
2484        let cfg = MemMapCfg {
2485            module_id: MID,
2486            path: std::path::PathBuf::from(".config.json"),
2487            version: 1,
2488            initial_t: 2,
2489            sync_interval: Some(Duration::from_millis(0x64)),
2490            page_size: None,
2491        };
2492        assert_eq!(cfg.versioned_path(), std::path::PathBuf::from(".config_1.json"));
2493    }
2494
2495    #[test]
2496    fn ok_versioned_path_and_multiversion() {
2497        let dir = tempfile::tempdir().unwrap();
2498        let base_path = dir.path().join("store.dat");
2499
2500        let cfg_v1 = MemMapCfg {
2501            module_id: MID,
2502            path: base_path.clone(),
2503            version: 1,
2504            initial_t: 512,
2505            sync_interval: Some(Duration::from_millis(0x64)),
2506            page_size: None,
2507        };
2508        assert_eq!(cfg_v1.versioned_path(), dir.path().join("store_1.dat"));
2509
2510        let cfg_v2 = MemMapCfg {
2511            module_id: MID,
2512            path: base_path.clone(),
2513            version: 2,
2514            initial_t: 256,
2515            sync_interval: Some(Duration::from_millis(0x64)),
2516            page_size: None,
2517        };
2518        assert_eq!(cfg_v2.versioned_path(), dir.path().join("store_2.dat"));
2519
2520        // Both versions can coexist independently on disk
2521        let mmap_v1 = MemMap::<u64>::new(cfg_v1.clone()).unwrap();
2522        let mmap_v2 = MemMap::<ValidT>::new(cfg_v2.clone()).unwrap();
2523
2524        assert!(cfg_v1.versioned_path().exists());
2525        assert!(cfg_v2.versioned_path().exists());
2526        assert_eq!(mmap_v1.length(), core::mem::size_of::<u64>() * 512);
2527        assert_eq!(mmap_v2.length(), core::mem::size_of::<ValidT>() * 256);
2528
2529        assert!(mmap_v1.delete().is_ok());
2530        assert!(!cfg_v1.versioned_path().exists());
2531        assert!(cfg_v2.versioned_path().exists());
2532
2533        assert!(mmap_v2.delete().is_ok());
2534        assert!(!cfg_v2.versioned_path().exists());
2535    }
2536
2537    #[test]
2538    fn ok_write_read_single() {
2539        let (_dir, cfg) = tmp_cfg();
2540        let mmap = MemMap::<u64>::new(cfg).unwrap();
2541
2542        assert_eq!(mmap.read(0, |v| *v).unwrap(), 0);
2543        let (ticket, _) = mmap.write(0, |v| *v = 0xDEAD_BEEF_CAFE_BABE).unwrap();
2544        assert_eq!(mmap.read(0, |v| *v).unwrap(), 0xDEAD_BEEF_CAFE_BABE);
2545        assert_eq!(ticket.epoch(), 1);
2546    }
2547
2548    #[test]
2549    fn ok_write_read_multiple_indices() {
2550        let (_dir, cfg) = tmp_cfg();
2551        let mmap = MemMap::<u64>::new(cfg).unwrap();
2552
2553        for i in 0..INIT_T {
2554            let val = (i as u64 + 1).wrapping_mul(0x1111_2222_3333_4444);
2555            mmap.write(i, |v| *v = val).unwrap();
2556        }
2557
2558        for i in 0..INIT_T {
2559            let expected = (i as u64 + 1).wrapping_mul(0x1111_2222_3333_4444);
2560            assert_eq!(mmap.read(i, |v| *v).unwrap(), expected);
2561        }
2562    }
2563
2564    #[test]
2565    fn ok_write_read_struct() {
2566        let (_dir, cfg) = tmp_cfg();
2567        let mmap = MemMap::<ValidT>::new(cfg).unwrap();
2568
2569        let val0 = ValidT { a: 0x0A, b: 0x14 };
2570        let val1 = ValidT { a: 0x64, b: 0xC8 };
2571
2572        mmap.write(0, |v| *v = val0).unwrap();
2573        mmap.write(1, |v| *v = val1).unwrap();
2574
2575        let r0 = mmap.read(0, |v| v.clone()).unwrap();
2576        let r1 = mmap.read(1, |v| v.clone()).unwrap();
2577
2578        assert_eq!(r0.a, 0x0A);
2579        assert_eq!(r0.b, 0x14);
2580        assert_eq!(r1.a, 0x64);
2581        assert_eq!(r1.b, 0xC8);
2582    }
2583
2584    #[test]
2585    fn ok_read_callback_zero_copy_projection() {
2586        let (_dir, cfg) = tmp_cfg();
2587        let mmap = MemMap::<ValidT>::new(cfg).unwrap();
2588
2589        let val = ValidT { a: 0x1234, b: 0x5678 };
2590        mmap.write(0, |v| *v = val).unwrap();
2591
2592        let a_val = mmap.read(0, |v| v.a).unwrap();
2593        assert_eq!(a_val, 0x1234);
2594
2595        let sum = mmap.read(0, |v| v.a + v.b).unwrap();
2596        assert_eq!(sum, 0x1234 + 0x5678);
2597
2598        let is_match = mmap.read(0, |v| v.b == 0x5678).unwrap();
2599        assert!(is_match);
2600    }
2601
2602    #[test]
2603    fn ok_read_callback_unwind_releases_lock() {
2604        let (_dir, cfg) = tmp_cfg();
2605        let mmap = MemMap::<u64>::new(cfg).unwrap();
2606        mmap.write(0, |v| *v = 0x42).unwrap();
2607
2608        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2609            let _ = mmap.read(0, |_| panic!("simulated panic in read callback"));
2610        }));
2611        assert!(result.is_err());
2612
2613        assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x42);
2614        assert!(mmap.write(0, |v| *v = 0x99).is_ok());
2615        assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x99);
2616    }
2617
2618    #[test]
2619    fn ok_write_callback_in_place_mutation() {
2620        let (_dir, cfg) = tmp_cfg();
2621        let mmap = MemMap::<ValidT>::new(cfg).unwrap();
2622
2623        mmap.write(0, |v| {
2624            v.a = 0x1111;
2625            v.b = 0x2222;
2626        })
2627        .unwrap();
2628
2629        mmap.write(0, |v| {
2630            v.b = 0x9999;
2631        })
2632        .unwrap();
2633
2634        let val = mmap.read(0, |v| v.clone()).unwrap();
2635        assert_eq!(val.a, 0x1111);
2636        assert_eq!(val.b, 0x9999);
2637    }
2638
2639    #[test]
2640    fn ok_write_callback_returns_derived_value() {
2641        let (_dir, cfg) = tmp_cfg();
2642        let mmap = MemMap::<u64>::new(cfg).unwrap();
2643
2644        let (ticket, old_prev) = mmap
2645            .write(0, |v| {
2646                let prev = *v;
2647                *v = 42;
2648                prev
2649            })
2650            .unwrap();
2651
2652        assert_eq!(old_prev, 0);
2653        assert_eq!(ticket.epoch(), 1);
2654        assert_eq!(mmap.read(0, |v| *v).unwrap(), 42);
2655    }
2656
2657    #[test]
2658    fn ok_write_callback_unwind_releases_lock() {
2659        let (_dir, cfg) = tmp_cfg();
2660        let mmap = MemMap::<u64>::new(cfg).unwrap();
2661        let epoch_before = mmap.current_epoch();
2662
2663        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2664            let _ = mmap.write(0, |v| {
2665                *v = 0xBAD;
2666                panic!("intentional panic inside write callback");
2667            });
2668        }));
2669        assert!(result.is_err());
2670
2671        assert_eq!(mmap.current_epoch(), epoch_before);
2672        assert!(mmap.write(0, |v| *v = 0x600D).is_ok());
2673        assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x600D);
2674    }
2675
2676    #[test]
2677    fn ok_concurrent_writes_and_reads_single_slot() {
2678        let (_dir, mut cfg) = tmp_cfg();
2679        cfg.initial_t = 256;
2680        let mmap = Arc::new(MemMap::<ValidT>::new(cfg).unwrap());
2681
2682        let barrier = Arc::new(std::sync::Barrier::new(4));
2683        let mut handles = Vec::new();
2684
2685        for t in 0..2 {
2686            let m = Arc::clone(&mmap);
2687            let b = Arc::clone(&barrier);
2688            handles.push(std::thread::spawn(move || {
2689                b.wait();
2690                for i in 0..0xC8 {
2691                    let val = ((t + 1) as u64) << 0x20 | (i as u64);
2692                    let _ = m.write(0, |node| {
2693                        node.a = val;
2694                        node.b = val;
2695                    });
2696                }
2697            }));
2698        }
2699
2700        for _ in 0..2 {
2701            let m = Arc::clone(&mmap);
2702            let b = Arc::clone(&barrier);
2703            handles.push(std::thread::spawn(move || {
2704                b.wait();
2705                for _ in 0..0x190 {
2706                    let _ = m.read(0, |node| {
2707                        assert_eq!(node.a, node.b);
2708                    });
2709                }
2710            }));
2711        }
2712
2713        for h in handles {
2714            h.join().unwrap();
2715        }
2716
2717        mmap.read(0, |node| {
2718            assert_eq!(node.a, node.b);
2719        })
2720        .unwrap();
2721    }
2722
2723    #[test]
2724    fn err_write_read_out_of_bounds() {
2725        let (_dir, cfg) = tmp_cfg();
2726        let mmap = MemMap::<u64>::new(cfg).unwrap();
2727
2728        let err_r = mmap.read(INIT_T, |_| ()).unwrap_err();
2729        assert_eq!(err_r.reason, err::HCF.reason);
2730
2731        let err_w = mmap.write(INIT_T, |_| ()).unwrap_err();
2732        assert_eq!(err_w.reason, err::HCF.reason);
2733
2734        let err_r_large = mmap.read(INIT_T + 0x64, |_| ()).unwrap_err();
2735        assert_eq!(err_r_large.reason, err::HCF.reason);
2736
2737        let err_w_large = mmap.write(INIT_T + 0x64, |_| ()).unwrap_err();
2738        assert_eq!(err_w_large.reason, err::HCF.reason);
2739    }
2740
2741    #[test]
2742    fn err_write_read_overflow_index() {
2743        let (_dir, cfg) = tmp_cfg();
2744        let mmap = MemMap::<u64>::new(cfg).unwrap();
2745
2746        let err_r = mmap.read(usize::MAX, |_| ()).unwrap_err();
2747        assert_eq!(err_r.reason, err::INV.reason);
2748
2749        let err_w = mmap.write(usize::MAX, |_| ()).unwrap_err();
2750        assert_eq!(err_w.reason, err::INV.reason);
2751    }
2752
2753    mod read_exact {
2754        use super::*;
2755
2756        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
2757        #[repr(C)]
2758        struct TestNode {
2759            id: u64,
2760            flags: u64,
2761            keys: [u64; 2],
2762        }
2763
2764        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
2765        #[repr(C)]
2766        struct TestBadSizeTarget([u8; 12]);
2767
2768        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
2769        #[repr(C, align(16))]
2770        struct TestOveralignedTarget {
2771            _a: u64,
2772            _b: u64,
2773        }
2774
2775        #[test]
2776        fn ok_read_exact_single_and_multi_slot() {
2777            let (_dir, cfg) = tmp_cfg();
2778            let mmap = MemMap::<u64>::new(cfg).unwrap();
2779
2780            mmap.write(0, |v| *v = 0x1111).unwrap();
2781            mmap.write(1, |v| *v = 0x2222).unwrap();
2782            mmap.write(2, |v| *v = 0x3333).unwrap();
2783            mmap.write(3, |v| *v = 0x4444).unwrap();
2784
2785            let single = mmap.read_exact(0, |val: &u64| *val).unwrap();
2786            assert_eq!(single, 0x1111);
2787
2788            let node = mmap.read_exact(0, |n: &TestNode| *n).unwrap();
2789            assert_eq!(node, TestNode { id: 0x1111, flags: 0x2222, keys: [0x3333, 0x4444] });
2790
2791            let keys = mmap.read_exact(2, |k: &[u64; 2]| *k).unwrap();
2792            assert_eq!(keys, [0x3333, 0x4444]);
2793
2794            let flag_is_set = mmap.read_exact(0, |n: &TestNode| n.flags == 0x2222).unwrap();
2795            assert!(flag_is_set);
2796        }
2797
2798        #[test]
2799        fn err_read_exact_invariants() {
2800            let (_dir, cfg) = tmp_cfg();
2801            let mmap = MemMap::<u64>::new(cfg).unwrap();
2802
2803            let err_zro = mmap.read_exact::<(), _, _>(0, |_| ()).unwrap_err();
2804            assert_eq!(err_zro.reason, err::ZRO.reason);
2805
2806            let err_sze = mmap.read_exact::<TestBadSizeTarget, _, _>(0, |_| ()).unwrap_err();
2807            assert_eq!(err_sze.reason, err::SZE.reason);
2808
2809            let err_aln = mmap.read_exact::<TestOveralignedTarget, _, _>(0, |_| ()).unwrap_err();
2810            assert_eq!(err_aln.reason, err::ALN.reason);
2811
2812            let err_inv = mmap.read_exact::<u64, _, _>(usize::MAX, |_| ()).unwrap_err();
2813            assert_eq!(err_inv.reason, err::INV.reason);
2814
2815            let err_inv_add =
2816                mmap.read_exact::<TestNode, _, _>(usize::MAX - 1, |_| ()).unwrap_err();
2817            assert_eq!(err_inv_add.reason, err::INV.reason);
2818
2819            let err_hcf_start = mmap.read_exact::<u64, _, _>(INIT_T, |_| ()).unwrap_err();
2820            assert_eq!(err_hcf_start.reason, err::HCF.reason);
2821
2822            let err_hcf_end = mmap.read_exact::<TestNode, _, _>(INIT_T - 2, |_| ()).unwrap_err();
2823            assert_eq!(err_hcf_end.reason, err::HCF.reason);
2824        }
2825
2826        #[test]
2827        fn ok_read_exact_concurrent_readers() {
2828            let (_dir, cfg) = tmp_cfg();
2829            let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
2830
2831            mmap.write(0, |v| *v = 0xCAFE).unwrap();
2832            mmap.write(1, |v| *v = 0xBABE).unwrap();
2833            mmap.write(2, |v| *v = 0x1234).unwrap();
2834            mmap.write(3, |v| *v = 0x5678).unwrap();
2835
2836            let barrier = Arc::new(std::sync::Barrier::new(4));
2837            let mut handles = Vec::new();
2838
2839            for _ in 0..4 {
2840                let mmap_clone = Arc::clone(&mmap);
2841                let barrier_clone = Arc::clone(&barrier);
2842                handles.push(std::thread::spawn(move || {
2843                    barrier_clone.wait();
2844                    for _ in 0..0x1F4 {
2845                        let id = mmap_clone.read_exact(0, |n: &TestNode| n.id).unwrap();
2846                        assert_eq!(id, 0xCAFE);
2847                        let k1 = mmap_clone.read_exact(0, |n: &TestNode| n.keys[1]).unwrap();
2848                        assert_eq!(k1, 0x5678);
2849                    }
2850                }));
2851            }
2852
2853            for h in handles {
2854                h.join().unwrap();
2855            }
2856        }
2857
2858        #[test]
2859        fn ok_read_exact_concurrent_writers_isolation() {
2860            let (_dir, cfg) = tmp_cfg();
2861            let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
2862
2863            let barrier = Arc::new(std::sync::Barrier::new(5));
2864            let mut handles = Vec::new();
2865
2866            {
2867                let mmap_clone = Arc::clone(&mmap);
2868                let barrier_clone = Arc::clone(&barrier);
2869                handles.push(std::thread::spawn(move || {
2870                    barrier_clone.wait();
2871                    for i in 0..0x1F4 {
2872                        let v = i as u64;
2873                        let _ = mmap_clone.write(0, |val| *val = v).unwrap();
2874                        let _ = mmap_clone.write(1, |val| *val = v).unwrap();
2875                    }
2876                }));
2877            }
2878
2879            for _ in 0..4 {
2880                let mmap_clone = Arc::clone(&mmap);
2881                let barrier_clone = Arc::clone(&barrier);
2882                handles.push(std::thread::spawn(move || {
2883                    barrier_clone.wait();
2884                    for _ in 0..0x1F4 {
2885                        mmap_clone
2886                            .read_exact(0, |pair: &[u64; 2]| {
2887                                assert!(pair[0] <= 0x1F4 && pair[1] <= 0x1F4);
2888                            })
2889                            .unwrap();
2890                    }
2891                }));
2892            }
2893
2894            for h in handles {
2895                h.join().unwrap();
2896            }
2897        }
2898
2899        #[test]
2900        fn ok_read_exact_unwind_releases_locks() {
2901            let (_dir, cfg) = tmp_cfg();
2902            let mmap = MemMap::<u64>::new(cfg).unwrap();
2903
2904            mmap.write(0, |v| *v = 0x1111).unwrap();
2905            mmap.write(1, |v| *v = 0x2222).unwrap();
2906
2907            let panic_res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2908                let _ = mmap.read_exact(0, |_pair: &[u64; 2]| {
2909                    panic!("intentional panic inside read_exact callback");
2910                });
2911            }));
2912            assert!(panic_res.is_err());
2913
2914            let (ticket, _) = mmap.write(0, |v| *v = 0x9999).unwrap();
2915            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x9999);
2916            assert_eq!(ticket.epoch(), 3);
2917
2918            let (ticket2, _) = mmap.write(1, |v| *v = 0x8888).unwrap();
2919            assert_eq!(mmap.read(1, |v| *v).unwrap(), 0x8888);
2920            assert_eq!(ticket2.epoch(), 4);
2921        }
2922    }
2923
2924    mod write_exact {
2925        use super::*;
2926
2927        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
2928        #[repr(C)]
2929        struct TestNode {
2930            id: u64,
2931            flags: u64,
2932            keys: [u64; 2],
2933        }
2934
2935        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
2936        #[repr(C)]
2937        struct TestBadSizeTarget([u8; 12]);
2938
2939        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
2940        #[repr(C, align(16))]
2941        struct TestOveralignedTarget {
2942            _a: u64,
2943            _b: u64,
2944        }
2945
2946        #[test]
2947        fn ok_write_exact_single_and_multi_slot() {
2948            let (_dir, cfg) = tmp_cfg();
2949            let mmap = MemMap::<u64>::new(cfg).unwrap();
2950
2951            let (ticket_single, res) = mmap
2952                .write_exact(0, |val: &mut u64| {
2953                    *val = 0xAAAA;
2954                    42u32
2955                })
2956                .unwrap();
2957            assert_eq!(res, 42);
2958            assert_eq!(ticket_single.epoch(), 1);
2959            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0xAAAA);
2960
2961            let (ticket_multi, _) = mmap
2962                .write_exact(0, |node: &mut TestNode| {
2963                    node.id = 0x1111;
2964                    node.flags = 0x2222;
2965                    node.keys = [0x3333, 0x4444];
2966                })
2967                .unwrap();
2968            assert_eq!(ticket_multi.epoch(), 2);
2969            assert_eq!(
2970                mmap.read_exact(0, |n: &TestNode| *n).unwrap(),
2971                TestNode { id: 0x1111, flags: 0x2222, keys: [0x3333, 0x4444] }
2972            );
2973
2974            let (ticket_partial, _) = mmap
2975                .write_exact(2, |keys: &mut [u64; 2]| {
2976                    keys[0] = 0x5555;
2977                    keys[1] = 0x6666;
2978                })
2979                .unwrap();
2980            assert_eq!(ticket_partial.epoch(), 3);
2981            assert_eq!(mmap.read(2, |v| *v).unwrap(), 0x5555);
2982            assert_eq!(mmap.read(3, |v| *v).unwrap(), 0x6666);
2983        }
2984
2985        #[test]
2986        fn err_write_exact_invariants() {
2987            let (_dir, cfg) = tmp_cfg();
2988            let mmap = MemMap::<u64>::new(cfg).unwrap();
2989
2990            let err_zro = mmap.write_exact::<(), _, _>(0, |_| ()).unwrap_err();
2991            assert_eq!(err_zro.reason, err::ZRO.reason);
2992
2993            let err_sze = mmap.write_exact::<TestBadSizeTarget, _, _>(0, |_| ()).unwrap_err();
2994            assert_eq!(err_sze.reason, err::SZE.reason);
2995
2996            let err_aln = mmap.write_exact::<TestOveralignedTarget, _, _>(0, |_| ()).unwrap_err();
2997            assert_eq!(err_aln.reason, err::ALN.reason);
2998
2999            let err_inv = mmap.write_exact::<u64, _, _>(usize::MAX, |_| ()).unwrap_err();
3000            assert_eq!(err_inv.reason, err::INV.reason);
3001
3002            let err_inv_add =
3003                mmap.write_exact::<TestNode, _, _>(usize::MAX - 1, |_| ()).unwrap_err();
3004            assert_eq!(err_inv_add.reason, err::INV.reason);
3005
3006            let err_hcf_start = mmap.write_exact::<u64, _, _>(INIT_T, |_| ()).unwrap_err();
3007            assert_eq!(err_hcf_start.reason, err::HCF.reason);
3008
3009            let err_hcf_end = mmap.write_exact::<TestNode, _, _>(INIT_T - 2, |_| ()).unwrap_err();
3010            assert_eq!(err_hcf_end.reason, err::HCF.reason);
3011        }
3012
3013        #[test]
3014        fn ok_write_exact_concurrent_writers() {
3015            let (_dir, cfg) = tmp_cfg();
3016            let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3017
3018            let barrier = Arc::new(std::sync::Barrier::new(2));
3019            let mut handles = Vec::new();
3020
3021            {
3022                let mmap_clone = Arc::clone(&mmap);
3023                let barrier_clone = Arc::clone(&barrier);
3024                handles.push(std::thread::spawn(move || {
3025                    barrier_clone.wait();
3026                    for i in 0..0x1F4 {
3027                        let _ = mmap_clone
3028                            .write_exact(0, |node: &mut TestNode| {
3029                                node.id = i as u64;
3030                                node.flags = (i as u64) + 1;
3031                                node.keys = [(i as u64) + 2, (i as u64) + 3];
3032                            })
3033                            .unwrap();
3034                    }
3035                }));
3036            }
3037
3038            {
3039                let mmap_clone = Arc::clone(&mmap);
3040                let barrier_clone = Arc::clone(&barrier);
3041                handles.push(std::thread::spawn(move || {
3042                    barrier_clone.wait();
3043                    for i in 0..0x1F4 {
3044                        let _ = mmap_clone
3045                            .write_exact(4, |node: &mut TestNode| {
3046                                node.id = (i as u64) * 10;
3047                                node.flags = (i as u64) * 10 + 1;
3048                                node.keys = [(i as u64) * 10 + 2, (i as u64) * 10 + 3];
3049                            })
3050                            .unwrap();
3051                    }
3052                }));
3053            }
3054
3055            for h in handles {
3056                h.join().unwrap();
3057            }
3058
3059            let node0 = mmap.read_exact(0, |n: &TestNode| *n).unwrap();
3060            assert_eq!(node0.id, 0x1F3);
3061            assert_eq!(node0.flags, 0x1F4);
3062            assert_eq!(node0.keys, [0x1F5, 0x1F6]);
3063
3064            let node1 = mmap.read_exact(4, |n: &TestNode| *n).unwrap();
3065            assert_eq!(node1.id, 0x1F3 * 10);
3066            assert_eq!(node1.flags, 0x1F3 * 10 + 1);
3067            assert_eq!(node1.keys, [0x1F3 * 10 + 2, 0x1F3 * 10 + 3]);
3068        }
3069
3070        #[test]
3071        fn ok_write_exact_concurrent_readers_writers_isolation() {
3072            let (_dir, cfg) = tmp_cfg();
3073            let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3074
3075            let barrier = Arc::new(std::sync::Barrier::new(5));
3076            let mut handles = Vec::new();
3077
3078            {
3079                let mmap_clone = Arc::clone(&mmap);
3080                let barrier_clone = Arc::clone(&barrier);
3081                handles.push(std::thread::spawn(move || {
3082                    barrier_clone.wait();
3083                    for i in 0..0x1F4 {
3084                        let v = i as u64;
3085                        let _ = mmap_clone
3086                            .write_exact(0, |pair: &mut [u64; 2]| {
3087                                pair[0] = v;
3088                                pair[1] = v;
3089                            })
3090                            .unwrap();
3091                    }
3092                }));
3093            }
3094
3095            for _ in 0..4 {
3096                let mmap_clone = Arc::clone(&mmap);
3097                let barrier_clone = Arc::clone(&barrier);
3098                handles.push(std::thread::spawn(move || {
3099                    barrier_clone.wait();
3100                    for _ in 0..0x1F4 {
3101                        mmap_clone
3102                            .read_exact(0, |pair: &[u64; 2]| {
3103                                assert_eq!(pair[0], pair[1]);
3104                            })
3105                            .unwrap();
3106                    }
3107                }));
3108            }
3109
3110            for h in handles {
3111                h.join().unwrap();
3112            }
3113        }
3114
3115        #[test]
3116        fn ok_write_exact_unwind_releases_locks() {
3117            let (_dir, cfg) = tmp_cfg();
3118            let mmap = MemMap::<u64>::new(cfg).unwrap();
3119
3120            let panic_res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3121                let _ = mmap.write_exact(0, |_pair: &mut [u64; 2]| {
3122                    panic!("intentional panic inside write_exact callback");
3123                });
3124            }));
3125            assert!(panic_res.is_err());
3126
3127            let (ticket, _) = mmap.write(0, |v| *v = 0x9999).unwrap();
3128            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x9999);
3129            assert_eq!(ticket.epoch(), 1);
3130
3131            let (ticket2, _) = mmap.write(1, |v| *v = 0x8888).unwrap();
3132            assert_eq!(mmap.read(1, |v| *v).unwrap(), 0x8888);
3133            assert_eq!(ticket2.epoch(), 2);
3134        }
3135
3136        #[test]
3137        fn ok_write_exact_durability() {
3138            let (_dir, cfg) = tmp_cfg();
3139            {
3140                let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
3141                let (ticket, _) = mmap
3142                    .write_exact(0, |node: &mut TestNode| {
3143                        node.id = 0xDEAD;
3144                        node.flags = 0xBEEF;
3145                        node.keys = [0xCAFE, 0xBABE];
3146                    })
3147                    .unwrap();
3148                mmap.inner.sync_internal().unwrap();
3149                assert_eq!(ticket.epoch(), 1);
3150            }
3151
3152            let reopened = MemMap::<u64>::open(cfg).unwrap();
3153            assert_eq!(
3154                reopened.read_exact(0, |n: &TestNode| *n).unwrap(),
3155                TestNode { id: 0xDEAD, flags: 0xBEEF, keys: [0xCAFE, 0xBABE] }
3156            );
3157        }
3158    }
3159
3160    #[test]
3161    fn ok_durability_across_reopen() {
3162        let (_dir, cfg) = tmp_cfg();
3163        {
3164            let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
3165            mmap.write(1, |v| *v = 0xCAFE_BABE_1234_5678).unwrap();
3166            mmap.inner.sync_internal().unwrap();
3167        }
3168
3169        let reopened = MemMap::<u64>::open(cfg).unwrap();
3170        assert_eq!(reopened.read(1, |v| *v).unwrap(), 0xCAFE_BABE_1234_5678);
3171    }
3172
3173    #[test]
3174    fn ok_background_sync_advances_epoch() {
3175        let (_dir, mut cfg) = tmp_cfg();
3176        cfg.sync_interval = Some(Duration::from_millis(0x19));
3177
3178        let mmap = MemMap::<u64>::new(cfg).unwrap();
3179        let (ticket, _) = mmap.write(0, |v| *v = 0x1234).unwrap();
3180        let epoch = ticket.wait().unwrap();
3181
3182        assert_eq!(epoch, ticket.epoch());
3183        assert!(ticket.is_durable());
3184        assert!(mmap.durable_epoch() >= ticket.epoch());
3185    }
3186
3187    #[test]
3188    fn ok_ticket_force_wakes_bg_worker() {
3189        let (_dir, mut cfg) = tmp_cfg();
3190        // Long interval to prove force wakes up immediately
3191        cfg.sync_interval = Some(Duration::from_secs(0x0A));
3192
3193        let mmap = MemMap::<u64>::new(cfg).unwrap();
3194        let (ticket, _) = mmap.write(0, |v| *v = 0xABCD).unwrap();
3195        assert!(!ticket.is_durable());
3196
3197        let start = std::time::Instant::now();
3198        let epoch = ticket.force().unwrap();
3199        let elapsed = start.elapsed();
3200
3201        assert_eq!(epoch, ticket.epoch());
3202        assert!(ticket.is_durable());
3203        assert!(elapsed < Duration::from_secs(2));
3204    }
3205
3206    #[test]
3207    fn ok_drop_persists_unflushed_writes() {
3208        let (_dir, mut cfg) = tmp_cfg();
3209        cfg.sync_interval = Some(Duration::from_secs(0x1E));
3210
3211        {
3212            let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
3213            let (_ticket, _) = mmap.write(0, |v| *v = 0xBEEF_CAFE).unwrap();
3214            drop(mmap);
3215        }
3216
3217        let reopened = MemMap::<u64>::open(cfg).unwrap();
3218        assert_eq!(reopened.read(0, |v| *v).unwrap(), 0xBEEF_CAFE);
3219    }
3220
3221    #[test]
3222    fn ok_concurrent_slot_locking() {
3223        let (_dir, mut cfg) = tmp_cfg();
3224        cfg.sync_interval = Some(Duration::from_secs(0x3C));
3225
3226        let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3227        let barrier = Arc::new(std::sync::Barrier::new(8));
3228        let mut handles = Vec::new();
3229
3230        // 4 writer threads, each writing to index 0 repeatedly
3231        for t in 0..4 {
3232            let mmap_clone = Arc::clone(&mmap);
3233            let barrier_clone = Arc::clone(&barrier);
3234            handles.push(std::thread::spawn(move || {
3235                barrier_clone.wait();
3236                for i in 0..0x1F4 {
3237                    let val = ((t + 1) as u64) << 0x20 | (i as u64);
3238                    let _ = mmap_clone.write(0, |slot| *slot = val).unwrap();
3239                }
3240            }));
3241        }
3242
3243        // 4 reader threads, reading from index 0 repeatedly
3244        for _ in 0..4 {
3245            let mmap_clone = Arc::clone(&mmap);
3246            let barrier_clone = Arc::clone(&barrier);
3247            handles.push(std::thread::spawn(move || {
3248                barrier_clone.wait();
3249                for _ in 0..0x3E8 {
3250                    let val = mmap_clone.read(0, |v| *v).unwrap();
3251                    let thread_id = (val >> 0x20) as usize;
3252                    assert!(thread_id <= 4, "read value must be valid without torn reads");
3253                }
3254            }));
3255        }
3256
3257        for h in handles {
3258            h.join().unwrap();
3259        }
3260
3261        // Parallel reads across different and same indices
3262        let barrier2 = Arc::new(std::sync::Barrier::new(4));
3263        let mut read_handles = Vec::new();
3264        for _ in 0..4 {
3265            let mmap_clone = Arc::clone(&mmap);
3266            let barrier_clone = Arc::clone(&barrier2);
3267            read_handles.push(std::thread::spawn(move || {
3268                barrier_clone.wait();
3269                for _ in 0..0x1F4 {
3270                    mmap_clone.read(0, |_| ()).unwrap();
3271                }
3272            }));
3273        }
3274
3275        for h in read_handles {
3276            h.join().unwrap();
3277        }
3278    }
3279
3280    #[test]
3281    fn err_concurrent_new_same_path() {
3282        let (_dir, cfg) = tmp_cfg();
3283        let barrier = Arc::new(std::sync::Barrier::new(2));
3284
3285        let t1 = {
3286            let cfg = cfg.clone();
3287            let b = Arc::clone(&barrier);
3288            std::thread::spawn(move || {
3289                b.wait();
3290                MemMap::<u64>::new(cfg)
3291            })
3292        };
3293
3294        let t2 = {
3295            let b = Arc::clone(&barrier);
3296            std::thread::spawn(move || {
3297                b.wait();
3298                MemMap::<u64>::new(cfg)
3299            })
3300        };
3301
3302        let r1 = t1.join().unwrap();
3303        let r2 = t2.join().unwrap();
3304
3305        match (r1, r2) {
3306            (Ok(m), Err(e)) | (Err(e), Ok(m)) => {
3307                assert_eq!(e.reason, err::EXS.reason);
3308                assert!(m.delete().is_ok());
3309            }
3310            (Ok(_), Ok(_)) => {
3311                panic!("Concurrent MemMap::new on identical path must not both succeed");
3312            }
3313            (Err(e1), Err(e2)) => {
3314                panic!("Unexpected error on both threads: {:?}, {:?}", e1, e2);
3315            }
3316        }
3317    }
3318
3319    #[test]
3320    fn err_concurrent_open_same_path() {
3321        let (_dir, cfg) = tmp_cfg();
3322        {
3323            let m = MemMap::<u64>::new(cfg.clone()).unwrap();
3324            assert_eq!(m.length(), core::mem::size_of::<u64>() * INIT_T);
3325        }
3326
3327        let barrier = Arc::new(std::sync::Barrier::new(2));
3328
3329        let t1 = {
3330            let cfg = cfg.clone();
3331            let b = Arc::clone(&barrier);
3332            std::thread::spawn(move || {
3333                b.wait();
3334                MemMap::<u64>::open(cfg)
3335            })
3336        };
3337
3338        let t2 = {
3339            let b = Arc::clone(&barrier);
3340            std::thread::spawn(move || {
3341                b.wait();
3342                MemMap::<u64>::open(cfg)
3343            })
3344        };
3345
3346        let r1 = t1.join().unwrap();
3347        let r2 = t2.join().unwrap();
3348
3349        match (r1, r2) {
3350            (Ok(m), Err(e)) | (Err(e), Ok(m)) => {
3351                assert_eq!(e.domain, 0x08, "Error domain must be File (0x08)");
3352                assert_eq!(
3353                    e.reason, 0x1C,
3354                    "Concurrent open should yield LCK (0x1C) from kernel flock: {:?}",
3355                    e
3356                );
3357                assert!(m.delete().is_ok());
3358            }
3359            (Ok(_), Ok(_)) => {
3360                panic!("Exclusive advisory lock violation: both concurrent open calls succeeded");
3361            }
3362            (Err(e1), Err(e2)) => {
3363                panic!("Unexpected failure of both threads: {:?}, {:?}", e1, e2);
3364            }
3365        }
3366    }
3367
3368    #[test]
3369    fn ok_reopen_after_drop_releases_lock() {
3370        let (_dir, cfg) = tmp_cfg();
3371        let m1 = MemMap::<u64>::new(cfg.clone()).unwrap();
3372        m1.write(0, |v| *v = 0x1122_3344).unwrap();
3373        drop(m1);
3374
3375        let m2 = MemMap::<u64>::open(cfg).unwrap();
3376        assert_eq!(m2.read(0, |v| *v).unwrap(), 0x1122_3344);
3377        assert!(m2.delete().is_ok());
3378    }
3379
3380    #[test]
3381    fn ok_delete_and_recreate_cycle() {
3382        let (_dir, cfg) = tmp_cfg();
3383        for i in 0..4 {
3384            let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
3385            let val = (i as u64 + 1) * 0xAA55;
3386            mmap.write(0, |v| *v = val).unwrap();
3387            assert_eq!(mmap.read(0, |v| *v).unwrap(), val);
3388            assert!(mmap.delete().is_ok());
3389            assert!(!cfg.versioned_path().exists());
3390        }
3391    }
3392
3393    #[test]
3394    fn ok_concurrent_distinct_slots_no_contention() {
3395        let (_dir, mut cfg) = tmp_cfg();
3396        const THREADS: usize = 8;
3397        cfg.sync_interval = Some(Duration::from_secs(0x3C));
3398
3399        let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3400        let barrier = Arc::new(std::sync::Barrier::new(THREADS));
3401        let mut handles = Vec::with_capacity(THREADS);
3402
3403        for t in 0..THREADS {
3404            let mmap_clone = Arc::clone(&mmap);
3405            let barrier_clone = Arc::clone(&barrier);
3406            handles.push(std::thread::spawn(move || {
3407                barrier_clone.wait();
3408                for i in 0..0x3E8 {
3409                    let val = (t as u64) << 0x20 | (i as u64);
3410                    let _ = mmap_clone.write(t, |slot| *slot = val).unwrap();
3411                    let read_val = mmap_clone.read(t, |v| *v).unwrap();
3412                    assert_eq!(read_val >> 0x20, t as u64);
3413                }
3414            }));
3415        }
3416
3417        for h in handles {
3418            h.join().unwrap();
3419        }
3420
3421        for t in 0..THREADS {
3422            let val = mmap.read(t, |v| *v).unwrap();
3423            assert_eq!(val >> 0x20, t as u64);
3424        }
3425    }
3426
3427    #[test]
3428    fn ok_stress_interleaved_reads_writes() {
3429        let (_dir, mut cfg) = tmp_cfg();
3430        const NUM_SLOTS: usize = 0x10;
3431        const NUM_THREADS: usize = 8;
3432        cfg.sync_interval = Some(Duration::from_millis(0x32));
3433
3434        let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3435        let barrier = Arc::new(std::sync::Barrier::new(NUM_THREADS));
3436        let mut handles = Vec::with_capacity(NUM_THREADS);
3437
3438        for t in 0..NUM_THREADS {
3439            let mmap_clone = Arc::clone(&mmap);
3440            let barrier_clone = Arc::clone(&barrier);
3441            handles.push(std::thread::spawn(move || {
3442                barrier_clone.wait();
3443                for i in 0..0x5DC {
3444                    let slot = (t + i) % NUM_SLOTS;
3445                    if i % 2 == 0 {
3446                        let val = ((t + 1) as u64) << 0x20 | (i as u64);
3447                        let _ = mmap_clone.write(slot, |s| *s = val).unwrap();
3448                    } else {
3449                        mmap_clone.read(slot, |_| ()).unwrap();
3450                    }
3451                }
3452            }));
3453        }
3454
3455        for h in handles {
3456            h.join().unwrap();
3457        }
3458
3459        assert!(mmap.inner.sync_internal().is_ok());
3460    }
3461
3462    #[test]
3463    fn ok_concurrent_writers_with_background_sync() {
3464        let (_dir, mut cfg) = tmp_cfg();
3465        cfg.sync_interval = Some(Duration::from_millis(5));
3466
3467        let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3468        let barrier = Arc::new(std::sync::Barrier::new(4));
3469        let mut handles = Vec::new();
3470
3471        for t in 0..4 {
3472            let mmap_clone = Arc::clone(&mmap);
3473            let barrier_clone = Arc::clone(&barrier);
3474            handles.push(std::thread::spawn(move || {
3475                barrier_clone.wait();
3476                let mut tickets = Vec::with_capacity(0x32);
3477                for i in 0..0x32 {
3478                    let val = (t as u64) * 0x3E8 + i as u64;
3479                    tickets.push(mmap_clone.write(t, |slot| *slot = val).unwrap().0);
3480                    std::thread::sleep(Duration::from_millis(1));
3481                }
3482                for ticket in tickets {
3483                    let _ = ticket.wait().unwrap();
3484                    assert!(ticket.is_durable());
3485                }
3486            }));
3487        }
3488
3489        for h in handles {
3490            h.join().unwrap();
3491        }
3492
3493        assert!(mmap.durable_epoch() >= mmap.inner.completion.read_current_epoch());
3494    }
3495
3496    #[test]
3497    fn ok_concurrent_ticket_force_stress() {
3498        let (_dir, mut cfg) = tmp_cfg();
3499        cfg.sync_interval = Some(Duration::from_secs(0x1E));
3500
3501        let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3502        let barrier = Arc::new(std::sync::Barrier::new(4));
3503        let mut handles = Vec::new();
3504
3505        for t in 0..4 {
3506            let mmap_clone = Arc::clone(&mmap);
3507            let barrier_clone = Arc::clone(&barrier);
3508            handles.push(std::thread::spawn(move || {
3509                barrier_clone.wait();
3510                for i in 0..0x14 {
3511                    let val = (t as u64) * 0x64 + i as u64;
3512                    let (ticket, _) = mmap_clone.write(t, |slot| *slot = val).unwrap();
3513                    let durable_epoch = ticket.force().unwrap();
3514                    assert!(ticket.is_durable());
3515                    assert!(durable_epoch >= ticket.epoch());
3516                }
3517            }));
3518        }
3519
3520        for h in handles {
3521            h.join().unwrap();
3522        }
3523    }
3524
3525    #[test]
3526    fn ok_exact_boundary_indices() {
3527        let (_dir, cfg) = tmp_cfg();
3528        let mmap = MemMap::<u64>::new(cfg).unwrap();
3529
3530        assert!(mmap.write(INIT_T - 1, |v| *v = 0xF00D).is_ok());
3531        assert_eq!(mmap.read(INIT_T - 1, |v| *v).unwrap(), 0xF00D);
3532
3533        assert_eq!(mmap.write(INIT_T, |_| ()).unwrap_err().reason, err::HCF.reason);
3534        assert_eq!(mmap.read(INIT_T, |_| ()).unwrap_err().reason, err::HCF.reason);
3535    }
3536
3537    #[test]
3538    fn ok_large_slot_count_initialization() {
3539        let (_dir, mut cfg) = tmp_cfg();
3540        const LARGE_COUNT: usize = 0x2800;
3541        cfg.initial_t = LARGE_COUNT;
3542
3543        let mmap = MemMap::<u64>::new(cfg).unwrap();
3544        assert_eq!(mmap.length(), LARGE_COUNT * core::mem::size_of::<u64>());
3545
3546        assert!(mmap.write(0, |v| *v = 1).is_ok());
3547        assert!(mmap.write(0x1388, |v| *v = 0x1388).is_ok());
3548        assert!(mmap.write(LARGE_COUNT - 1, |v| *v = 0x27FF).is_ok());
3549
3550        assert_eq!(mmap.read(0, |v| *v).unwrap(), 1);
3551        assert_eq!(mmap.read(0x1388, |v| *v).unwrap(), 0x1388);
3552        assert_eq!(mmap.read(LARGE_COUNT - 1, |v| *v).unwrap(), 0x27FF);
3553    }
3554
3555    #[test]
3556    fn ok_new_with_sync_interval_none() {
3557        let (_dir, mut cfg) = tmp_cfg();
3558        cfg.sync_interval = None;
3559
3560        let mmap = MemMap::<u64>::new(cfg).unwrap();
3561        assert!(mmap.bg_thread.is_none());
3562
3563        let (ticket, _) = mmap.write(0, |v| *v = 0x55AA).unwrap();
3564        assert!(!ticket.is_durable());
3565
3566        mmap.sync().unwrap();
3567        assert!(ticket.is_durable());
3568        assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x55AA);
3569    }
3570
3571    #[test]
3572    fn ok_ticket_force_with_sync_interval_none() {
3573        let (_dir, mut cfg) = tmp_cfg();
3574        cfg.sync_interval = None;
3575
3576        let mmap = MemMap::<u64>::new(cfg).unwrap();
3577        assert!(mmap.bg_thread.is_none());
3578
3579        let (ticket, _) = mmap.write(1, |v| *v = 0x1122).unwrap();
3580        assert!(!ticket.is_durable());
3581
3582        let epoch = ticket.force().unwrap();
3583        assert_eq!(epoch, ticket.epoch());
3584        assert!(ticket.is_durable());
3585        assert!(mmap.durable_epoch() >= ticket.epoch());
3586    }
3587
3588    #[test]
3589    fn ok_drop_with_sync_interval_none() {
3590        let (_dir, mut cfg) = tmp_cfg();
3591        cfg.sync_interval = None;
3592
3593        {
3594            let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
3595            assert!(mmap.bg_thread.is_none());
3596            let (_ticket, _) = mmap.write(0, |v| *v = 0xCAFE_F00D).unwrap();
3597            drop(mmap);
3598        }
3599
3600        let reopened = MemMap::<u64>::open(cfg).unwrap();
3601        assert_eq!(reopened.read(0, |v| *v).unwrap(), 0xCAFE_F00D);
3602    }
3603
3604    #[test]
3605    fn ok_bg_worker_parks_when_idle_and_unparks_on_write() {
3606        let (_dir, mut cfg) = tmp_cfg();
3607        cfg.sync_interval = Some(Duration::from_millis(0x0F));
3608
3609        let mmap = MemMap::<u64>::new(cfg).unwrap();
3610
3611        // Initially without writes, the worker thread should park within a short delay
3612        let start = std::time::Instant::now();
3613        while !mmap.is_parked() && start.elapsed() < Duration::from_secs(1) {
3614            std::thread::sleep(Duration::from_millis(5));
3615        }
3616        assert!(mmap.is_parked());
3617
3618        // First write should unpark the worker
3619        let (ticket, _) = mmap.write(0, |v| *v = 0x9999).unwrap();
3620        assert!(!mmap.is_parked());
3621
3622        // Ticket should become durable via the unparked background thread
3623        let epoch = ticket.wait().unwrap();
3624        assert_eq!(epoch, ticket.epoch());
3625        assert!(ticket.is_durable());
3626        assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x9999);
3627
3628        // After flushing, worker should re-park when no new writes arrive
3629        let start = std::time::Instant::now();
3630        while !mmap.is_parked() && start.elapsed() < Duration::from_secs(1) {
3631            std::thread::sleep(Duration::from_millis(5));
3632        }
3633        assert!(mmap.is_parked());
3634    }
3635
3636    #[test]
3637    fn ok_batched_writes_while_active_remain_unparked() {
3638        let (_dir, mut cfg) = tmp_cfg();
3639        cfg.sync_interval = Some(Duration::from_millis(0x1E));
3640
3641        let mmap = MemMap::<u64>::new(cfg).unwrap();
3642
3643        // Wait until parked
3644        let start = std::time::Instant::now();
3645        while !mmap.is_parked() && start.elapsed() < Duration::from_secs(1) {
3646            std::thread::sleep(Duration::from_millis(5));
3647        }
3648        assert!(mmap.is_parked());
3649
3650        // First write unparks
3651        let (t1, _) = mmap.write(0, |v| *v = 0x1111).unwrap();
3652        assert!(!mmap.is_parked());
3653
3654        // Subsequent rapid writes find worker already unparked (is_parked == false)
3655        let (t2, _) = mmap.write(1, |v| *v = 0x2222).unwrap();
3656        let (t3, _) = mmap.write(2, |v| *v = 0x3333).unwrap();
3657        assert!(!mmap.is_parked());
3658
3659        t1.wait().unwrap();
3660        t2.wait().unwrap();
3661        t3.wait().unwrap();
3662
3663        assert!(t1.is_durable());
3664        assert!(t2.is_durable());
3665        assert!(t3.is_durable());
3666        assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x1111);
3667        assert_eq!(mmap.read(1, |v| *v).unwrap(), 0x2222);
3668        assert_eq!(mmap.read(2, |v| *v).unwrap(), 0x3333);
3669    }
3670
3671    #[test]
3672    fn ok_concurrent_stress_park_unpark_no_lost_wakeups() {
3673        let (_dir, mut cfg) = tmp_cfg();
3674        cfg.sync_interval = Some(Duration::from_millis(5));
3675
3676        let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3677        const THREADS: usize = 4;
3678        const WRITES_PER_THREAD: usize = 0x19;
3679        let mut handles = Vec::with_capacity(THREADS);
3680
3681        for t in 0..THREADS {
3682            let mmap_clone = Arc::clone(&mmap);
3683            handles.push(std::thread::spawn(move || {
3684                let mut tickets = Vec::with_capacity(WRITES_PER_THREAD);
3685                for i in 0..WRITES_PER_THREAD {
3686                    let val = (t as u64) << 0x20 | (i as u64);
3687                    let slot = t;
3688                    let (ticket, _) = mmap_clone.write(slot, |s| *s = val).unwrap();
3689                    tickets.push(ticket);
3690
3691                    // Alternate between short sleep (allowing worker to park) and rapid writes
3692                    if i % 5 == 0 {
3693                        std::thread::sleep(Duration::from_millis(0x0F));
3694                    }
3695                }
3696
3697                for ticket in tickets {
3698                    ticket.wait().unwrap();
3699                    assert!(ticket.is_durable());
3700                }
3701            }));
3702        }
3703
3704        for h in handles {
3705            h.join().unwrap();
3706        }
3707
3708        assert!(mmap.durable_epoch() >= mmap.inner.completion.read_current_epoch());
3709    }
3710
3711    #[test]
3712    fn ok_spurious_wakeup_resumes_park_immediately() {
3713        let (_dir, mut cfg) = tmp_cfg();
3714        cfg.sync_interval = Some(Duration::from_secs(0x0A)); // Long interval
3715
3716        let mmap = MemMap::<u64>::new(cfg).unwrap();
3717
3718        // Wait until parked
3719        let start = std::time::Instant::now();
3720        while !mmap.is_parked() && start.elapsed() < Duration::from_secs(1) {
3721            std::thread::sleep(Duration::from_millis(5));
3722        }
3723        assert!(mmap.is_parked());
3724
3725        // Send spurious condvar notification without any writes
3726        mmap.inner.sync_condvar.notify_one();
3727        std::thread::sleep(Duration::from_millis(0x0A));
3728
3729        // Because no writes occurred, worker should detect spurious wakeup and re-park immediately rather than sleeping
3730        // for the 10-second interval
3731        let start = std::time::Instant::now();
3732        while !mmap.is_parked() && start.elapsed() < Duration::from_millis(0x1F4) {
3733            std::thread::sleep(Duration::from_millis(5));
3734        }
3735        assert!(mmap.is_parked());
3736    }
3737
3738    #[test]
3739    fn ok_delete_cleans_up_file_when_mmap_already_unmapped() {
3740        let (_dir, cfg) = tmp_cfg();
3741        let path = cfg.versioned_path();
3742        let mmap = MemMap::<u64>::new(cfg).unwrap();
3743        assert!(path.exists());
3744
3745        // Simulate mmap having been taken/unmapped prior to delete
3746        {
3747            let mut guard = mmap.inner.mmap.write().unwrap();
3748            let _ = guard.take();
3749        }
3750
3751        // delete should still successfully clean up the backing file on disk
3752        assert!(mmap.delete().is_ok());
3753        assert!(!path.exists());
3754    }
3755
3756    mod memmap_grow {
3757        use super::*;
3758
3759        #[test]
3760        fn ok_grow_updates_length() {
3761            let (_dir, cfg) = tmp_cfg();
3762            let mmap = MemMap::<u64>::new(cfg).unwrap();
3763            let initial_len = INIT_T * core::mem::size_of::<u64>();
3764            let page_size = mmap.inner.page_size.as_usize();
3765            assert_eq!(mmap.length(), initial_len);
3766
3767            mmap.grow(4).unwrap();
3768            assert_eq!(mmap.length(), initial_len + 4 * page_size);
3769        }
3770
3771        #[test]
3772        fn ok_grow_zero_count() {
3773            let (_dir, cfg) = tmp_cfg();
3774            let mmap = MemMap::<u64>::new(cfg).unwrap();
3775            let initial_len = mmap.length();
3776
3777            mmap.grow(0).unwrap();
3778            assert_eq!(mmap.length(), initial_len);
3779        }
3780
3781        #[test]
3782        fn ok_grow_extends_with_zero() {
3783            let (_dir, cfg) = tmp_cfg();
3784            let mmap = MemMap::<u64>::new(cfg).unwrap();
3785            let page_slots = mmap.inner.page_size.as_usize() / core::mem::size_of::<u64>();
3786            mmap.grow(1).unwrap();
3787
3788            for idx in INIT_T..(INIT_T + page_slots) {
3789                assert_eq!(mmap.read(idx, |v| *v).unwrap(), 0u64);
3790            }
3791        }
3792
3793        #[test]
3794        fn ok_grow_preserves_existing_data() {
3795            let (_dir, cfg) = tmp_cfg();
3796            let mmap = MemMap::<u64>::new(cfg).unwrap();
3797            let page_slots = mmap.inner.page_size.as_usize() / core::mem::size_of::<u64>();
3798
3799            for i in 0..INIT_T {
3800                mmap.write(i, |v| *v = 0xCAFE_0000 + i as u64).unwrap();
3801            }
3802
3803            mmap.grow(1).unwrap();
3804
3805            for i in 0..INIT_T {
3806                assert_eq!(mmap.read(i, |v| *v).unwrap(), 0xCAFE_0000 + i as u64);
3807            }
3808
3809            for i in INIT_T..(INIT_T + page_slots) {
3810                mmap.write(i, |v| *v = 0xBEEF_0000 + i as u64).unwrap();
3811                assert_eq!(mmap.read(i, |v| *v).unwrap(), 0xBEEF_0000 + i as u64);
3812            }
3813        }
3814
3815        #[test]
3816        fn ok_grow_syncs_dirty_pages_when_curr_epoch_gt_durable() {
3817            let (_dir, mut cfg) = tmp_cfg();
3818            cfg.sync_interval = None; // Manual sync mode
3819
3820            let mmap = MemMap::<u64>::new(cfg).unwrap();
3821            let (ticket, _) = mmap.write(1, |v| *v = 0x1234_5678u64).unwrap();
3822
3823            assert!(!ticket.is_durable());
3824            assert!(mmap.current_epoch() > mmap.durable_epoch());
3825
3826            // grow should trigger durability sync before re-mapping
3827            mmap.grow(2).unwrap();
3828
3829            assert!(ticket.is_durable());
3830            assert!(mmap.durable_epoch() >= ticket.epoch());
3831        }
3832
3833        #[test]
3834        fn ok_grow_when_already_durable() {
3835            let (_dir, mut cfg) = tmp_cfg();
3836            cfg.sync_interval = None;
3837
3838            let mmap = MemMap::<u64>::new(cfg).unwrap();
3839            let (ticket, _) = mmap.write(0, |v| *v = 0x9999u64).unwrap();
3840            mmap.sync().unwrap();
3841            assert!(ticket.is_durable());
3842            assert_eq!(mmap.current_epoch(), mmap.durable_epoch());
3843
3844            mmap.grow(2).unwrap();
3845            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x9999u64);
3846        }
3847
3848        #[test]
3849        fn err_grow_overflow_usize() {
3850            let (_dir, cfg) = tmp_cfg();
3851            let mmap = MemMap::<u64>::new(cfg).unwrap();
3852
3853            let err = mmap.grow(usize::MAX).unwrap_err();
3854            assert_eq!(err.reason, err::GRW.reason);
3855        }
3856
3857        #[test]
3858        fn ok_grow_multiple_times() {
3859            let (_dir, cfg) = tmp_cfg();
3860            let mmap = MemMap::<u64>::new(cfg).unwrap();
3861            let page_size = mmap.inner.page_size.as_usize();
3862
3863            mmap.write(0, |v| *v = 0x0Au64).unwrap();
3864            mmap.grow(2).unwrap();
3865            mmap.write(INIT_T, |v| *v = 0x14u64).unwrap();
3866            mmap.grow(3).unwrap();
3867            mmap.write(INIT_T + 2, |v| *v = 0x1Eu64).unwrap();
3868
3869            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x0Au64);
3870            assert_eq!(mmap.read(INIT_T, |v| *v).unwrap(), 0x14u64);
3871            assert_eq!(mmap.read(INIT_T + 2, |v| *v).unwrap(), 0x1Eu64);
3872            assert_eq!(mmap.length(), (INIT_T * core::mem::size_of::<u64>()) + 5 * page_size);
3873        }
3874
3875        #[test]
3876        fn ok_concurrent_grow_and_reads_writes() {
3877            let (_dir, mut cfg) = tmp_cfg();
3878            cfg.sync_interval = Some(Duration::from_millis(0x0A));
3879            let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
3880            let page_size = mmap.inner.page_size.as_usize();
3881
3882            let num_writers = 4;
3883            let mut handles = Vec::new();
3884
3885            for w in 0..num_writers {
3886                let m = Arc::clone(&mmap);
3887                handles.push(std::thread::spawn(move || {
3888                    for i in 0..0x32 {
3889                        let _ = m.write(w % INIT_T, |v| *v = (w * 0x3E8 + i) as u64);
3890                        m.read(w % INIT_T, |_| ()).unwrap();
3891                    }
3892                }));
3893            }
3894
3895            let m_grower = Arc::clone(&mmap);
3896            let grower_handle = std::thread::spawn(move || {
3897                for _ in 0..3 {
3898                    std::thread::sleep(Duration::from_millis(5));
3899                    m_grower.grow(2).unwrap();
3900                }
3901            });
3902
3903            for h in handles {
3904                h.join().unwrap();
3905            }
3906            grower_handle.join().unwrap();
3907
3908            assert_eq!(mmap.length(), (INIT_T * core::mem::size_of::<u64>()) + 6 * page_size);
3909        }
3910    }
3911
3912    mod thread_failure_lifecycle {
3913        use super::*;
3914
3915        #[test]
3916        fn err_new_failure_paths_spawn_no_sync_thread() {
3917            let (_dir, cfg) = tmp_cfg();
3918            // Pre-create file to force EXS error
3919            std::fs::write(cfg.versioned_path(), b"preexisting").unwrap();
3920
3921            let res = MemMap::<u64>::new(cfg);
3922            assert!(res.is_err());
3923            // No MemMap instance was created, and no background sync thread was spawned
3924        }
3925
3926        #[test]
3927        fn err_open_failure_paths_spawn_no_sync_thread() {
3928            let (_dir, mut cfg) = tmp_cfg();
3929            cfg.path = std::path::PathBuf::from("/non/existent/path/memmap.bin");
3930
3931            let res = MemMap::<u64>::open(cfg);
3932            assert!(res.is_err());
3933            // No MemMap instance was created, and no background sync thread was spawned
3934        }
3935
3936        #[test]
3937        fn ok_thread_shutdown_on_panic_unwinding() {
3938            let (_dir, cfg) = tmp_cfg();
3939            let thread_inner = Arc::new(atomic::AtomicBool::new(false));
3940            let thread_inner_clone = Arc::clone(&thread_inner);
3941
3942            // Spawn worker thread that panics with MemMap in scope
3943            let handle = std::thread::spawn(move || {
3944                let mmap = MemMap::<u64>::new(cfg).unwrap();
3945                let _inner = Arc::clone(&mmap.inner);
3946                thread_inner_clone.store(true, atomic::Ordering::Release);
3947                // Trigger panic while mmap is in scope
3948                panic!("intentional test panic to trigger Drop unwinding");
3949            });
3950
3951            assert!(handle.join().is_err());
3952            assert!(thread_inner.load(atomic::Ordering::Acquire));
3953            // Stack unwinding drops MemMap, setting shutdown = true and joining the background worker
3954        }
3955
3956        #[test]
3957        fn ok_thread_shutdown_in_delete_even_if_already_unmapped() {
3958            let (_dir, cfg) = tmp_cfg();
3959            let mmap = MemMap::<u64>::new(cfg).unwrap();
3960
3961            // Simulate mmap having been taken/unmapped prior to delete
3962            {
3963                let mut guard = mmap.inner.mmap.write().unwrap();
3964                let _ = guard.take();
3965            }
3966
3967            // delete shuts down and joins bg_thread before performing unmap and file deletion
3968            assert!(mmap.delete().is_ok());
3969        }
3970
3971        #[test]
3972        fn ok_grow_failure_keeps_thread_healthy() {
3973            let (_dir, cfg) = tmp_cfg();
3974            let mmap = MemMap::<u64>::new(cfg).unwrap();
3975
3976            assert!(mmap.grow(usize::MAX).is_err());
3977
3978            // The instance and background sync worker remain healthy
3979            let (ticket, _) = mmap.write(0, |v| *v = 0x2Au64).unwrap();
3980            mmap.sync().unwrap();
3981            assert!(ticket.is_durable());
3982            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x2Au64);
3983        }
3984    }
3985
3986    mod memmap_transactions {
3987        use super::*;
3988
3989        #[test]
3990        fn ok_single_slot_cas_success_and_conflict() {
3991            let (_dir, cfg) = tmp_cfg();
3992            let mmap = MemMap::<u64>::new(cfg).unwrap();
3993
3994            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0);
3995
3996            let t1 = mmap.compare_and_write(&0, &0x64, 0).unwrap();
3997            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x64);
3998            assert_eq!(mmap.current_epoch(), 1);
3999
4000            let err = mmap.compare_and_write(&0, &0xC8, 0).unwrap_err();
4001            assert_eq!(err.reason, err::CAS.reason);
4002            assert!(err.context.contains("slot data has changed"));
4003            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x64);
4004            assert_eq!(mmap.current_epoch(), 1); // Epoch does not advance on failure
4005
4006            let t2 = mmap.compare_and_write(&0x64, &0xC8, 0).unwrap();
4007            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0xC8);
4008            assert_eq!(mmap.current_epoch(), 2);
4009            assert!(t2.epoch() > t1.epoch());
4010        }
4011
4012        #[test]
4013        fn ok_tx_multiple_writes_single_epoch() {
4014            let (_dir, cfg) = tmp_cfg();
4015            let mmap = MemMap::<u64>::new(cfg).unwrap();
4016
4017            let epoch_before = mmap.current_epoch();
4018
4019            let mut tx = mmap.new_tx();
4020            assert!(tx.is_empty());
4021            tx.write(&0, &0x6F, 0).unwrap();
4022            tx.write(&0, &0xDE, 1).unwrap();
4023            tx.write(&0, &0x14D, 2).unwrap();
4024            assert_eq!(tx.len(), 3);
4025
4026            let ticket = tx.commit().unwrap();
4027            let epoch_after = mmap.current_epoch();
4028
4029            // Single epoch increment for all 3 writes
4030            assert_eq!(epoch_after, epoch_before + 1);
4031            assert_eq!(ticket.epoch(), epoch_after);
4032
4033            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x6F);
4034            assert_eq!(mmap.read(1, |v| *v).unwrap(), 0xDE);
4035            assert_eq!(mmap.read(2, |v| *v).unwrap(), 0x14D);
4036
4037            ticket.wait().unwrap();
4038            assert!(ticket.is_durable());
4039        }
4040
4041        #[test]
4042        fn err_tx_cas_conflict_aborts_all_with_zero_side_effects() {
4043            let (_dir, cfg) = tmp_cfg();
4044            let mmap = MemMap::<u64>::new(cfg).unwrap();
4045
4046            // Prepare slot 1 with a different value
4047            mmap.write(1, |v| *v = 0x3E7).unwrap();
4048            let epoch_before = mmap.current_epoch();
4049
4050            let mut tx = mmap.new_tx();
4051            tx.write(&0, &0x0A, 0).unwrap(); // Expected 0, currently 0 (would succeed)
4052            tx.write(&0, &0x14, 1).unwrap(); // Expected 0, currently 999 (will fail!)
4053            tx.write(&0, &0x1E, 2).unwrap(); // Expected 0, currently 0
4054
4055            let err = tx.commit().unwrap_err();
4056            assert_eq!(err.reason, err::CAS.reason);
4057            assert!(err.context.contains("state conflict at index 1"));
4058
4059            // Verify NONE of the slots were modified (all-or-nothing rollback)
4060            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0);
4061            assert_eq!(mmap.read(1, |v| *v).unwrap(), 0x3E7);
4062            assert_eq!(mmap.read(2, |v| *v).unwrap(), 0);
4063
4064            // Epoch must NOT have advanced
4065            assert_eq!(mmap.current_epoch(), epoch_before);
4066        }
4067
4068        #[test]
4069        fn ok_tx_retry_after_conflict() {
4070            let (_dir, cfg) = tmp_cfg();
4071            let mmap = MemMap::<u64>::new(cfg).unwrap();
4072
4073            // Simulate concurrent write modifying slot 1
4074            mmap.write(1, |v| *v = 0x2A).unwrap();
4075
4076            // First attempt assuming slot 1 is 0 fails
4077            let mut tx1 = mmap.new_tx();
4078            tx1.write(&0, &0x64, 0).unwrap();
4079            tx1.write(&0, &0xC8, 1).unwrap();
4080            assert_eq!(tx1.commit().unwrap_err().reason, err::CAS.reason);
4081
4082            // Caller observes current slot 1 value is 42, retries transaction with updated precondition
4083            let curr_val = mmap.read(1, |v| *v).unwrap();
4084            assert_eq!(curr_val, 0x2A);
4085
4086            let mut tx2 = mmap.new_tx();
4087            tx2.write(&0, &0x64, 0).unwrap();
4088            tx2.write(&0x2A, &0xC8, 1).unwrap();
4089            let ticket = tx2.commit().unwrap();
4090
4091            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x64);
4092            assert_eq!(mmap.read(1, |v| *v).unwrap(), 0xC8);
4093            ticket.wait().unwrap();
4094            assert!(ticket.is_durable());
4095        }
4096
4097        #[test]
4098        fn err_tx_out_of_order() {
4099            let (_dir, cfg) = tmp_cfg();
4100            let mmap = MemMap::<u64>::new(cfg).unwrap();
4101
4102            let mut tx = mmap.new_tx();
4103            tx.write(&0, &0x0A, 2).unwrap();
4104            let err = tx.write(&0, &0x14, 1).unwrap_err();
4105            assert_eq!(err.reason, err::HCF.reason);
4106            assert!(err.context.contains("strictly ordered"));
4107        }
4108
4109        #[test]
4110        fn err_tx_duplicate_index() {
4111            let (_dir, cfg) = tmp_cfg();
4112            let mmap = MemMap::<u64>::new(cfg).unwrap();
4113
4114            let mut tx = mmap.new_tx();
4115            tx.write(&0, &0x0A, 1).unwrap();
4116            let err = tx.write(&0, &0x14, 1).unwrap_err();
4117            assert_eq!(err.reason, err::HCF.reason);
4118            assert!(err.context.contains("strictly ordered"));
4119        }
4120
4121        #[test]
4122        fn err_tx_out_of_bounds() {
4123            let (_dir, cfg) = tmp_cfg();
4124            let mmap = MemMap::<u64>::new(cfg).unwrap();
4125
4126            let mut tx = mmap.new_tx();
4127            let err = tx.write(&0, &0x0A, INIT_T).unwrap_err();
4128            assert_eq!(err.reason, err::HCF.reason);
4129        }
4130
4131        #[test]
4132        fn err_tx_empty_commit() {
4133            let (_dir, cfg) = tmp_cfg();
4134            let mmap = MemMap::<u64>::new(cfg).unwrap();
4135
4136            let tx = mmap.new_tx();
4137            let err = tx.commit().unwrap_err();
4138            assert_eq!(err.reason, err::INV.reason);
4139            assert!(err.context.contains("empty transaction"));
4140        }
4141
4142        #[test]
4143        fn ok_tx_discard_on_drop() {
4144            let (_dir, cfg) = tmp_cfg();
4145            let mmap = MemMap::<u64>::new(cfg).unwrap();
4146
4147            let epoch_before = mmap.current_epoch();
4148            {
4149                let mut tx = mmap.new_tx();
4150                tx.write(&0, &0x309, 0).unwrap();
4151                tx.write(&0, &0x378, 1).unwrap();
4152                // tx dropped without calling commit()
4153            }
4154
4155            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0);
4156            assert_eq!(mmap.read(1, |v| *v).unwrap(), 0);
4157            assert_eq!(mmap.current_epoch(), epoch_before);
4158        }
4159
4160        #[test]
4161        fn ok_tx_concurrent_non_overlapping() {
4162            let (_dir, cfg) = tmp_cfg();
4163            let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
4164
4165            let mut handles = Vec::new();
4166            for thread_id in 0..4u64 {
4167                let m = mmap.clone();
4168                handles.push(std::thread::spawn(move || {
4169                    let base = (thread_id as usize) * 4;
4170                    let mut tx = m.new_tx();
4171                    tx.write(&0, &(thread_id * 0x0A + 1), base).unwrap();
4172                    tx.write(&0, &(thread_id * 0x0A + 2), base + 1).unwrap();
4173                    tx.write(&0, &(thread_id * 0x0A + 3), base + 2).unwrap();
4174                    tx.write(&0, &(thread_id * 0x0A + 4), base + 3).unwrap();
4175                    let ticket = tx.commit().unwrap();
4176                    ticket.wait().unwrap();
4177                }));
4178            }
4179
4180            for h in handles {
4181                h.join().unwrap();
4182            }
4183
4184            for thread_id in 0..4u64 {
4185                let base = (thread_id as usize) * 4;
4186                assert_eq!(mmap.read(base, |v| *v).unwrap(), thread_id * 0x0A + 1);
4187                assert_eq!(mmap.read(base + 1, |v| *v).unwrap(), thread_id * 0x0A + 2);
4188                assert_eq!(mmap.read(base + 2, |v| *v).unwrap(), thread_id * 0x0A + 3);
4189                assert_eq!(mmap.read(base + 3, |v| *v).unwrap(), thread_id * 0x0A + 4);
4190            }
4191        }
4192
4193        #[test]
4194        fn ok_tx_durability_across_reopen() {
4195            let (_dir, cfg) = tmp_cfg();
4196
4197            {
4198                let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
4199                let mut tx = mmap.new_tx();
4200                tx.write(&0, &0xAAAA_BBBB, 0).unwrap();
4201                tx.write(&0, &0xCCCC_DDDD, 1).unwrap();
4202                let ticket = tx.commit().unwrap();
4203                ticket.wait().unwrap();
4204                assert!(ticket.is_durable());
4205            }
4206
4207            {
4208                let reopened = MemMap::<u64>::open(cfg).unwrap();
4209                assert_eq!(reopened.read(0, |v| *v).unwrap(), 0xAAAA_BBBB);
4210                assert_eq!(reopened.read(1, |v| *v).unwrap(), 0xCCCC_DDDD);
4211            }
4212        }
4213
4214        #[test]
4215        fn ok_tx_unparks_idle_bg_worker() {
4216            let (_dir, cfg) = tmp_cfg();
4217            let mmap = MemMap::<u64>::new(cfg).unwrap();
4218
4219            // Wait for worker to park
4220            let mut parked = false;
4221            for _ in 0..0x64 {
4222                if mmap.is_parked() {
4223                    parked = true;
4224                    break;
4225                }
4226                std::thread::sleep(Duration::from_millis(0x0A));
4227            }
4228            assert!(parked);
4229
4230            let mut tx = mmap.new_tx();
4231            tx.write(&0, &0x22B, 0).unwrap();
4232            let ticket = tx.commit().unwrap();
4233
4234            // Background worker was unparked and should make ticket durable
4235            ticket.wait().unwrap();
4236            assert!(ticket.is_durable());
4237            assert_eq!(mmap.read(0, |v| *v).unwrap(), 0x22B);
4238        }
4239
4240        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
4241        #[repr(C)]
4242        struct TxNode {
4243            id: u64,
4244            flags: u64,
4245            keys: [u64; 2],
4246        }
4247
4248        #[test]
4249        fn ok_concurrent_tx_read_exact_and_write_exact_same_slots() {
4250            let (_dir, cfg) = tmp_cfg();
4251            let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
4252
4253            let barrier = Arc::new(std::sync::Barrier::new(6));
4254            let mut handles = Vec::new();
4255
4256            for t in 0..2 {
4257                let m = Arc::clone(&mmap);
4258                let b = Arc::clone(&barrier);
4259                handles.push(std::thread::spawn(move || {
4260                    b.wait();
4261                    for i in 0..0xC8 {
4262                        let val = ((t + 1) as u64) << 0x20 | (i as u64);
4263                        let _ = m
4264                            .write_exact(0, |node: &mut TxNode| {
4265                                node.id = val;
4266                                node.flags = val;
4267                                node.keys = [val, val];
4268                            })
4269                            .unwrap();
4270                    }
4271                }));
4272            }
4273
4274            for t in 2..4 {
4275                let m = Arc::clone(&mmap);
4276                let b = Arc::clone(&barrier);
4277                handles.push(std::thread::spawn(move || {
4278                    b.wait();
4279                    for i in 0..0xC8 {
4280                        let new_val = ((t + 1) as u64) << 0x20 | (i as u64);
4281                        let current_node = m.read_exact(0, |n: &TxNode| *n).unwrap();
4282                        let mut tx = m.new_tx();
4283                        if tx.write(&current_node.id, &new_val, 0).is_ok()
4284                            && tx.write(&current_node.flags, &new_val, 1).is_ok()
4285                            && tx.write(&current_node.keys[0], &new_val, 2).is_ok()
4286                            && tx.write(&current_node.keys[1], &new_val, 3).is_ok()
4287                        {
4288                            let _ = tx.commit();
4289                        }
4290                    }
4291                }));
4292            }
4293
4294            for _ in 0..2 {
4295                let m = Arc::clone(&mmap);
4296                let b = Arc::clone(&barrier);
4297                handles.push(std::thread::spawn(move || {
4298                    b.wait();
4299                    for _ in 0..0x190 {
4300                        m.read_exact(0, |node: &TxNode| {
4301                            assert_eq!(node.id, node.flags);
4302                            assert_eq!(node.id, node.keys[0]);
4303                            assert_eq!(node.id, node.keys[1]);
4304                        })
4305                        .unwrap();
4306                    }
4307                }));
4308            }
4309
4310            for h in handles {
4311                h.join().unwrap();
4312            }
4313
4314            let final_node = mmap.read_exact(0, |n: &TxNode| *n).unwrap();
4315            assert_eq!(final_node.id, final_node.flags);
4316            assert_eq!(final_node.id, final_node.keys[0]);
4317            assert_eq!(final_node.id, final_node.keys[1]);
4318        }
4319
4320        #[test]
4321        fn ok_tx_retry_loop_against_concurrent_write_exact_and_read_exact() {
4322            let (_dir, cfg) = tmp_cfg();
4323            let mmap = Arc::new(MemMap::<u64>::new(cfg).unwrap());
4324
4325            let barrier = Arc::new(std::sync::Barrier::new(4));
4326            let mut handles = Vec::new();
4327
4328            {
4329                let m = Arc::clone(&mmap);
4330                let b = Arc::clone(&barrier);
4331                handles.push(std::thread::spawn(move || {
4332                    b.wait();
4333                    for i in 0..0x64 {
4334                        let _ = m
4335                            .write_exact(0, |pair: &mut [u64; 2]| {
4336                                pair[0] = i as u64;
4337                                pair[1] = i as u64;
4338                            })
4339                            .unwrap();
4340                    }
4341                }));
4342            }
4343
4344            {
4345                let m = Arc::clone(&mmap);
4346                let b = Arc::clone(&barrier);
4347                handles.push(std::thread::spawn(move || {
4348                    b.wait();
4349                    let mut successful_commits = 0;
4350                    while successful_commits < 10 {
4351                        let current = m.read_exact(0, |pair: &[u64; 2]| *pair).unwrap();
4352                        let new_val = 0x8000_0000 + successful_commits;
4353                        let mut tx = m.new_tx();
4354                        if tx.write(&current[0], &new_val, 0).is_ok()
4355                            && tx.write(&current[1], &new_val, 1).is_ok()
4356                            && tx.commit().is_ok()
4357                        {
4358                            successful_commits += 1;
4359                        }
4360                    }
4361                }));
4362            }
4363
4364            for _ in 0..2 {
4365                let m = Arc::clone(&mmap);
4366                let b = Arc::clone(&barrier);
4367                handles.push(std::thread::spawn(move || {
4368                    b.wait();
4369                    for _ in 0..0xC8 {
4370                        m.read_exact(0, |pair: &[u64; 2]| {
4371                            assert_eq!(pair[0], pair[1]);
4372                        })
4373                        .unwrap();
4374                    }
4375                }));
4376            }
4377
4378            for h in handles {
4379                h.join().unwrap();
4380            }
4381
4382            mmap.read_exact(0, |pair: &[u64; 2]| {
4383                assert_eq!(pair[0], pair[1]);
4384            })
4385            .unwrap();
4386        }
4387    }
4388
4389    mod memmap_page_size {
4390        use super::*;
4391
4392        #[test]
4393        fn ok_page_size_returns_valid_power_of_two() {
4394            let page_size = MemMap::<u64>::page_size().unwrap();
4395            assert!(page_size.as_usize() >= 0x1000);
4396            assert!(page_size.is_power_of_two());
4397            assert_eq!(page_size.as_usize(), page_size.as_u32() as usize);
4398
4399            let matched_bytes = match page_size {
4400                PageSize::K4 => 4096,
4401                PageSize::K16 => 16384,
4402                PageSize::K64 => 65536,
4403            };
4404            assert_eq!(matched_bytes, page_size.as_usize());
4405        }
4406
4407        #[test]
4408        fn ok_page_size_consistency() {
4409            let ps1 = MemMap::<u64>::page_size().unwrap();
4410            let ps2 = MemMap::<u64>::page_size().unwrap();
4411            let ps3 = MemMap::<ValidT>::page_size().unwrap();
4412            assert_eq!(ps1, ps2);
4413            assert_eq!(ps1, ps3);
4414        }
4415
4416        #[test]
4417        fn ok_page_size_across_threads() {
4418            let expected = MemMap::<u64>::page_size().unwrap();
4419            let mut handles = Vec::new();
4420
4421            for _ in 0..8 {
4422                handles.push(std::thread::spawn(move || {
4423                    let ps = MemMap::<u64>::page_size().unwrap();
4424                    assert_eq!(ps, expected);
4425                }));
4426            }
4427
4428            for h in handles {
4429                h.join().unwrap();
4430            }
4431        }
4432    }
4433}