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 = tempfile::NamedTempFile::new().unwrap();
347 let data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
348 file.write_all(&data).unwrap();
349 file.flush().unwrap();
350 (file, data)
351 }
352
353 #[test]
354 fn test_mmap_file_creation() {
355 let (file, _data) = create_test_file();
356 let mmap = MmapFile::new(file.path()).unwrap();
357 assert_eq!(mmap.size(), 1024);
358 assert!(!mmap.is_empty());
359 }
360
361 #[test]
362 fn test_mmap_file_not_found() {
363 let result = MmapFile::new("/nonexistent/file.bin");
364 assert!(result.is_err());
365 match result {
366 Err(MmapError::FileNotFound(_)) => {}
367 _ => panic!("Expected FileNotFound error"),
368 }
369 }
370
371 #[test]
372 fn test_mmap_bytes() {
373 let (file, data) = create_test_file();
374 let mmap = MmapFile::new(file.path()).unwrap();
375 let bytes = mmap.bytes();
376 assert_eq!(bytes.len(), 1024);
377 assert_eq!(&bytes[..], &data[..]);
378 }
379
380 #[test]
381 fn test_mmap_range() {
382 let (file, data) = create_test_file();
383 let mmap = MmapFile::new(file.path()).unwrap();
384
385 let range = mmap.range(10..50).unwrap();
386 assert_eq!(range.len(), 40);
387 assert_eq!(&range[..], &data[10..50]);
388 }
389
390 #[test]
391 fn test_mmap_range_invalid() {
392 let (file, _data) = create_test_file();
393 let mmap = MmapFile::new(file.path()).unwrap();
394
395 assert!(mmap.range(2000..2100).is_err());
397
398 assert!(mmap.range(1000..2000).is_err());
400
401 assert!(mmap.range(100..100).is_err());
403 }
404
405 #[test]
406 fn test_mmap_multi_range() {
407 let (file, data) = create_test_file();
408 let mmap = MmapFile::new(file.path()).unwrap();
409
410 let ranges = vec![0..10, 50..60, 100..120];
411 let results = mmap.multi_range(&ranges).unwrap();
412
413 assert_eq!(results.len(), 3);
414 assert_eq!(&results[0][..], &data[0..10]);
415 assert_eq!(&results[1][..], &data[50..60]);
416 assert_eq!(&results[2][..], &data[100..120]);
417 }
418
419 #[test]
420 fn test_mmap_clone() {
421 let (file, _data) = create_test_file();
422 let mmap1 = MmapFile::new(file.path()).unwrap();
423 let mmap2 = mmap1.clone();
424
425 assert_eq!(mmap1.size(), mmap2.size());
426 assert_eq!(mmap1.path(), mmap2.path());
427 }
428
429 #[test]
430 fn test_mmap_cache() {
431 let (file, _data) = create_test_file();
432 let cache = MmapCache::new(10);
433
434 let mmap1 = cache.get_or_create(file.path()).unwrap();
436 assert_eq!(cache.len(), 1);
437
438 let mmap2 = cache.get_or_create(file.path()).unwrap();
440 assert_eq!(cache.len(), 1);
441
442 assert_eq!(mmap1.size(), mmap2.size());
444 }
445
446 #[test]
447 fn test_mmap_cache_clear() {
448 let (file, _data) = create_test_file();
449 let cache = MmapCache::new(10);
450
451 cache.get_or_create(file.path()).unwrap();
452 assert_eq!(cache.len(), 1);
453
454 cache.clear();
455 assert_eq!(cache.len(), 0);
456 assert!(cache.is_empty());
457 }
458
459 #[test]
460 fn test_mmap_config_presets() {
461 let sequential = MmapConfig::sequential();
462 assert!(sequential.sequential_access);
463 assert!(!sequential.random_access);
464
465 let random = MmapConfig::random();
466 assert!(!random.sequential_access);
467 assert!(random.random_access);
468
469 let hugepages = MmapConfig::hugepages();
470 assert!(hugepages.use_hugepages);
471 assert!(hugepages.populate);
472 }
473}