Skip to main content

platform_mem/
file_mapped.rs

1use {
2    crate::{Error::CapacityOverflow, RawMem, Result, raw_place::RawPlace, utils},
3    memmap2::{MmapMut, MmapOptions},
4    std::{
5        alloc::Layout,
6        fmt::{self, Formatter},
7        fs::File,
8        io,
9        mem::{self, MaybeUninit},
10        path::Path,
11        ptr::{self, NonNull},
12    },
13};
14
15/// Memory-mapped file storage.
16///
17/// Elements are stored directly in a memory-mapped region backed by a file.
18/// The file is resized and remapped on each [`grow`](RawMem::grow) or
19/// [`shrink`](RawMem::shrink) call. Data is synced to disk on drop.
20///
21/// The file is guaranteed to be at least 4 KiB (one page) to satisfy
22/// OS-level mmap requirements.
23///
24/// Growth through [`grow_filled`](RawMem::grow_filled),
25/// [`grow_zeroed`](RawMem::grow_zeroed), or [`grow_with`](RawMem::grow_with)
26/// preserves complete elements present when the file was opened and initializes
27/// only the new tail. Padding added on opening is initialized on first growth.
28pub struct FileMapped<T> {
29    buf: RawPlace<T>,
30    mmap: Option<MmapMut>,
31    pub(crate) file: File,
32    // Existing file data plus successfully initialized growth, excluding fresh
33    // minimum-page padding. This can exceed the currently mapped region.
34    initialized_size: u64,
35}
36
37impl<T> FileMapped<T> {
38    /// Creates a `FileMapped` from an already-opened file.
39    ///
40    /// If the file is smaller than one page (4 KiB), it is extended.
41    pub fn new(file: File) -> io::Result<Self> {
42        const MIN_PAGE_SIZE: u64 = 4096;
43
44        let initialized_size = file.metadata()?.len();
45        if initialized_size < MIN_PAGE_SIZE {
46            file.set_len(MIN_PAGE_SIZE)?;
47        }
48
49        Ok(Self { file, buf: RawPlace::dangling(), mmap: None, initialized_size })
50    }
51
52    /// Opens (or creates) a file at `path` and wraps it in a `FileMapped`.
53    pub fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
54        File::options()
55            .create(true)
56            .truncate(false)
57            .read(true)
58            .write(true)
59            .open(path)
60            .and_then(Self::new)
61    }
62
63    fn map_yet(&mut self, cap: u64) -> io::Result<MmapMut> {
64        unsafe { MmapOptions::new().len(cap as usize).map_mut(&self.file) }
65    }
66
67    unsafe fn assume_mapped(&mut self) -> &mut [u8] {
68        unsafe { self.mmap.as_mut().unwrap_unchecked() }
69    }
70}
71
72impl<T> RawMem for FileMapped<T> {
73    type Item = T;
74
75    fn allocated(&self) -> &[Self::Item] {
76        unsafe { self.buf.as_slice() }
77    }
78
79    fn allocated_mut(&mut self) -> &mut [Self::Item] {
80        unsafe { self.buf.as_slice_mut() }
81    }
82
83    unsafe fn grow(
84        &mut self,
85        addition: usize,
86        fill: impl FnOnce(usize, (&mut [T], &mut [MaybeUninit<T>])),
87    ) -> Result<&mut [T]> {
88        unsafe {
89            let cap = self.buf.cap().checked_add(addition).ok_or(CapacityOverflow)?;
90            // use layout to prevent all capacity bugs
91            let layout = Layout::array::<T>(cap).map_err(|_| CapacityOverflow)?;
92            let new_size = layout.size() as u64;
93
94            // unmap the file by calling `Drop` of `mmap`
95            let _ = self.mmap.take();
96
97            let old_size = self.file.metadata()?.len();
98
99            if old_size < new_size {
100                self.file.set_len(new_size)?;
101            }
102            let inited = (self.initialized_size / mem::size_of::<T>() as u64)
103                .saturating_sub(self.buf.cap() as u64)
104                .min(addition as u64) as usize;
105
106            let ptr = {
107                let mmap = self.map_yet(new_size)?;
108                self.mmap.replace(mmap);
109                // we set it now: ^^^
110                NonNull::from(self.assume_mapped()) // it assume that `mmap` is some
111            };
112
113            let grown = self.buf.handle_fill((ptr.cast(), cap), inited, fill);
114            self.initialized_size = self.initialized_size.max(new_size);
115            Ok(grown)
116        }
117    }
118
119    fn shrink(&mut self, cap: usize) -> Result<()> {
120        let cap = self.buf.cap().checked_sub(cap).expect("Tried to shrink to a larger capacity");
121        self.buf.shrink_to(cap);
122
123        let _ = self.mmap.take();
124
125        let ptr = unsafe {
126            // we can skip this checks because this memory layout is valid
127            // then smaller layout will also be valid
128            let new_size = mem::size_of::<T>().unchecked_mul(cap) as u64;
129            self.file.set_len(new_size)?;
130            self.initialized_size = new_size;
131
132            let mmap = self.map_yet(new_size)?;
133            self.mmap.replace(mmap);
134
135            self.assume_mapped().into()
136        };
137
138        self.buf.set_ptr(ptr);
139
140        Ok(())
141    }
142}
143
144impl<T> Drop for FileMapped<T> {
145    fn drop(&mut self) {
146        unsafe {
147            ptr::drop_in_place(self.buf.as_slice_mut());
148        }
149
150        let _ = self.file.sync_all();
151    }
152}
153
154impl<T> fmt::Debug for FileMapped<T> {
155    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
156        utils::debug_mem(f, &self.buf, "FileMapped")?
157            .field("mmap", &self.mmap)
158            .field("file", &self.file)
159            .finish()
160    }
161}