Skip to main content

MemMap

Struct MemMap 

Source
pub struct MemMap<T: Sized + Clone> { /* private fields */ }
Expand description

Custom memory-mapped typed array storage with slot-level locking and epoch durability

MemMap<T> maps an on-disk File into process virtual address space as a contiguous array of fixed-size slots of type T

It provides,

  • Slot Synchronization: Per-slot reader-writer spinlocks (SlotLocks) protecting each index against concurrent write-write and read-write hazards with backoff spin/yield/sleep stages
  • Epoch Durability: Monotonically advancing write epoch counter incremented on each mutation, tracked via AckTicket and synchronized in the background by a dedicated worker
  • Process Isolation: Advisory kernel-level locking (flock/LockFileEx) ensuring exclusive file ownership by a single running instance

§Constraints

  • T must be sized and cloneable (T: Sized + Clone)
  • T must be 8-byte aligned (core::mem::align_of::<T>() == 8)
  • size_of::<T>() must be non-zero, a multiple of 8, and must not exceed the host page size
  • T must not implement Drop (!core::mem::needs_drop::<T>())
  • Type layout is immutable across file lifetime; schema changes require bumping MemMapCfg::version

§Example

use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("storage.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let memmap = MemMap::<u64>::new(cfg).unwrap();
assert_eq!(memmap.length(), 512 * std::mem::size_of::<u64>());

let (ticket, _) = memmap.write(0, |v| *v = 12345u64).unwrap();
assert_eq!(memmap.read(0, |v| *v).unwrap(), 12345u64);
assert!(memmap.delete().is_ok());

Implementations§

Source§

impl<T> MemMap<T>
where T: Sized + Clone,

Source

pub fn new(cfg: MemMapCfg) -> FrozenResult<Self>

Creates and memory-maps a new File at cfg.versioned_path()

Allocates an underlying file with capacity for initial_t * size_of::<T>() bytes, acquires an exclusive advisory lock on the descriptor, establishes the memory mapping, and spawns the background worker for periodic durability flushing

§Constraints
  • Returns [err::EXS] if the versioned file already exists on the filesystem; Use MemMap::open or MemMap::open_or_create instead
  • Returns [err::PTH] or [err::DIR] if the base path or its parent directory is invalid
  • Enforces all structural type invariants for T (align == 8, size % 8 == 0, size <= page_size, no Drop)
  • An exclusive advisory lock is acquired on creation; concurrent callers on the same file path fail
§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("new_memmap.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
assert_eq!(mmap.length(), std::mem::size_of::<u64>() * 512);

// Creating another file at the same path fails
assert!(MemMap::<u64>::new(cfg).is_err());
Source

pub fn open(cfg: MemMapCfg) -> FrozenResult<Self>

Opens an existing memory-mapped File at cfg.versioned_path()

Opens the underlying versioned file, verifies its length matches the requested layout, acquires an exclusive advisory lock on the descriptor, maps its memory region, and spawns the background flush worker

§Constraints
  • Returns [err::NEX] if the backing file does not exist on disk
  • Returns [err::PTH] if the path resolves to a directory
  • Fails with advisory lock error if another handle currently owns the exclusive lock
  • Type T invariants must strictly match the binary layout originally written to the file
§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("open_memmap.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

{
    let mmap = MemMap::<u64>::new(cfg.clone()).unwrap();
    assert_eq!(mmap.length(), std::mem::size_of::<u64>() * 512);
}

let opened = MemMap::<u64>::open(cfg).unwrap();
assert_eq!(opened.length(), std::mem::size_of::<u64>() * 512);
Source

pub fn open_or_create(cfg: MemMapCfg) -> FrozenResult<Self>

Opens an existing memory-mapped File or creates a new one if it does not yet exist

Idempotent entrypoint that transparently delegates to either open or creation semantics based on filesystem presence, maintaining consistent advisory locking and background flushing

§Constraints
  • Returns [err::PTH] if the resolved path points to an existing directory
  • Returns [err::DIR] if parent directory does not exist
  • Fails if another handle currently owns the advisory lock on the target file
  • All structural T invariants (align, size, needs_drop) are strictly validated
§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("open_or_create_memmap.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

{
    let mmap = MemMap::<u64>::open_or_create(cfg.clone()).unwrap();
    assert_eq!(mmap.length(), std::mem::size_of::<u64>() * 512);
}

let reopened = MemMap::<u64>::open_or_create(cfg).unwrap();
assert_eq!(reopened.length(), std::mem::size_of::<u64>() * 512);
Source

pub fn page_size() -> FrozenResult<PageSize>

Returns the system memory page size queried from kernel settings

§Example
use frozen_core::memmap::{MemMap, PageSize};

let page_size = MemMap::<u64>::page_size().unwrap();
assert!(page_size.as_usize() >= 4096);
assert!(page_size.is_power_of_two());
Source

pub fn length(&self) -> usize

Returns the length of the memory mapping in bytes

Corresponds to initial_t * size_of::<T>()

§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("length_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
assert_eq!(mmap.length(), 512 * std::mem::size_of::<u64>());
Source

pub fn grow(&self, pages: usize) -> FrozenResult<()>

Dynamically grows the memory-mapped file by pages additional virtual memory pages

§Semantics
  • Halts all concurrent read and write operations across all slots for the duration of the growth
  • Halts background durability flushing and manual sync operations
  • If uncommitted dirty writes exist (current_epoch > durable_epoch), performs a durability sync before modifying file boundaries
  • Explicitly unmaps the current virtual memory mapping
  • Zero-extends the backing file on disk safely via File::grow and synchronizes file metadata in units of the configured page size
  • Establishes a new memory mapping over the enlarged file range
  • Dynamically expands per-slot spinlocks to accommodate new slots
  • Updates tracked mapping length and advances durability epoch
§Crash Durability & Safety
  • Existing payload data is flushed to persistent storage before file extension, ensuring no data loss
  • Disk block pre-allocation (fallocate / f_preallocate) precedes logical size expansion; out-of-space failures leave existing data untouched
  • If file growth fails, the original memory mapping is automatically restored
§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("grow_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: None,
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
assert_eq!(mmap.length(), 512 * std::mem::size_of::<u64>());
mmap.write(0, |v| *v = 111u64).unwrap();

mmap.grow(1).unwrap();
assert_eq!(mmap.length(), (512 * std::mem::size_of::<u64>()) + MemMap::<u64>::page_size().unwrap().as_usize());
assert_eq!(mmap.read(0, |v| *v).unwrap(), 111u64);
assert_eq!(mmap.read(512, |v| *v).unwrap(), 0u64);
Source

pub fn cfg(&self) -> &MemMapCfg

Returns a reference to the active MemMapCfg configuration

§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("cfg_demo.bin"),
    version: 2,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
assert_eq!(mmap.cfg().version, 2);
assert_eq!(mmap.cfg().initial_t, 512);
Source

pub fn current_epoch(&self) -> TEpoch

Fetch the latest assigned durability epoch

The current epoch monotonically increments on each MemMap::write call

§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("epoch_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
assert_eq!(mmap.current_epoch(), 0);
mmap.write(0, |v| *v = 999u64).unwrap();
assert_eq!(mmap.current_epoch(), 1);
Source

pub fn durable_epoch(&self) -> TEpoch

Fetch the latest confirmed durable epoch flushed to persistent storage

§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("durable_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
let (ticket, _) = mmap.write(0, |v| *v = 100u64).unwrap();
mmap.sync().unwrap();
assert_eq!(mmap.durable_epoch(), ticket.epoch());
Source

pub fn sync(&self) -> FrozenResult<()>

Synchronize dirty pages and file metadata to stable storage

Performs a synchronous two-phase flush,

  • Flushes modified virtual memory pages to host cache via [PlatformMemMap::sync]
  • Advances the durable epoch to the current write epoch and awakens all pending listeners

Returns Ok(()) immediately if no writes have occurred since the last sync

§Example
use frozen_core::memmap::{MemMap, MemMapCfg};

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("sync_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: None,
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
let (ticket, _) = mmap.write(1, |v| *v = 0xCAFE_BABE).unwrap();
assert!(!ticket.is_durable());

mmap.sync().unwrap();
assert!(ticket.is_durable());
Source

pub fn read<R, F>(&self, index: usize, f: F) -> FrozenResult<R>
where F: FnOnce(&T) -> R,

Locks slot index for reading and invokes f with a reference to T

§Constraints
  • index * size_of::<T>() must not overflow usize, otherwise returns [err::INV]
  • Reading past the mapped memory region returns [err::HCF]
§Zero-Copy & Safety

Callback f is invoked with &T directly referencing mapped memory under lock

The higher-ranked lifetime bound (F: FnOnce(&T) -> R) ensures references cannot escape or outlive the locked callback scope

§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("read_test.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
mmap.write(0, |v| *v = 42u64).unwrap();
assert_eq!(mmap.read(0, |v| *v).unwrap(), 42u64);
Source

pub fn read_exact<Target, R, F>(&self, start: usize, f: F) -> FrozenResult<R>
where F: FnOnce(&Target) -> R,

Atomically locks N consecutive slots and invokes f with a reference to Target

Requires size_of::<Target>() == N * size_of::<T>() and compatible alignment

§Constraints
  • size_of::<Target>() must not be zero ([err::ZRO])
  • size_of::<Target>() must be an exact multiple of size_of::<T>() ([err::SZE])
  • align_of::<Target>() must not exceed align_of::<T>() ([err::ALN])
  • Slot index and byte offset calculations must not overflow usize ([err::INV])
  • Target slot range start..start + N must not exceed mapping bounds ([err::HCF])
§Deadlock Freedom

Read spinlocks for slots start..start + N are acquired in strictly ascending order

§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

#[derive(Clone, Copy)]
#[repr(C)]
struct Page {
    header: u64,
    payload: [u64; 3],
}

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("read_exact_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
mmap.write(0, |v| *v = 0xAA).unwrap();
mmap.write(1, |v| *v = 0xBB).unwrap();
mmap.write(2, |v| *v = 0xCC).unwrap();
mmap.write(3, |v| *v = 0xDD).unwrap();

let header = mmap.read_exact(0, |page: &Page| page.header).unwrap();
assert_eq!(header, 0xAA);
Source

pub fn write<R, F>(&self, index: usize, f: F) -> FrozenResult<(AckTicket, R)>
where F: FnOnce(&mut T) -> R,

Locks slot index for writing and invokes f with a mutable reference to T

Returns a tuple (AckTicket, R) containing the durability acknowledgement ticket and the result returned by callback f

§Constraints
  • index * size_of::<T>() must not overflow usize, otherwise returns [err::INV]
  • Writing past the mapped memory region returns [err::HCF]
§Deadlock & Safety
  • Exclusive writer spinlock for slot index is held during closure execution
  • If the closure unwinds / panics, the spinlock is cleanly released via RAII and the durability epoch counter is not incremented
§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("write_test.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
let (ticket, _) = mmap.write(2, |v| *v = 100u64).unwrap();
assert_eq!(mmap.read(2, |v| *v).unwrap(), 100u64);
Source

pub fn write_exact<Target, R, F>( &self, start: usize, f: F, ) -> FrozenResult<(AckTicket, R)>
where F: FnOnce(&mut Target) -> R,

Atomically locks N consecutive slots and invokes f with mutable access to Target

Requires size_of::<Target>() == N * size_of::<T>() and compatible alignment

Returns a tuple (AckTicket, R) containing the durability acknowledgement ticket and the result returned by callback f

§Constraints
  • size_of::<Target>() must not be zero ([err::ZRO])
  • size_of::<Target>() must be an exact multiple of size_of::<T>() ([err::SZE])
  • align_of::<Target>() must not exceed align_of::<T>() ([err::ALN])
  • Slot index and byte offset calculations must not overflow usize ([err::INV])
  • Target slot range start..start + N must not exceed mapping bounds ([err::HCF])
§Deadlock Freedom

Exclusive write spinlocks for slots start..start + N are acquired in strictly ascending order

§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

#[repr(C)]
struct Page {
    header: u64,
    payload: [u64; 3],
}

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("write_exact_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
let (ticket, _) = mmap
    .write_exact(0, |page: &mut Page| {
        page.header = 0xAA;
        page.payload[0] = 0xBB;
    })
    .unwrap();

assert_eq!(mmap.read(0, |v| *v).unwrap(), 0xAA);
assert_eq!(mmap.read(1, |v| *v).unwrap(), 0xBB);
assert_eq!(ticket.epoch(), 1);
Source

pub fn delete(self) -> FrozenResult<()>

Deletes the MemMap and underlying File entry from the storage device

Consumes self by value to release resources in deterministic order,

  • Signals the background flush worker to stop and waits for its thread join
  • Unmaps the active memory mapping from virtual address space
  • Closes and unlinks the backing versioned file from the filesystem
§Constraints
  • Consumes self by value; after deletion, the instance can no longer be accessed
  • Returns [err::INV] if the underlying file handle was already released
§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("delete_demo.bin");
let versioned_path = dir.path().join("delete_demo_1.bin");
let cfg = MemMapCfg {
    module_id: 1,
    path,
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
assert!(versioned_path.exists());

mmap.delete().unwrap();
assert!(!versioned_path.exists());
Source§

impl<T> MemMap<T>
where T: Sized + Clone + PartialEq,

Source

pub fn compare_and_write( &self, expected: &T, val: &T, index: usize, ) -> FrozenResult<AckTicket>

Conditionally writes val into slot index if and only if the current value matches expected

§Semantics

Acquires the exclusive slot lock for index and verifies that the current slot value equals expected; if equal, writes val, advances the epoch, and returns an AckTicket. If the current value differs from expected, the operation immediately aborts without modifying the slot, returning [err::CAS] so the caller can re-read and retry

§Constraints
  • index * size_of::<T>() must not overflow usize, otherwise returns [err::INV]
  • Writing past the mapped memory region returns [err::HCF]
  • Returns [err::CAS] if expected != current
§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("cas_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();
mmap.write(0, |v| *v = 100u64).unwrap();

// Successful CAS: 100 -> 200
let ticket = mmap.compare_and_write(&100u64, &200u64, 0).unwrap();
assert_eq!(mmap.read(0, |v| *v).unwrap(), 200u64);

// Failed CAS: expected 100, but slot is 200
assert!(mmap.compare_and_write(&100u64, &300u64, 0).is_err());
assert_eq!(mmap.read(0, |v| *v).unwrap(), 200u64);
Source

pub fn new_tx(&self) -> MemMapTransaction<'_, T>

Creates a new MemMapTransaction context for grouping multiple conditional writes into a single atomic epoch

§Overview

MemMapTransaction allows grouping multiple slot mutations into a single atomic operation:

  • Atomicity: All writes succeed or fail together
  • Optimistic Concurrency Control (CAS): Each write asserts that its target slot currently matches expected; if any slot differs at commit time, all changes abort and no slots are modified
  • Single Epoch: All committed writes share a single durability epoch and durable window
  • Deadlock Freedom: Slot locks are acquired in strictly ascending index order
§Example
use frozen_core::memmap::{MemMap, MemMapCfg};
use std::time::Duration;

let dir = tempfile::tempdir().unwrap();
let cfg = MemMapCfg {
    module_id: 1,
    path: dir.path().join("tx_demo.bin"),
    version: 1,
    initial_t: 512,
    sync_interval: Some(Duration::from_millis(100)),
    page_size: None,
};

let mmap = MemMap::<u64>::new(cfg).unwrap();

let mut tx = mmap.new_tx();
tx.write(&0u64, &10u64, 0).unwrap();
tx.write(&0u64, &20u64, 1).unwrap();

let ticket = tx.commit().unwrap();
assert_eq!(mmap.read(0, |v| *v).unwrap(), 10u64);
assert_eq!(mmap.read(1, |v| *v).unwrap(), 20u64);

Trait Implementations§

Source§

impl<T> Debug for MemMap<T>
where T: Sized + Clone,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<T> Drop for MemMap<T>
where T: Sized + Clone,

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl<T> Send for MemMap<T>
where T: Sized + Clone,

Source§

impl<T> Sync for MemMap<T>
where T: Sized + Clone,

Auto Trait Implementations§

§

impl<T> !RefUnwindSafe for MemMap<T>

§

impl<T> !UnwindSafe for MemMap<T>

§

impl<T> Freeze for MemMap<T>
where PhantomData<T>: Freeze,

§

impl<T> Unpin for MemMap<T>
where PhantomData<T>: Unpin,

§

impl<T> UnsafeUnpin for MemMap<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.