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.
23pub struct FileMapped<T> {
24    buf: RawPlace<T>,
25    mmap: Option<MmapMut>,
26    pub(crate) file: File,
27}
28
29impl<T> FileMapped<T> {
30    /// Creates a `FileMapped` from an already-opened file.
31    ///
32    /// If the file is smaller than one page (4 KiB), it is extended.
33    pub fn new(file: File) -> io::Result<Self> {
34        const MIN_PAGE_SIZE: u64 = 4096;
35
36        if file.metadata()?.len() < MIN_PAGE_SIZE {
37            file.set_len(MIN_PAGE_SIZE)?;
38        }
39
40        Ok(Self { file, buf: RawPlace::dangling(), mmap: None })
41    }
42
43    /// Opens (or creates) a file at `path` and wraps it in a `FileMapped`.
44    pub fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
45        File::options()
46            .create(true)
47            .truncate(false)
48            .read(true)
49            .write(true)
50            .open(path)
51            .and_then(Self::new)
52    }
53
54    fn map_yet(&mut self, cap: u64) -> io::Result<MmapMut> {
55        unsafe { MmapOptions::new().len(cap as usize).map_mut(&self.file) }
56    }
57
58    unsafe fn assume_mapped(&mut self) -> &mut [u8] {
59        unsafe { self.mmap.as_mut().unwrap_unchecked() }
60    }
61}
62
63impl<T> RawMem for FileMapped<T> {
64    type Item = T;
65
66    fn allocated(&self) -> &[Self::Item] {
67        unsafe { self.buf.as_slice() }
68    }
69
70    fn allocated_mut(&mut self) -> &mut [Self::Item] {
71        unsafe { self.buf.as_slice_mut() }
72    }
73
74    unsafe fn grow(
75        &mut self,
76        addition: usize,
77        fill: impl FnOnce(usize, (&mut [T], &mut [MaybeUninit<T>])),
78    ) -> Result<&mut [T]> {
79        unsafe {
80            let cap = self.buf.cap().checked_add(addition).ok_or(CapacityOverflow)?;
81            // use layout to prevent all capacity bugs
82            let layout = Layout::array::<T>(cap).map_err(|_| CapacityOverflow)?;
83            let new_size = layout.size() as u64;
84
85            // unmap the file by calling `Drop` of `mmap`
86            let _ = self.mmap.take();
87
88            let old_size = self.file.metadata()?.len();
89
90            #[rustfmt::skip]
91        let inited = if old_size < new_size {
92            self.file.set_len(new_size)?;
93            (old_size as usize / mem::size_of::<T>()) // more flexible without `rustfmt`
94                .unchecked_sub(self.buf.cap())
95        } else {
96            addition // all place is available as initialized
97        };
98
99            let ptr = {
100                let mmap = self.map_yet(new_size)?;
101                self.mmap.replace(mmap);
102                // we set it now: ^^^
103                NonNull::from(self.assume_mapped()) // it assume that `mmap` is some
104            };
105
106            Ok(self.buf.handle_fill((ptr.cast(), cap), inited, fill))
107        }
108    }
109
110    fn shrink(&mut self, cap: usize) -> Result<()> {
111        let cap = self.buf.cap().checked_sub(cap).expect("Tried to shrink to a larger capacity");
112        self.buf.shrink_to(cap);
113
114        let _ = self.mmap.take();
115
116        let ptr = unsafe {
117            // we can skip this checks because this memory layout is valid
118            // then smaller layout will also be valid
119            let new_size = mem::size_of::<T>().unchecked_mul(cap) as u64;
120            self.file.set_len(new_size)?;
121
122            let mmap = self.map_yet(new_size)?;
123            self.mmap.replace(mmap);
124
125            self.assume_mapped().into()
126        };
127
128        self.buf.set_ptr(ptr);
129
130        Ok(())
131    }
132}
133
134impl<T> Drop for FileMapped<T> {
135    fn drop(&mut self) {
136        unsafe {
137            ptr::drop_in_place(self.buf.as_slice_mut());
138        }
139
140        let _ = self.file.sync_all();
141    }
142}
143
144impl<T> fmt::Debug for FileMapped<T> {
145    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
146        utils::debug_mem(f, &self.buf, "FileMapped")?
147            .field("mmap", &self.mmap)
148            .field("file", &self.file)
149            .finish()
150    }
151}