1use 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
60pub(in crate::memmap) mod err {
62 use super::{ErrCode, FrozenError, FrozenResult};
63
64 const ERRDOMAIN: u8 = 0x10;
66
67 pub const HCF: ErrCode = ErrCode::new(0x02, "hault and catch fire");
69
70 pub const UNK: ErrCode = ErrCode::new(0x04, "unknown error");
72
73 pub const NMM: ErrCode = ErrCode::new(0x06, "not enough memory available on the device");
75
76 pub const SYN: ErrCode = ErrCode::new(0x08, "failed to sync/flush data to storage device");
78
79 pub const PRM: ErrCode = ErrCode::new(0x0A, "missing permissions for IO");
81
82 pub const FXE: ErrCode = ErrCode::new(0x10, "unable to spawn flush_tx");
84
85 pub const ZRO: ErrCode = ErrCode::new(0x0C, "type T must not be zero sized");
87
88 pub const DRP: ErrCode = ErrCode::new(0x12, "type T must not implement `Drop`");
90
91 pub const ALN: ErrCode = ErrCode::new(0x14, "type T must be 8-bytes aligned");
93
94 pub const SZE: ErrCode = ErrCode::new(0x16, "`size_of::<T>()` must be multiple of 8 bytes");
96
97 pub const INV: ErrCode =
100 ErrCode::new(0x18, "`size_of::<T>()` must not exceed system page size");
101
102 pub const DIR: ErrCode = ErrCode::new(0x1A, "directory does not exist");
104
105 pub const PTH: ErrCode = ErrCode::new(0x1C, "invalid path to file");
107
108 pub const EXS: ErrCode = ErrCode::new(0x1E, "file already exists");
110
111 pub const NEX: ErrCode = ErrCode::new(0x20, "file does not exist");
113
114 pub const GRW: ErrCode = ErrCode::new(0x22, "unable to grow memory-mapped file");
116
117 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#[derive(Debug, Clone)]
167pub struct MemMapCfg {
168 pub module_id: u8,
170
171 pub path: std::path::PathBuf,
180
181 pub version: u32,
190
191 pub initial_t: usize,
195
196 pub sync_interval: Option<std::time::Duration>,
202
203 pub page_size: Option<PageSize>,
208}
209
210impl MemMapCfg {
211 #[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#[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
334struct 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
344struct 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#[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 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
426pub 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 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 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 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 #[inline]
927 pub fn page_size() -> FrozenResult<PageSize> {
928 PlatformMemMap::page_size()
929 }
930
931 #[inline]
955 pub fn length(&self) -> usize {
956 self.inner.length.load(atomic::Ordering::Acquire)
957 }
958
959 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 let mut slot_locks_guard = self.inner.slot_locks.write().unwrap_or_else(|e| e.into_inner());
1033
1034 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 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 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 #[inline]
1125 pub fn cfg(&self) -> &MemMapCfg {
1126 &self.cfg
1127 }
1128
1129 #[inline]
1155 pub fn current_epoch(&self) -> TEpoch {
1156 self.inner.completion.read_current_epoch()
1157 }
1158
1159 #[inline]
1183 pub fn durable_epoch(&self) -> TEpoch {
1184 self.inner.completion.read_durable_epoch()
1185 }
1186
1187 #[inline]
1219 pub fn sync(&self) -> FrozenResult<()> {
1220 self.inner.sync_internal()
1221 }
1222
1223 #[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 #[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 #[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 #[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 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 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 #[inline]
1855 pub fn new_tx(&self) -> MemMapTransaction<'_, T> {
1856 MemMapTransaction { memmap: self, ops: Vec::new() }
1857 }
1858}
1859
1860#[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 #[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 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 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 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 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 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 #[inline]
2106 pub fn len(&self) -> usize {
2107 self.ops.len()
2108 }
2109
2110 #[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 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 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 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 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 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 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 let (ticket, _) = mmap.write(0, |v| *v = 0x9999).unwrap();
3620 assert!(!mmap.is_parked());
3621
3622 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 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 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 let (t1, _) = mmap.write(0, |v| *v = 0x1111).unwrap();
3652 assert!(!mmap.is_parked());
3653
3654 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 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)); let mmap = MemMap::<u64>::new(cfg).unwrap();
3717
3718 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 mmap.inner.sync_condvar.notify_one();
3727 std::thread::sleep(Duration::from_millis(0x0A));
3728
3729 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 {
3747 let mut guard = mmap.inner.mmap.write().unwrap();
3748 let _ = guard.take();
3749 }
3750
3751 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; 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 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 std::fs::write(cfg.versioned_path(), b"preexisting").unwrap();
3920
3921 let res = MemMap::<u64>::new(cfg);
3922 assert!(res.is_err());
3923 }
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 }
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 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 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 }
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 {
3963 let mut guard = mmap.inner.mmap.write().unwrap();
3964 let _ = guard.take();
3965 }
3966
3967 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 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); 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 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 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(); tx.write(&0, &0x14, 1).unwrap(); tx.write(&0, &0x1E, 2).unwrap(); 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 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 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 mmap.write(1, |v| *v = 0x2A).unwrap();
4075
4076 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 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 }
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 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 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(¤t_node.id, &new_val, 0).is_ok()
4284 && tx.write(¤t_node.flags, &new_val, 1).is_ok()
4285 && tx.write(¤t_node.keys[0], &new_val, 2).is_ok()
4286 && tx.write(¤t_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(¤t[0], &new_val, 0).is_ok()
4355 && tx.write(¤t[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}