use std::fs::File;
use std::io;
use memmap2::MmapMut;
pub trait ByteStore: AsRef<[u8]> + AsMut<[u8]> {
fn grow(&mut self, additional: usize);
fn grow_new_empty(&self, additional: usize) -> Self;
fn stats(&self) -> u64;
}
#[derive(Debug, Clone, Default)]
pub struct VecStore {
vec: Vec<u8>,
resizes: u64,
}
impl VecStore {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
vec: Vec::with_capacity(capacity),
resizes: 0,
}
}
}
impl AsRef<[u8]> for VecStore {
fn as_ref(&self) -> &[u8] {
&self.vec
}
}
impl AsMut<[u8]> for VecStore {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.vec
}
}
impl ByteStore for VecStore {
fn grow(&mut self, additional: usize) {
self.resizes += 1;
self.vec.resize(self.vec.len() + additional, 0);
}
fn grow_new_empty(&self, additional: usize) -> Self {
let len = (self.vec.len() + additional).next_power_of_two();
Self {
vec: vec![0u8; len],
resizes: 0,
}
}
fn stats(&self) -> u64 {
self.resizes
}
}
impl<const N: usize> ByteStore for [u8; N] {
fn grow(&mut self, _additional: usize) {
panic!("Cannot grow fixed size arrays")
}
fn grow_new_empty(&self, _additional: usize) -> Self {
panic!("can't add additional to fixed size N")
}
fn stats(&self) -> u64 {
0
}
}
impl ByteStore for Box<[u8]> {
fn grow(&mut self, additional: usize) {
let old_len = self.len();
let new_len = (old_len + additional).next_power_of_two();
let mut new_vec = vec![0u8; new_len];
new_vec[..old_len].copy_from_slice(self);
*self = new_vec.into_boxed_slice();
}
fn grow_new_empty(&self, additional: usize) -> Self {
let old_len = self.len();
let new_len = (old_len + additional).next_power_of_two();
vec![0u8; new_len].into_boxed_slice()
}
fn stats(&self) -> u64 {
0 }
}
pub struct MMapFile {
mmap: MmapMut,
file: File,
path: std::path::PathBuf,
idx: usize,
resizes: u64,
}
impl MMapFile {
pub fn new<P: AsRef<std::path::Path>>(path: P, length_bytes: usize) -> io::Result<Self> {
Self::new_inner(path, length_bytes, 0)
}
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> io::Result<Self> {
use std::fs::OpenOptions;
let path = path.as_ref();
let file = OpenOptions::new().read(true).write(true).open(path)?;
let mmap = unsafe { MmapMut::map_mut(&file)? };
let file_stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
let idx = if let Some(stripped) = file_stem.strip_prefix("entries_") {
stripped.parse::<usize>().unwrap_or(0)
} else {
0
};
Ok(Self {
mmap,
file,
path: path.to_path_buf(),
idx,
resizes: 0,
})
}
fn new_inner<P: AsRef<std::path::Path>>(
path: P,
length_bytes: usize,
idx: usize,
) -> io::Result<Self> {
use std::fs::OpenOptions;
let mut size = length_bytes.max(1);
size = size.next_power_of_two();
let path = path.as_ref();
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
let path = path.to_path_buf();
file.set_len(size as u64)?;
let mmap = unsafe { MmapMut::map_mut(&file)? };
Ok(Self {
mmap,
file,
idx,
path,
resizes: 0,
})
}
}
impl Drop for MMapFile {
fn drop(&mut self) {
let _ = self.mmap.flush();
let _ = self.file.sync_all();
}
}
impl AsRef<[u8]> for MMapFile {
fn as_ref(&self) -> &[u8] {
&self.mmap
}
}
impl AsMut<[u8]> for MMapFile {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.mmap
}
}
impl ByteStore for MMapFile {
fn grow(&mut self, additional: usize) {
self.resizes += 1;
if self.mmap.flush().is_ok() {
self.file
.sync_all()
.unwrap_or_else(|_| panic!("Unrecoverable error syncing file"));
}
let current_size = self.mmap.len();
let new_size = (current_size + additional).next_power_of_two();
let mut old_mmap = MmapMut::map_anon(1)
.unwrap_or_else(|_| panic!("Unrecoverable error creating anonymous mmap"));
std::mem::swap(&mut self.mmap, &mut old_mmap);
drop(old_mmap);
self.file.set_len(new_size as u64).unwrap_or_else(|_| {
panic!("Unrecoverable error resizing file to {new_size} bytes");
});
self.mmap = unsafe {
MmapMut::map_mut(&self.file).unwrap_or_else(|_| {
panic!("Unrecoverable error remapping file to {new_size} bytes");
})
};
}
fn grow_new_empty(&self, additional: usize) -> Self {
let current_size = self.mmap.len();
let new_size = (current_size + additional).next_power_of_two();
let parent_path = self
.path
.parent()
.expect("Failed to get parent path of mmap file");
let path_name = self
.path
.file_name()
.expect("Failed to get file name from mmap file path")
.to_string_lossy()
.to_string();
let file_name = parent_path.join(format!("{}_{}.bin", path_name, self.idx + 1));
let mut new_file =
MMapFile::new_inner(file_name, new_size, self.idx + 1).unwrap_or_else(|err| {
panic!("Unrecoverable error creating new mmap file with {new_size} bytes: {err}");
});
new_file.resizes = self.resizes + 1;
new_file
}
fn stats(&self) -> u64 {
self.resizes
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::OpenOptions;
use tempfile::NamedTempFile;
#[test]
fn test_mmapfile_create_and_write() {
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path();
let mut mmapfile = MMapFile::new(path, 1024).unwrap();
assert_eq!(mmapfile.as_ref().len(), 1024);
mmapfile.as_mut()[0..4].copy_from_slice(b"test");
mmapfile.mmap.flush().unwrap();
drop(mmapfile);
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.unwrap();
let mmap = unsafe { memmap2::Mmap::map(&file).unwrap() };
assert_eq!(&mmap[0..4], b"test");
}
#[test]
fn test_mmapfile_grow_and_persist() {
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path();
let mut mmapfile = MMapFile::new(path, 1024).unwrap(); assert_eq!(mmapfile.as_ref().len(), 1024);
let len = mmapfile.as_ref().len();
mmapfile.as_mut()[len - 4..len].copy_from_slice(b"grow");
mmapfile.mmap.flush().unwrap();
mmapfile.grow(1024);
assert_eq!(mmapfile.as_ref().len(), 2048);
assert_eq!(&mmapfile.as_ref()[len - 4..len], b"grow");
mmapfile.as_mut()[0..6].copy_from_slice(b"hello!");
mmapfile.mmap.flush().unwrap();
drop(mmapfile);
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.unwrap();
let mmap = unsafe { memmap2::Mmap::map(&file).unwrap() };
assert_eq!(&mmap[len - 4..len], b"grow");
assert_eq!(&mmap[0..6], b"hello!");
}
#[test]
fn test_mmapfile_rounds_to_power_of_2() {
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path();
let mmapfile = MMapFile::new(path, 3 * 1024).unwrap(); assert_eq!(mmapfile.as_ref().len(), 4096);
}
}