1use bytes::Bytes;
20use memmap2::Mmap;
21use std::fs::File;
22use std::io;
23use std::path::{Path, PathBuf};
24use std::sync::Arc;
25use thiserror::Error;
26
27#[derive(Debug, Error)]
33pub enum MmapError {
34 #[error("Failed to open file: {0}")]
35 FileOpen(#[from] io::Error),
36
37 #[error("Failed to create memory map: {0}")]
38 MmapCreation(String),
39
40 #[error("Invalid range: {0}")]
41 InvalidRange(String),
42
43 #[error("File not found: {0}")]
44 FileNotFound(String),
45}
46
47pub struct MmapFile {
56 mmap: Arc<Mmap>,
58 path: PathBuf,
60 size: usize,
62}
63
64impl MmapFile {
65 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, MmapError> {
82 let path = path.as_ref();
83
84 let file = File::open(path).map_err(|e| {
86 if e.kind() == io::ErrorKind::NotFound {
87 MmapError::FileNotFound(path.display().to_string())
88 } else {
89 MmapError::FileOpen(e)
90 }
91 })?;
92
93 let metadata = file.metadata()?;
95 let size = metadata.len() as usize;
96
97 let mmap = unsafe { Mmap::map(&file).map_err(|e| MmapError::MmapCreation(e.to_string()))? };
100
101 Ok(MmapFile {
102 mmap: Arc::new(mmap),
103 path: path.to_path_buf(),
104 size,
105 })
106 }
107
108 pub fn bytes(&self) -> Bytes {
113 Bytes::copy_from_slice(&self.mmap[..])
114 }
115
116 pub fn range(&self, range: std::ops::Range<usize>) -> Result<Bytes, MmapError> {
126 if range.start > self.size {
127 return Err(MmapError::InvalidRange(format!(
128 "Start {} exceeds file size {}",
129 range.start, self.size
130 )));
131 }
132
133 if range.end > self.size {
134 return Err(MmapError::InvalidRange(format!(
135 "End {} exceeds file size {}",
136 range.end, self.size
137 )));
138 }
139
140 if range.start >= range.end {
141 return Err(MmapError::InvalidRange(format!(
142 "Invalid range: {}..{}",
143 range.start, range.end
144 )));
145 }
146
147 Ok(Bytes::copy_from_slice(&self.mmap[range]))
148 }
149
150 pub fn size(&self) -> usize {
152 self.size
153 }
154
155 pub fn path(&self) -> &Path {
157 &self.path
158 }
159
160 pub fn is_empty(&self) -> bool {
162 self.size == 0
163 }
164
165 pub fn multi_range(&self, ranges: &[std::ops::Range<usize>]) -> Result<Vec<Bytes>, MmapError> {
175 let mut results = Vec::with_capacity(ranges.len());
176
177 for range in ranges {
178 results.push(self.range(range.clone())?);
179 }
180
181 Ok(results)
182 }
183}
184
185impl Clone for MmapFile {
187 fn clone(&self) -> Self {
188 MmapFile {
189 mmap: Arc::clone(&self.mmap),
190 path: self.path.clone(),
191 size: self.size,
192 }
193 }
194}
195
196#[allow(dead_code)]
205pub struct MmapCache {
206 max_entries: usize,
208 cache: dashmap::DashMap<PathBuf, Arc<MmapFile>>,
210}
211
212impl MmapCache {
213 pub fn new(max_entries: usize) -> Self {
219 MmapCache {
220 max_entries,
221 cache: dashmap::DashMap::new(),
222 }
223 }
224
225 pub fn get_or_create<P: AsRef<Path>>(&self, path: P) -> Result<Arc<MmapFile>, MmapError> {
230 let path = path.as_ref();
231
232 if let Some(cached) = self.cache.get(path) {
234 return Ok(Arc::clone(&*cached));
235 }
236
237 let mmap_file = Arc::new(MmapFile::new(path)?);
239
240 if self.cache.len() >= self.max_entries {
242 tracing::warn!(
245 "Mmap cache size {} exceeds max {}",
246 self.cache.len(),
247 self.max_entries
248 );
249 }
250
251 self.cache
252 .insert(path.to_path_buf(), Arc::clone(&mmap_file));
253
254 Ok(mmap_file)
255 }
256
257 pub fn clear(&self) {
259 self.cache.clear();
260 }
261
262 pub fn len(&self) -> usize {
264 self.cache.len()
265 }
266
267 pub fn is_empty(&self) -> bool {
269 self.cache.is_empty()
270 }
271}
272
273#[derive(Debug, Clone, Copy)]
279pub struct MmapConfig {
280 pub use_hugepages: bool,
282
283 pub sequential_access: bool,
285
286 pub random_access: bool,
288
289 pub populate: bool,
291}
292
293impl Default for MmapConfig {
294 fn default() -> Self {
295 MmapConfig {
296 use_hugepages: false,
297 sequential_access: true,
298 random_access: false,
299 populate: false,
300 }
301 }
302}
303
304impl MmapConfig {
305 pub fn sequential() -> Self {
307 MmapConfig {
308 use_hugepages: false,
309 sequential_access: true,
310 random_access: false,
311 populate: false,
312 }
313 }
314
315 pub fn random() -> Self {
317 MmapConfig {
318 use_hugepages: false,
319 sequential_access: false,
320 random_access: true,
321 populate: false,
322 }
323 }
324
325 pub fn hugepages() -> Self {
327 MmapConfig {
328 use_hugepages: true,
329 sequential_access: false,
330 random_access: false,
331 populate: true,
332 }
333 }
334}
335
336#[cfg(test)]
341mod tests {
342 use super::*;
343 use std::io::Write;
344
345 fn create_test_file() -> (tempfile::NamedTempFile, Vec<u8>) {
346 let mut file =
347 tempfile::NamedTempFile::new().expect("test: temp file creation should succeed");
348 let data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
349 file.write_all(&data)
350 .expect("test: write to temp file should succeed");
351 file.flush()
352 .expect("test: flush to temp file should succeed");
353 (file, data)
354 }
355
356 #[test]
357 fn test_mmap_file_creation() {
358 let (file, _data) = create_test_file();
359 let mmap = MmapFile::new(file.path()).expect("test: mmap creation should succeed");
360 assert_eq!(mmap.size(), 1024);
361 assert!(!mmap.is_empty());
362 }
363
364 #[test]
365 fn test_mmap_file_not_found() {
366 let result = MmapFile::new("/nonexistent/file.bin");
367 assert!(result.is_err());
368 match result {
369 Err(MmapError::FileNotFound(_)) => {}
370 _ => panic!("Expected FileNotFound error"),
371 }
372 }
373
374 #[test]
375 fn test_mmap_bytes() {
376 let (file, data) = create_test_file();
377 let mmap = MmapFile::new(file.path()).expect("test: mmap creation should succeed");
378 let bytes = mmap.bytes();
379 assert_eq!(bytes.len(), 1024);
380 assert_eq!(&bytes[..], &data[..]);
381 }
382
383 #[test]
384 fn test_mmap_range() {
385 let (file, data) = create_test_file();
386 let mmap = MmapFile::new(file.path()).expect("test: mmap creation should succeed");
387
388 let range = mmap
389 .range(10..50)
390 .expect("test: range slice should succeed");
391 assert_eq!(range.len(), 40);
392 assert_eq!(&range[..], &data[10..50]);
393 }
394
395 #[test]
396 fn test_mmap_range_invalid() {
397 let (file, _data) = create_test_file();
398 let mmap = MmapFile::new(file.path()).expect("test: mmap creation should succeed");
399
400 assert!(mmap.range(2000..2100).is_err());
402
403 assert!(mmap.range(1000..2000).is_err());
405
406 assert!(mmap.range(100..100).is_err());
408 }
409
410 #[test]
411 fn test_mmap_multi_range() {
412 let (file, data) = create_test_file();
413 let mmap = MmapFile::new(file.path()).expect("test: mmap creation should succeed");
414
415 let ranges = vec![0..10, 50..60, 100..120];
416 let results = mmap
417 .multi_range(&ranges)
418 .expect("test: multi_range should succeed");
419
420 assert_eq!(results.len(), 3);
421 assert_eq!(&results[0][..], &data[0..10]);
422 assert_eq!(&results[1][..], &data[50..60]);
423 assert_eq!(&results[2][..], &data[100..120]);
424 }
425
426 #[test]
427 fn test_mmap_clone() {
428 let (file, _data) = create_test_file();
429 let mmap1 = MmapFile::new(file.path()).expect("test: mmap creation should succeed");
430 let mmap2 = mmap1.clone();
431
432 assert_eq!(mmap1.size(), mmap2.size());
433 assert_eq!(mmap1.path(), mmap2.path());
434 }
435
436 #[test]
437 fn test_mmap_cache() {
438 let (file, _data) = create_test_file();
439 let cache = MmapCache::new(10);
440
441 let mmap1 = cache
443 .get_or_create(file.path())
444 .expect("test: cache get_or_create should succeed");
445 assert_eq!(cache.len(), 1);
446
447 let mmap2 = cache
449 .get_or_create(file.path())
450 .expect("test: cache get_or_create should succeed");
451 assert_eq!(cache.len(), 1);
452
453 assert_eq!(mmap1.size(), mmap2.size());
455 }
456
457 #[test]
458 fn test_mmap_cache_clear() {
459 let (file, _data) = create_test_file();
460 let cache = MmapCache::new(10);
461
462 cache
463 .get_or_create(file.path())
464 .expect("test: cache get_or_create should succeed");
465 assert_eq!(cache.len(), 1);
466
467 cache.clear();
468 assert_eq!(cache.len(), 0);
469 assert!(cache.is_empty());
470 }
471
472 #[test]
473 fn test_mmap_config_presets() {
474 let sequential = MmapConfig::sequential();
475 assert!(sequential.sequential_access);
476 assert!(!sequential.random_access);
477
478 let random = MmapConfig::random();
479 assert!(!random.sequential_access);
480 assert!(random.random_access);
481
482 let hugepages = MmapConfig::hugepages();
483 assert!(hugepages.use_hugepages);
484 assert!(hugepages.populate);
485 }
486}