#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TouchHint {
#[default]
Never,
Eager,
Lazy,
}
use std::{
fs::{File, OpenOptions},
path::{Path, PathBuf},
sync::Arc,
};
use memmap2::{Mmap, MmapMut};
use crate::flush::FlushPolicy;
#[cfg(feature = "cow")]
use memmap2::MmapOptions;
use parking_lot::RwLock;
use crate::errors::{MmapIoError, Result};
use crate::utils::{ensure_in_bounds, slice_range};
const ERR_ZERO_SIZE: &str = "Size must be greater than zero";
const ERR_ZERO_LENGTH_FILE: &str = "Cannot map zero-length file";
#[cfg(target_pointer_width = "64")]
const MAX_MMAP_SIZE: u64 = 128 * (1 << 40);
#[cfg(target_pointer_width = "32")]
const MAX_MMAP_SIZE: u64 = 2 * (1 << 30);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MmapMode {
ReadOnly,
ReadWrite,
CopyOnWrite,
}
#[doc(hidden)]
pub struct Inner {
pub(crate) path: PathBuf,
pub(crate) file: File,
pub(crate) mode: MmapMode,
pub(crate) cached_len: RwLock<u64>,
pub(crate) map: MapVariant,
pub(crate) flush_policy: FlushPolicy,
pub(crate) written_since_last_flush: RwLock<u64>,
pub(crate) flusher: RwLock<Option<crate::flush::TimeBasedFlusher>>,
#[cfg(feature = "hugepages")]
pub(crate) huge_pages: bool,
}
#[doc(hidden)]
pub enum MapVariant {
Ro(Mmap),
Rw(RwLock<MmapMut>),
Cow(Mmap),
}
#[derive(Clone)]
pub struct MemoryMappedFile {
pub(crate) inner: Arc<Inner>,
}
impl std::fmt::Debug for MemoryMappedFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut ds = f.debug_struct("MemoryMappedFile");
ds.field("path", &self.inner.path)
.field("mode", &self.inner.mode)
.field("len", &self.len());
#[cfg(feature = "hugepages")]
{
ds.field("huge_pages", &self.inner.huge_pages);
}
ds.finish()
}
}
impl MemoryMappedFile {
pub fn builder<P: AsRef<Path>>(path: P) -> MemoryMappedFileBuilder {
MemoryMappedFileBuilder {
path: path.as_ref().to_path_buf(),
size: None,
mode: None,
flush_policy: FlushPolicy::default(),
touch_hint: TouchHint::default(),
#[cfg(feature = "hugepages")]
huge_pages: false,
}
}
pub fn create_rw<P: AsRef<Path>>(path: P, size: u64) -> Result<Self> {
if size == 0 {
return Err(MmapIoError::ResizeFailed(ERR_ZERO_SIZE.into()));
}
if size > MAX_MMAP_SIZE {
return Err(MmapIoError::ResizeFailed(format!(
"Size {size} exceeds maximum safe limit of {MAX_MMAP_SIZE} bytes"
)));
}
let path_ref = path.as_ref();
let file = OpenOptions::new()
.create(true)
.write(true)
.read(true)
.truncate(true)
.open(path_ref)?;
file.set_len(size)?;
let mmap = unsafe { MmapMut::map_mut(&file)? };
let inner = Inner {
path: path_ref.to_path_buf(),
file,
mode: MmapMode::ReadWrite,
cached_len: RwLock::new(size),
map: MapVariant::Rw(RwLock::new(mmap)),
flush_policy: FlushPolicy::default(),
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: false,
};
Ok(Self {
inner: Arc::new(inner),
})
}
pub fn open_ro<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_ref = path.as_ref();
let file = OpenOptions::new().read(true).open(path_ref)?;
let len = file.metadata()?.len();
let mmap = unsafe { Mmap::map(&file)? };
let inner = Inner {
path: path_ref.to_path_buf(),
file,
mode: MmapMode::ReadOnly,
cached_len: RwLock::new(len),
map: MapVariant::Ro(mmap),
flush_policy: FlushPolicy::Never,
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: false,
};
Ok(Self {
inner: Arc::new(inner),
})
}
pub fn open_rw<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_ref = path.as_ref();
let file = OpenOptions::new().read(true).write(true).open(path_ref)?;
let len = file.metadata()?.len();
if len == 0 {
return Err(MmapIoError::ResizeFailed(ERR_ZERO_LENGTH_FILE.into()));
}
let mmap = unsafe { MmapMut::map_mut(&file)? };
let inner = Inner {
path: path_ref.to_path_buf(),
file,
mode: MmapMode::ReadWrite,
cached_len: RwLock::new(len),
map: MapVariant::Rw(RwLock::new(mmap)),
flush_policy: FlushPolicy::default(),
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: false,
};
Ok(Self {
inner: Arc::new(inner),
})
}
#[must_use]
pub fn mode(&self) -> MmapMode {
self.inner.mode
}
#[must_use]
pub fn len(&self) -> u64 {
*self.inner.cached_len.read()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_slice(&self, offset: u64, len: u64) -> Result<MappedSlice<'_>> {
let total = self.current_len()?;
let (start, end) = slice_range(offset, len, total)?;
match &self.inner.map {
MapVariant::Ro(m) => Ok(MappedSlice::owned(&m[start..end])),
MapVariant::Rw(lock) => {
let guard = lock.read();
Ok(MappedSlice::guarded(guard, start..end))
}
MapVariant::Cow(m) => Ok(MappedSlice::owned(&m[start..end])),
}
}
pub fn as_slice_mut(&self, offset: u64, len: u64) -> Result<MappedSliceMut<'_>> {
let (start, end) = slice_range(offset, len, self.current_len()?)?;
match &self.inner.map {
MapVariant::Ro(_) => Err(MmapIoError::InvalidMode(
"mutable access on read-only mapping",
)),
MapVariant::Rw(lock) => {
let guard = lock.write();
Ok(MappedSliceMut {
guard,
range: start..end,
})
}
MapVariant::Cow(_) => {
Err(MmapIoError::InvalidMode(
"mutable access on copy-on-write mapping (phase-1 read-only)",
))
}
}
}
pub fn update_region(&self, offset: u64, data: &[u8]) -> Result<()> {
if data.is_empty() {
return Ok(());
}
if self.inner.mode != MmapMode::ReadWrite {
return Err(MmapIoError::InvalidMode(
"Update region requires ReadWrite mode.",
));
}
let len = data.len() as u64;
let (start, end) = slice_range(offset, len, self.current_len()?)?;
match &self.inner.map {
MapVariant::Ro(_) => Err(MmapIoError::InvalidMode(
"Cannot write to read-only mapping",
)),
MapVariant::Rw(lock) => {
{
let mut guard = lock.write();
guard[start..end].copy_from_slice(data);
}
self.apply_flush_policy(len)?;
Ok(())
}
MapVariant::Cow(_) => Err(MmapIoError::InvalidMode(
"Cannot write to copy-on-write mapping (phase-1 read-only)",
)),
}
}
#[cfg(feature = "async")]
pub async fn update_region_async(&self, offset: u64, data: &[u8]) -> Result<()> {
let this = self.clone();
let data_vec = data.to_vec();
tokio::task::spawn_blocking(move || {
this.update_region(offset, &data_vec)?;
this.flush()
})
.await
.map_err(|e| MmapIoError::FlushFailed(format!("join error: {e}")))?
}
pub fn flush(&self) -> Result<()> {
match &self.inner.map {
MapVariant::Ro(_) => Ok(()),
MapVariant::Cow(_) => Ok(()), MapVariant::Rw(lock) => {
if *self.inner.written_since_last_flush.read() == 0 {
return Ok(());
}
#[cfg(all(unix, target_os = "linux"))]
{
if let Ok(len) = self.current_len() {
if len > 0 && self.try_linux_async_flush(len as usize)? {
return Ok(());
}
}
}
let guard = lock.read();
guard
.flush()
.map_err(|e| MmapIoError::FlushFailed(e.to_string()))?;
*self.inner.written_since_last_flush.write() = 0;
Ok(())
}
}
}
#[cfg(feature = "async")]
pub async fn flush_async(&self) -> Result<()> {
let this = self.clone();
tokio::task::spawn_blocking(move || this.flush())
.await
.map_err(|e| MmapIoError::FlushFailed(format!("join error: {e}")))?
}
#[cfg(feature = "async")]
pub async fn flush_range_async(&self, offset: u64, len: u64) -> Result<()> {
let this = self.clone();
tokio::task::spawn_blocking(move || this.flush_range(offset, len))
.await
.map_err(|e| MmapIoError::FlushFailed(format!("join error: {e}")))?
}
pub fn flush_range(&self, offset: u64, len: u64) -> Result<()> {
if len == 0 {
return Ok(());
}
ensure_in_bounds(offset, len, self.current_len()?)?;
match &self.inner.map {
MapVariant::Ro(_) => Ok(()),
MapVariant::Cow(_) => Ok(()), MapVariant::Rw(lock) => {
if *self.inner.written_since_last_flush.read() == 0 {
return Ok(());
}
let (start, end) = slice_range(offset, len, self.current_len()?)?;
let range_len = end - start;
let (optimized_start, optimized_len) = if range_len < crate::utils::page_size() {
use crate::utils::{align_up, page_size};
let page_sz = page_size();
let aligned_start = (start / page_sz) * page_sz;
let aligned_end = align_up(end as u64, page_sz as u64) as usize;
let file_len = self.current_len()? as usize;
let bounded_end = std::cmp::min(aligned_end, file_len);
let bounded_len = bounded_end.saturating_sub(aligned_start);
(aligned_start, bounded_len)
} else {
(start, range_len)
};
#[cfg(all(unix, target_os = "linux"))]
{
let msync_res: i32 = {
let guard = lock.read();
let base = guard.as_ptr();
let ptr = unsafe { base.add(optimized_start) } as *mut libc::c_void;
unsafe { libc::msync(ptr, optimized_len, libc::MS_ASYNC) }
};
if msync_res == 0 {
let mut acc = self.inner.written_since_last_flush.write();
*acc = acc.saturating_sub(optimized_len as u64);
return Ok(());
}
}
let guard = lock.read();
guard
.flush_range(optimized_start, optimized_len)
.map_err(|e| MmapIoError::FlushFailed(e.to_string()))?;
let mut acc = self.inner.written_since_last_flush.write();
*acc = acc.saturating_sub(optimized_len as u64);
Ok(())
}
}
}
pub fn resize(&self, new_size: u64) -> Result<()> {
if self.inner.mode != MmapMode::ReadWrite {
return Err(MmapIoError::InvalidMode("Resize requires ReadWrite mode"));
}
if new_size == 0 {
return Err(MmapIoError::ResizeFailed(
"New size must be greater than zero".into(),
));
}
if new_size > MAX_MMAP_SIZE {
return Err(MmapIoError::ResizeFailed(format!(
"New size {new_size} exceeds maximum safe limit of {MAX_MMAP_SIZE} bytes"
)));
}
let current = self.current_len()?;
#[cfg(windows)]
{
use std::cmp::Ordering;
match new_size.cmp(¤t) {
Ordering::Less => {
*self.inner.cached_len.write() = new_size;
return Ok(());
}
Ordering::Equal => {
return Ok(());
}
Ordering::Greater => {
}
}
}
let _ = ¤t;
self.inner.file.set_len(new_size)?;
let new_map = unsafe { MmapMut::map_mut(&self.inner.file)? };
match &self.inner.map {
MapVariant::Ro(_) => Err(MmapIoError::InvalidMode(
"Cannot remap read-only mapping as read-write",
)),
MapVariant::Cow(_) => Err(MmapIoError::InvalidMode(
"resize not supported on copy-on-write mapping",
)),
MapVariant::Rw(lock) => {
let mut guard = lock.write();
*guard = new_map;
*self.inner.cached_len.write() = new_size;
Ok(())
}
}
}
#[must_use]
pub fn path(&self) -> &Path {
&self.inner.path
}
pub fn touch_pages(&self) -> Result<()> {
use crate::utils::page_size;
let total_len = self.current_len()?;
if total_len == 0 {
return Ok(());
}
let page_sz = page_size();
let total = total_len as usize;
match &self.inner.map {
MapVariant::Ro(m) => {
let base = m.as_ptr();
touch_range_with_ptr(base, 0, total, page_sz);
}
MapVariant::Cow(m) => {
let base = m.as_ptr();
touch_range_with_ptr(base, 0, total, page_sz);
}
MapVariant::Rw(lock) => {
let guard = lock.read();
let base = guard.as_ptr();
touch_range_with_ptr(base, 0, total, page_sz);
drop(guard);
}
}
Ok(())
}
pub fn touch_pages_range(&self, offset: u64, len: u64) -> Result<()> {
use crate::utils::page_size;
if len == 0 {
return Ok(());
}
let total_len = self.current_len()?;
crate::utils::ensure_in_bounds(offset, len, total_len)?;
let page_sz = page_size();
let total = total_len as usize;
let start_page_aligned = (offset as usize / page_sz) * page_sz;
let end_offset = (offset + len) as usize;
let end_page_aligned = std::cmp::min(
crate::utils::align_up(end_offset as u64, page_sz as u64) as usize,
total,
);
if start_page_aligned >= end_page_aligned {
return Ok(());
}
let walk_len = end_page_aligned - start_page_aligned;
match &self.inner.map {
MapVariant::Ro(m) => {
touch_range_with_ptr(m.as_ptr(), start_page_aligned, walk_len, page_sz);
}
MapVariant::Cow(m) => {
touch_range_with_ptr(m.as_ptr(), start_page_aligned, walk_len, page_sz);
}
MapVariant::Rw(lock) => {
let guard = lock.read();
let base = guard.as_ptr();
touch_range_with_ptr(base, start_page_aligned, walk_len, page_sz);
drop(guard);
}
}
Ok(())
}
}
#[inline]
fn touch_range_with_ptr(base: *const u8, start: usize, walk_len: usize, page_sz: usize) {
if walk_len == 0 || page_sz == 0 {
return;
}
let end = start + walk_len;
let mut off = start;
while off < end {
unsafe {
let byte = std::ptr::read_volatile(base.add(off));
std::hint::black_box(byte);
}
off += page_sz;
}
}
impl MemoryMappedFile {
#[cfg(all(unix, target_os = "linux"))]
fn try_linux_async_flush(&self, len: usize) -> Result<bool> {
use std::os::fd::AsRawFd;
let _fd = self.inner.file.as_raw_fd();
match &self.inner.map {
MapVariant::Rw(lock) => {
let guard = lock.read();
let ptr = guard.as_ptr() as *mut libc::c_void;
let ret = unsafe { libc::msync(ptr, len, libc::MS_ASYNC) };
if ret == 0 {
*self.inner.written_since_last_flush.write() = 0;
Ok(true)
} else {
Ok(false)
}
}
_ => Ok(false),
}
}
}
#[cfg(feature = "hugepages")]
fn map_mut_with_options(file: &File, len: u64, huge: bool) -> Result<MmapMut> {
#[cfg(all(unix, target_os = "linux"))]
{
if huge {
if let Ok(mmap) = try_create_optimized_mapping(file, len) {
log::debug!("Successfully created optimized mapping for huge pages");
return Ok(mmap);
}
}
let mmap = unsafe { MmapMut::map_mut(file) }.map_err(MmapIoError::Io)?;
if huge {
unsafe {
let mmap_ptr = mmap.as_ptr() as *mut libc::c_void;
let ret = libc::madvise(mmap_ptr, len as usize, libc::MADV_HUGEPAGE);
if ret == 0 {
log::debug!("Successfully requested THP for {} bytes", len);
} else {
log::debug!("madvise(MADV_HUGEPAGE) failed, using regular pages");
}
}
}
Ok(mmap)
}
#[cfg(not(all(unix, target_os = "linux")))]
{
let _ = (len, huge);
unsafe { MmapMut::map_mut(file) }.map_err(MmapIoError::Io)
}
}
#[cfg(all(unix, target_os = "linux", feature = "hugepages"))]
fn try_create_optimized_mapping(file: &File, len: u64) -> Result<MmapMut> {
const HUGE_PAGE_SIZE: u64 = 2 * 1024 * 1024;
if len >= HUGE_PAGE_SIZE {
let mmap = unsafe { MmapMut::map_mut(file) }.map_err(MmapIoError::Io)?;
unsafe {
let mmap_ptr = mmap.as_ptr() as *mut libc::c_void;
let ret = libc::madvise(mmap_ptr, len as usize, libc::MADV_HUGEPAGE);
if ret == 0 {
#[cfg(target_os = "linux")]
{
const MADV_POPULATE_WRITE: i32 = 23; let populate_ret = libc::madvise(mmap_ptr, len as usize, MADV_POPULATE_WRITE);
if populate_ret == 0 {
log::debug!("Successfully created and populated optimized mapping");
} else {
log::debug!(
"Optimization successful, populate failed (expected on older kernels)"
);
}
}
#[cfg(not(target_os = "linux"))]
{
log::debug!("Successfully created optimized mapping (populate not available)");
}
}
}
Ok(mmap)
} else {
Err(MmapIoError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"File too small for huge page optimization",
)))
}
}
#[cfg(not(all(unix, target_os = "linux", feature = "hugepages")))]
#[allow(dead_code)]
fn try_create_optimized_mapping(_file: &File, _len: u64) -> Result<MmapMut> {
Err(MmapIoError::Io(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Huge pages not supported on this platform",
)))
}
#[cfg(feature = "cow")]
impl MemoryMappedFile {
pub fn open_cow<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_ref = path.as_ref();
let file = OpenOptions::new().read(true).open(path_ref)?;
let len = file.metadata()?.len();
if len == 0 {
return Err(MmapIoError::ResizeFailed(ERR_ZERO_LENGTH_FILE.into()));
}
let mmap = unsafe {
let mut opts = MmapOptions::new();
opts.len(len as usize);
#[cfg(unix)]
{
opts.map(&file)?
}
#[cfg(not(unix))]
{
opts.map(&file)?
}
};
let inner = Inner {
path: path_ref.to_path_buf(),
file,
mode: MmapMode::CopyOnWrite,
cached_len: RwLock::new(len),
map: MapVariant::Cow(mmap),
flush_policy: FlushPolicy::Never,
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: false,
};
Ok(Self {
inner: Arc::new(inner),
})
}
}
impl MemoryMappedFile {
fn apply_flush_policy(&self, written: u64) -> Result<()> {
match self.inner.flush_policy {
FlushPolicy::Never | FlushPolicy::Manual => Ok(()),
FlushPolicy::Always => {
*self.inner.written_since_last_flush.write() += written;
self.flush()
}
FlushPolicy::EveryBytes(n) => {
let n = n as u64;
if n == 0 {
return Ok(());
}
let mut acc = self.inner.written_since_last_flush.write();
*acc += written;
if *acc >= n {
drop(acc);
self.flush()
} else {
Ok(())
}
}
FlushPolicy::EveryWrites(w) => {
if w == 0 {
return Ok(());
}
let mut acc = self.inner.written_since_last_flush.write();
*acc += 1;
if *acc >= w as u64 {
drop(acc);
self.flush()
} else {
Ok(())
}
}
FlushPolicy::EveryMillis(ms) => {
if ms == 0 {
return Ok(());
}
*self.inner.written_since_last_flush.write() += written;
Ok(())
}
}
}
pub fn current_len(&self) -> Result<u64> {
Ok(*self.inner.cached_len.read())
}
pub fn read_into(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let total = self.current_len()?;
let len = buf.len() as u64;
ensure_in_bounds(offset, len, total)?;
match &self.inner.map {
MapVariant::Ro(m) => {
let (start, end) = slice_range(offset, len, total)?;
buf.copy_from_slice(&m[start..end]);
Ok(())
}
MapVariant::Rw(lock) => {
let guard = lock.read();
let (start, end) = slice_range(offset, len, total)?;
buf.copy_from_slice(&guard[start..end]);
Ok(())
}
MapVariant::Cow(m) => {
let (start, end) = slice_range(offset, len, total)?;
buf.copy_from_slice(&m[start..end]);
Ok(())
}
}
}
}
pub struct MemoryMappedFileBuilder {
path: PathBuf,
size: Option<u64>,
mode: Option<MmapMode>,
flush_policy: FlushPolicy,
touch_hint: TouchHint,
#[cfg(feature = "hugepages")]
huge_pages: bool,
}
impl MemoryMappedFileBuilder {
pub fn size(mut self, size: u64) -> Self {
self.size = Some(size);
self
}
pub fn mode(mut self, mode: MmapMode) -> Self {
self.mode = Some(mode);
self
}
pub fn flush_policy(mut self, policy: FlushPolicy) -> Self {
self.flush_policy = policy;
self
}
pub fn touch_hint(mut self, hint: TouchHint) -> Self {
self.touch_hint = hint;
self
}
#[cfg(feature = "hugepages")]
pub fn huge_pages(mut self, enable: bool) -> Self {
self.huge_pages = enable;
self
}
pub fn create(self) -> Result<MemoryMappedFile> {
let mode = self.mode.unwrap_or(MmapMode::ReadWrite);
match mode {
MmapMode::ReadWrite => {
let size = self.size.ok_or_else(|| {
MmapIoError::ResizeFailed(
"Size must be set for create() in ReadWrite mode".into(),
)
})?;
if size == 0 {
return Err(MmapIoError::ResizeFailed(ERR_ZERO_SIZE.into()));
}
if size > MAX_MMAP_SIZE {
return Err(MmapIoError::ResizeFailed(format!(
"Size {size} exceeds maximum safe limit of {MAX_MMAP_SIZE} bytes"
)));
}
let path_ref = &self.path;
let file = OpenOptions::new()
.create(true)
.write(true)
.read(true)
.truncate(true)
.open(path_ref)?;
file.set_len(size)?;
#[cfg(feature = "hugepages")]
let mmap = map_mut_with_options(&file, size, self.huge_pages)?;
#[cfg(not(feature = "hugepages"))]
let mmap = unsafe { MmapMut::map_mut(&file)? };
let inner = Inner {
path: path_ref.clone(),
file,
mode,
cached_len: RwLock::new(size),
map: MapVariant::Rw(RwLock::new(mmap)),
flush_policy: self.flush_policy,
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: self.huge_pages,
};
let mmap_file = MemoryMappedFile {
inner: Arc::new(inner),
};
if let FlushPolicy::EveryMillis(ms) = self.flush_policy {
if ms > 0 {
let inner_weak = Arc::downgrade(&mmap_file.inner);
let flusher = crate::flush::TimeBasedFlusher::new(ms, move || {
let Some(inner) = inner_weak.upgrade() else {
return false;
};
let pending = *inner.written_since_last_flush.read() > 0;
if !pending {
return false;
}
let temp = MemoryMappedFile { inner };
temp.flush().is_ok()
});
*mmap_file.inner.flusher.write() = flusher;
}
}
if self.touch_hint == TouchHint::Eager {
log::debug!("Eagerly touching all pages for {size} bytes");
if let Err(e) = mmap_file.touch_pages() {
log::warn!("Failed to eagerly touch pages: {e}");
}
}
Ok(mmap_file)
}
MmapMode::ReadOnly => {
let path_ref = &self.path;
let file = OpenOptions::new().read(true).open(path_ref)?;
let len = file.metadata()?.len();
let mmap = unsafe { Mmap::map(&file)? };
let inner = Inner {
path: path_ref.clone(),
file,
mode,
cached_len: RwLock::new(len),
map: MapVariant::Ro(mmap),
flush_policy: FlushPolicy::Never,
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: false,
};
Ok(MemoryMappedFile {
inner: Arc::new(inner),
})
}
#[cfg(feature = "cow")]
MmapMode::CopyOnWrite => {
let path_ref = &self.path;
let file = OpenOptions::new().read(true).open(path_ref)?;
let len = file.metadata()?.len();
if len == 0 {
return Err(MmapIoError::ResizeFailed(ERR_ZERO_LENGTH_FILE.into()));
}
let mmap = unsafe {
let mut opts = MmapOptions::new();
opts.len(len as usize);
opts.map(&file)?
};
let inner = Inner {
path: path_ref.clone(),
file,
mode,
cached_len: RwLock::new(len),
map: MapVariant::Cow(mmap),
flush_policy: FlushPolicy::Never,
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: false,
};
Ok(MemoryMappedFile {
inner: Arc::new(inner),
})
}
#[cfg(not(feature = "cow"))]
MmapMode::CopyOnWrite => Err(MmapIoError::InvalidMode(
"CopyOnWrite mode requires 'cow' feature",
)),
}
}
pub fn open(self) -> Result<MemoryMappedFile> {
let mode = self.mode.unwrap_or(MmapMode::ReadOnly);
match mode {
MmapMode::ReadOnly => {
let path_ref = &self.path;
let file = OpenOptions::new().read(true).open(path_ref)?;
let len = file.metadata()?.len();
let mmap = unsafe { Mmap::map(&file)? };
let inner = Inner {
path: path_ref.clone(),
file,
mode,
cached_len: RwLock::new(len),
map: MapVariant::Ro(mmap),
flush_policy: FlushPolicy::Never,
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: false,
};
Ok(MemoryMappedFile {
inner: Arc::new(inner),
})
}
MmapMode::ReadWrite => {
let path_ref = &self.path;
let file = OpenOptions::new().read(true).write(true).open(path_ref)?;
let len = file.metadata()?.len();
if len == 0 {
return Err(MmapIoError::ResizeFailed(ERR_ZERO_LENGTH_FILE.into()));
}
#[cfg(feature = "hugepages")]
let mmap = map_mut_with_options(&file, len, self.huge_pages)?;
#[cfg(not(feature = "hugepages"))]
let mmap = unsafe { MmapMut::map_mut(&file)? };
let inner = Inner {
path: path_ref.clone(),
file,
mode,
cached_len: RwLock::new(len),
map: MapVariant::Rw(RwLock::new(mmap)),
flush_policy: self.flush_policy,
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: self.huge_pages,
};
Ok(MemoryMappedFile {
inner: Arc::new(inner),
})
}
#[cfg(feature = "cow")]
MmapMode::CopyOnWrite => {
let path_ref = &self.path;
let file = OpenOptions::new().read(true).open(path_ref)?;
let len = file.metadata()?.len();
if len == 0 {
return Err(MmapIoError::ResizeFailed(ERR_ZERO_LENGTH_FILE.into()));
}
let mmap = unsafe {
let mut opts = MmapOptions::new();
opts.len(len as usize);
opts.map(&file)?
};
let inner = Inner {
path: path_ref.clone(),
file,
mode,
cached_len: RwLock::new(len),
map: MapVariant::Cow(mmap),
flush_policy: FlushPolicy::Never,
written_since_last_flush: RwLock::new(0),
flusher: RwLock::new(None),
#[cfg(feature = "hugepages")]
huge_pages: false,
};
Ok(MemoryMappedFile {
inner: Arc::new(inner),
})
}
#[cfg(not(feature = "cow"))]
MmapMode::CopyOnWrite => Err(MmapIoError::InvalidMode(
"CopyOnWrite mode requires 'cow' feature",
)),
}
}
}
use parking_lot::{RwLockReadGuard, RwLockWriteGuard};
pub struct MappedSliceMut<'a> {
guard: RwLockWriteGuard<'a, MmapMut>,
range: std::ops::Range<usize>,
}
impl<'a> MappedSliceMut<'a> {
#[allow(clippy::should_implement_trait)]
pub fn as_mut(&mut self) -> &mut [u8] {
let start = self.range.start;
let end = self.range.end;
&mut self.guard[start..end]
}
#[must_use]
pub fn len(&self) -> usize {
self.range.end - self.range.start
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.range.start == self.range.end
}
}
impl std::ops::Deref for MappedSliceMut<'_> {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.guard[self.range.clone()]
}
}
impl std::ops::DerefMut for MappedSliceMut<'_> {
fn deref_mut(&mut self) -> &mut [u8] {
let start = self.range.start;
let end = self.range.end;
&mut self.guard[start..end]
}
}
pub struct MappedSlice<'a> {
inner: MappedSliceInner<'a>,
}
enum MappedSliceInner<'a> {
Owned(&'a [u8]),
Guarded {
guard: RwLockReadGuard<'a, MmapMut>,
range: std::ops::Range<usize>,
},
}
impl<'a> MappedSlice<'a> {
pub(crate) fn owned(slice: &'a [u8]) -> Self {
Self {
inner: MappedSliceInner::Owned(slice),
}
}
pub(crate) fn guarded(
guard: RwLockReadGuard<'a, MmapMut>,
range: std::ops::Range<usize>,
) -> Self {
Self {
inner: MappedSliceInner::Guarded { guard, range },
}
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
match &self.inner {
MappedSliceInner::Owned(s) => s,
MappedSliceInner::Guarded { guard, range } => &guard[range.clone()],
}
}
#[must_use]
pub fn len(&self) -> usize {
match &self.inner {
MappedSliceInner::Owned(s) => s.len(),
MappedSliceInner::Guarded { range, .. } => range.end - range.start,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl std::ops::Deref for MappedSlice<'_> {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.as_slice()
}
}
impl AsRef<[u8]> for MappedSlice<'_> {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl std::fmt::Debug for MappedSlice<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self.as_slice(), f)
}
}
impl PartialEq for MappedSlice<'_> {
fn eq(&self, other: &Self) -> bool {
self.as_slice() == other.as_slice()
}
}
impl Eq for MappedSlice<'_> {}
impl PartialEq<[u8]> for MappedSlice<'_> {
fn eq(&self, other: &[u8]) -> bool {
self.as_slice() == other
}
}
impl PartialEq<&[u8]> for MappedSlice<'_> {
fn eq(&self, other: &&[u8]) -> bool {
self.as_slice() == *other
}
}
impl<const N: usize> PartialEq<[u8; N]> for MappedSlice<'_> {
fn eq(&self, other: &[u8; N]) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<const N: usize> PartialEq<&[u8; N]> for MappedSlice<'_> {
fn eq(&self, other: &&[u8; N]) -> bool {
self.as_slice() == other.as_slice()
}
}