mmap_io/
advise.rs

1//! Memory advise operations for optimizing OS behavior.
2
3use crate::errors::{MmapIoError, Result};
4use crate::mmap::MemoryMappedFile;
5use crate::utils::slice_range;
6
7/// Memory access pattern advice for the OS.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum MmapAdvice {
10    /// Normal access pattern (default).
11    Normal,
12    /// Random access pattern.
13    Random,
14    /// Sequential access pattern.
15    Sequential,
16    /// Will need this range soon.
17    WillNeed,
18    /// Won't need this range soon.
19    DontNeed,
20}
21
22impl MemoryMappedFile {
23    /// Advise the OS about expected access patterns for a memory range.
24    ///
25    /// This can help the OS optimize memory management, prefetching, and caching.
26    /// The advice is a hint and may be ignored by the OS.
27    ///
28    /// # Platform-specific behavior
29    ///
30    /// - **Unix**: Uses `madvise` system call
31    /// - **Windows**: Uses `PrefetchVirtualMemory` for `WillNeed`, no-op for others
32    ///
33    /// # Errors
34    ///
35    /// Returns `MmapIoError::OutOfBounds` if the range exceeds file bounds.
36    /// Returns `MmapIoError::AdviceFailed` if the system call fails.
37    #[cfg(feature = "advise")]
38    pub fn advise(&self, offset: u64, len: u64, advice: MmapAdvice) -> Result<()> {
39        if len == 0 {
40            return Ok(());
41        }
42
43        let total = self.current_len()?;
44        let (start, end) = slice_range(offset, len, total)?;
45        let length = end - start;
46
47        // Get the base pointer for the mapping
48        let ptr = match &self.inner.map {
49            crate::mmap::MapVariant::Ro(m) => m.as_ptr(),
50            crate::mmap::MapVariant::Rw(lock) => {
51                let guard = lock.read();
52                guard.as_ptr()
53            }
54            crate::mmap::MapVariant::Cow(m) => m.as_ptr(),
55        };
56
57        // SAFETY: We've validated the range is within bounds
58        let addr = unsafe { ptr.add(start) };
59
60        #[cfg(unix)]
61        {
62            use libc::{madvise, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, MADV_DONTNEED};
63            
64            let advice_flag = match advice {
65                MmapAdvice::Normal => MADV_NORMAL,
66                MmapAdvice::Random => MADV_RANDOM,
67                MmapAdvice::Sequential => MADV_SEQUENTIAL,
68                MmapAdvice::WillNeed => MADV_WILLNEED,
69                MmapAdvice::DontNeed => MADV_DONTNEED,
70            };
71
72            // SAFETY: madvise is safe to call with validated parameters
73            let result = unsafe {
74                madvise(
75                    addr as *mut libc::c_void,
76                    length,
77                    advice_flag,
78                )
79            };
80
81            if result != 0 {
82                let err = std::io::Error::last_os_error();
83                return Err(MmapIoError::AdviceFailed(format!(
84                    "madvise failed: {err}"
85                )));
86            }
87        }
88
89        #[cfg(windows)]
90        {
91            // Windows only supports prefetching (WillNeed equivalent)
92            if matches!(advice, MmapAdvice::WillNeed) {
93                use std::mem;
94                use std::ptr;
95
96                #[allow(non_snake_case)]
97                #[repr(C)]
98                struct WIN32_MEMORY_RANGE_ENTRY {
99                    VirtualAddress: *mut core::ffi::c_void,
100                    NumberOfBytes: usize,
101                }
102
103                extern "system" {
104                    fn PrefetchVirtualMemory(
105                        hProcess: *mut core::ffi::c_void,
106                        NumberOfEntries: usize,
107                        VirtualAddresses: *const WIN32_MEMORY_RANGE_ENTRY,
108                        Flags: u32,
109                    ) -> i32;
110
111                    fn GetCurrentProcess() -> *mut core::ffi::c_void;
112                }
113
114                let entry = WIN32_MEMORY_RANGE_ENTRY {
115                    VirtualAddress: addr as *mut core::ffi::c_void,
116                    NumberOfBytes: length,
117                };
118
119                // SAFETY: PrefetchVirtualMemory is safe with valid memory range
120                let result = unsafe {
121                    PrefetchVirtualMemory(
122                        GetCurrentProcess(),
123                        1,
124                        &entry,
125                        0, // No special flags
126                    )
127                };
128
129                if result == 0 {
130                    let err = std::io::Error::last_os_error();
131                    return Err(MmapIoError::AdviceFailed(format!(
132                        "PrefetchVirtualMemory failed: {err}"
133                    )));
134                }
135            }
136            // Other advice types are no-ops on Windows
137        }
138
139        Ok(())
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use crate::create_mmap;
147    use std::fs;
148    use std::path::PathBuf;
149
150    fn tmp_path(name: &str) -> PathBuf {
151        let mut p = std::env::temp_dir();
152        p.push(format!("mmap_io_advise_test_{}_{}", name, std::process::id()));
153        p
154    }
155
156    #[test]
157    #[cfg(feature = "advise")]
158    fn test_advise_operations() {
159        // Skip test on unsupported platforms
160        if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
161            eprintln!("Skipping madvise test on unsupported platform");
162            return;
163        }
164
165        use crate::mmap::MemoryMappedFile;
166
167        let file_path = "test_advise_ops.tmp";
168        std::fs::write(file_path, [0u8; 4096]).unwrap();
169
170        // Use create_rw to open the file in read-write mode
171        let file = MemoryMappedFile::create_rw(file_path, 4096).unwrap();
172
173        // Validate alignment without borrowing a slice from RW mapping.
174        // The mapping base offset is 0 which is page-aligned by construction.
175        let page = crate::utils::page_size();
176        assert_eq!(0 % page, 0, "Mapping base offset must be page-aligned");
177
178        // Call advise on full region
179        let len = file.len();
180        file.advise(0, len, MmapAdvice::Sequential).expect("memory advice sequential failed");
181
182        std::fs::remove_file(file_path).unwrap();
183    }
184
185    #[test]
186    #[cfg(feature = "advise")]
187    fn test_advise_with_different_modes() {
188        let path = tmp_path("advise_modes");
189        let _ = fs::remove_file(&path);
190
191        // Create and test with RW mode
192        let mmap = create_mmap(&path, 4096).expect("create");
193        mmap.advise(0, 4096, MmapAdvice::Sequential).expect("rw advise");
194        drop(mmap);
195
196        // Test with RO mode
197        let mmap = MemoryMappedFile::open_ro(&path).expect("open ro");
198        mmap.advise(0, 4096, MmapAdvice::Random).expect("ro advise");
199
200        #[cfg(feature = "cow")]
201        {
202            // Test with COW mode
203            let mmap = MemoryMappedFile::open_cow(&path).expect("open cow");
204            mmap.advise(0, 4096, MmapAdvice::WillNeed).expect("cow advise");
205        }
206
207        fs::remove_file(&path).expect("cleanup");
208    }
209}