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::{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 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 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 let result = unsafe {
121 PrefetchVirtualMemory(
122 GetCurrentProcess(),
123 1,
124 &entry,
125 0, )
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 }
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 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 let file = MemoryMappedFile::create_rw(file_path, 4096).unwrap();
172
173 let page = crate::utils::page_size();
176 assert_eq!(0 % page, 0, "Mapping base offset must be page-aligned");
177
178 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 let mmap = create_mmap(&path, 4096).expect("create");
193 mmap.advise(0, 4096, MmapAdvice::Sequential).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).expect("cow advise");
205 }
206
207 fs::remove_file(&path).expect("cleanup");
208 }
209}