1use crate::errors::{MmapIoError, Result};
4use crate::mmap::MemoryMappedFile;
5use crate::utils::slice_range;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum MmapAdvice {
10 Normal,
12 Random,
14 Sequential,
16 WillNeed,
18 DontNeed,
20}
21
22impl MemoryMappedFile {
23 #[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 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 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 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 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 let result = unsafe {
115 PrefetchVirtualMemory(
116 GetCurrentProcess(),
117 1,
118 &entry,
119 0, )
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 }
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 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 let file = MemoryMappedFile::create_rw(file_path, 4096).unwrap();
170
171 let page = crate::utils::page_size();
174 assert_eq!(0 % page, 0, "Mapping base offset must be page-aligned");
175
176 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 let mmap = create_mmap(&path, 4096).expect("create");
192 mmap.advise(0, 4096, MmapAdvice::Sequential)
193 .expect("rw advise");
194 drop(mmap);
195
196 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 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}