use bytes::Bytes;
use memmap2::Mmap;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MmapError {
#[error("Failed to open file: {0}")]
FileOpen(#[from] io::Error),
#[error("Failed to create memory map: {0}")]
MmapCreation(String),
#[error("Invalid range: {0}")]
InvalidRange(String),
#[error("File not found: {0}")]
FileNotFound(String),
}
pub struct MmapFile {
mmap: Arc<Mmap>,
path: PathBuf,
size: usize,
}
impl MmapFile {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, MmapError> {
let path = path.as_ref();
let file = File::open(path).map_err(|e| {
if e.kind() == io::ErrorKind::NotFound {
MmapError::FileNotFound(path.display().to_string())
} else {
MmapError::FileOpen(e)
}
})?;
let metadata = file.metadata()?;
let size = metadata.len() as usize;
let mmap = unsafe { Mmap::map(&file).map_err(|e| MmapError::MmapCreation(e.to_string()))? };
Ok(MmapFile {
mmap: Arc::new(mmap),
path: path.to_path_buf(),
size,
})
}
pub fn bytes(&self) -> Bytes {
Bytes::copy_from_slice(&self.mmap[..])
}
pub fn range(&self, range: std::ops::Range<usize>) -> Result<Bytes, MmapError> {
if range.start > self.size {
return Err(MmapError::InvalidRange(format!(
"Start {} exceeds file size {}",
range.start, self.size
)));
}
if range.end > self.size {
return Err(MmapError::InvalidRange(format!(
"End {} exceeds file size {}",
range.end, self.size
)));
}
if range.start >= range.end {
return Err(MmapError::InvalidRange(format!(
"Invalid range: {}..{}",
range.start, range.end
)));
}
Ok(Bytes::copy_from_slice(&self.mmap[range]))
}
pub fn size(&self) -> usize {
self.size
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn multi_range(&self, ranges: &[std::ops::Range<usize>]) -> Result<Vec<Bytes>, MmapError> {
let mut results = Vec::with_capacity(ranges.len());
for range in ranges {
results.push(self.range(range.clone())?);
}
Ok(results)
}
}
impl Clone for MmapFile {
fn clone(&self) -> Self {
MmapFile {
mmap: Arc::clone(&self.mmap),
path: self.path.clone(),
size: self.size,
}
}
}
#[allow(dead_code)]
pub struct MmapCache {
max_entries: usize,
cache: dashmap::DashMap<PathBuf, Arc<MmapFile>>,
}
impl MmapCache {
pub fn new(max_entries: usize) -> Self {
MmapCache {
max_entries,
cache: dashmap::DashMap::new(),
}
}
pub fn get_or_create<P: AsRef<Path>>(&self, path: P) -> Result<Arc<MmapFile>, MmapError> {
let path = path.as_ref();
if let Some(cached) = self.cache.get(path) {
return Ok(Arc::clone(&*cached));
}
let mmap_file = Arc::new(MmapFile::new(path)?);
if self.cache.len() >= self.max_entries {
tracing::warn!(
"Mmap cache size {} exceeds max {}",
self.cache.len(),
self.max_entries
);
}
self.cache
.insert(path.to_path_buf(), Arc::clone(&mmap_file));
Ok(mmap_file)
}
pub fn clear(&self) {
self.cache.clear();
}
pub fn len(&self) -> usize {
self.cache.len()
}
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
pub struct MmapConfig {
pub use_hugepages: bool,
pub sequential_access: bool,
pub random_access: bool,
pub populate: bool,
}
impl Default for MmapConfig {
fn default() -> Self {
MmapConfig {
use_hugepages: false,
sequential_access: true,
random_access: false,
populate: false,
}
}
}
impl MmapConfig {
pub fn sequential() -> Self {
MmapConfig {
use_hugepages: false,
sequential_access: true,
random_access: false,
populate: false,
}
}
pub fn random() -> Self {
MmapConfig {
use_hugepages: false,
sequential_access: false,
random_access: true,
populate: false,
}
}
pub fn hugepages() -> Self {
MmapConfig {
use_hugepages: true,
sequential_access: false,
random_access: false,
populate: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn create_test_file() -> (tempfile::NamedTempFile, Vec<u8>) {
let mut file = tempfile::NamedTempFile::new().unwrap();
let data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
file.write_all(&data).unwrap();
file.flush().unwrap();
(file, data)
}
#[test]
fn test_mmap_file_creation() {
let (file, _data) = create_test_file();
let mmap = MmapFile::new(file.path()).unwrap();
assert_eq!(mmap.size(), 1024);
assert!(!mmap.is_empty());
}
#[test]
fn test_mmap_file_not_found() {
let result = MmapFile::new("/nonexistent/file.bin");
assert!(result.is_err());
match result {
Err(MmapError::FileNotFound(_)) => {}
_ => panic!("Expected FileNotFound error"),
}
}
#[test]
fn test_mmap_bytes() {
let (file, data) = create_test_file();
let mmap = MmapFile::new(file.path()).unwrap();
let bytes = mmap.bytes();
assert_eq!(bytes.len(), 1024);
assert_eq!(&bytes[..], &data[..]);
}
#[test]
fn test_mmap_range() {
let (file, data) = create_test_file();
let mmap = MmapFile::new(file.path()).unwrap();
let range = mmap.range(10..50).unwrap();
assert_eq!(range.len(), 40);
assert_eq!(&range[..], &data[10..50]);
}
#[test]
fn test_mmap_range_invalid() {
let (file, _data) = create_test_file();
let mmap = MmapFile::new(file.path()).unwrap();
assert!(mmap.range(2000..2100).is_err());
assert!(mmap.range(1000..2000).is_err());
assert!(mmap.range(100..100).is_err());
}
#[test]
fn test_mmap_multi_range() {
let (file, data) = create_test_file();
let mmap = MmapFile::new(file.path()).unwrap();
let ranges = vec![0..10, 50..60, 100..120];
let results = mmap.multi_range(&ranges).unwrap();
assert_eq!(results.len(), 3);
assert_eq!(&results[0][..], &data[0..10]);
assert_eq!(&results[1][..], &data[50..60]);
assert_eq!(&results[2][..], &data[100..120]);
}
#[test]
fn test_mmap_clone() {
let (file, _data) = create_test_file();
let mmap1 = MmapFile::new(file.path()).unwrap();
let mmap2 = mmap1.clone();
assert_eq!(mmap1.size(), mmap2.size());
assert_eq!(mmap1.path(), mmap2.path());
}
#[test]
fn test_mmap_cache() {
let (file, _data) = create_test_file();
let cache = MmapCache::new(10);
let mmap1 = cache.get_or_create(file.path()).unwrap();
assert_eq!(cache.len(), 1);
let mmap2 = cache.get_or_create(file.path()).unwrap();
assert_eq!(cache.len(), 1);
assert_eq!(mmap1.size(), mmap2.size());
}
#[test]
fn test_mmap_cache_clear() {
let (file, _data) = create_test_file();
let cache = MmapCache::new(10);
cache.get_or_create(file.path()).unwrap();
assert_eq!(cache.len(), 1);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_mmap_config_presets() {
let sequential = MmapConfig::sequential();
assert!(sequential.sequential_access);
assert!(!sequential.random_access);
let random = MmapConfig::random();
assert!(!random.sequential_access);
assert!(random.random_access);
let hugepages = MmapConfig::hugepages();
assert!(hugepages.use_hugepages);
assert!(hugepages.populate);
}
}