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::{
63                madvise, MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED,
64            };
65
66            let advice_flag = match advice {
67                MmapAdvice::Normal => MADV_NORMAL,
68                MmapAdvice::Random => MADV_RANDOM,
69                MmapAdvice::Sequential => MADV_SEQUENTIAL,
70                MmapAdvice::WillNeed => MADV_WILLNEED,
71                MmapAdvice::DontNeed => MADV_DONTNEED,
72            };
73
74            // SAFETY: madvise is safe to call with validated parameters
75            let result = unsafe { madvise(addr as *mut libc::c_void, length, advice_flag) };
76
77            if result != 0 {
78                let err = std::io::Error::last_os_error();
79                return Err(MmapIoError::AdviceFailed(format!("madvise failed: {err}")));
80            }
81        }
82
83        #[cfg(windows)]
84        {
85            // Windows only supports prefetching (WillNeed equivalent)
86            if matches!(advice, MmapAdvice::WillNeed) {
87                use std::mem;
88                use std::ptr;
89
90                #[allow(non_snake_case)]
91                #[repr(C)]
92                struct WIN32_MEMORY_RANGE_ENTRY {
93                    VirtualAddress: *mut core::ffi::c_void,
94                    NumberOfBytes: usize,
95                }
96
97                extern "system" {
98                    fn PrefetchVirtualMemory(
99                        hProcess: *mut core::ffi::c_void,
100                        NumberOfEntries: usize,
101                        VirtualAddresses: *const WIN32_MEMORY_RANGE_ENTRY,
102                        Flags: u32,
103                    ) -> i32;
104
105                    fn GetCurrentProcess() -> *mut core::ffi::c_void;
106                }
107
108                let entry = WIN32_MEMORY_RANGE_ENTRY {
109                    VirtualAddress: addr as *mut core::ffi::c_void,
110                    NumberOfBytes: length,
111                };
112
113                // SAFETY: PrefetchVirtualMemory is safe with valid memory range
114                let result = unsafe {
115                    PrefetchVirtualMemory(
116                        GetCurrentProcess(),
117                        1,
118                        &entry,
119                        0, // No special flags
120                    )
121                };
122
123                if result == 0 {
124                    let err = std::io::Error::last_os_error();
125                    return Err(MmapIoError::AdviceFailed(format!(
126                        "PrefetchVirtualMemory failed: {err}"
127                    )));
128                }
129            }
130            // Other advice types are no-ops on Windows
131        }
132
133        Ok(())
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::create_mmap;
141    use std::fs;
142    use std::path::PathBuf;
143
144    fn tmp_path(name: &str) -> PathBuf {
145        let mut p = std::env::temp_dir();
146        p.push(format!(
147            "mmap_io_advise_test_{}_{}",
148            name,
149            std::process::id()
150        ));
151        p
152    }
153
154    #[test]
155    #[cfg(feature = "advise")]
156    fn test_advise_operations() {
157        // Skip test on unsupported platforms
158        if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
159            eprintln!("Skipping madvise test on unsupported platform");
160            return;
161        }
162
163        use crate::mmap::MemoryMappedFile;
164
165        let file_path = "test_advise_ops.tmp";
166        std::fs::write(file_path, [0u8; 4096]).unwrap();
167
168        // Use create_rw to open the file in read-write mode
169        let file = MemoryMappedFile::create_rw(file_path, 4096).unwrap();
170
171        // Validate alignment without borrowing a slice from RW mapping.
172        // The mapping base offset is 0 which is page-aligned by construction.
173        let page = crate::utils::page_size();
174        assert_eq!(0 % page, 0, "Mapping base offset must be page-aligned");
175
176        // Call advise on full region
177        let len = file.len();
178        file.advise(0, len, MmapAdvice::Sequential)
179            .expect("memory advice sequential failed");
180
181        std::fs::remove_file(file_path).unwrap();
182    }
183
184    #[test]
185    #[cfg(feature = "advise")]
186    fn test_advise_with_different_modes() {
187        let path = tmp_path("advise_modes");
188        let _ = fs::remove_file(&path);
189
190        // Create and test with RW mode
191        let mmap = create_mmap(&path, 4096).expect("create");
192        mmap.advise(0, 4096, MmapAdvice::Sequential)
193            .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)
205                .expect("cow advise");
206        }
207
208        fs::remove_file(&path).expect("cleanup");
209    }
210}