hermes_core/segment/
pin.rs1use std::sync::{Arc, OnceLock};
15
16use crate::directories::OwnedBytes;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PinMode {
21 Mlock,
25 Copy,
28}
29
30#[derive(Debug, Clone, Copy)]
32pub struct PinPolicy {
33 pub budget_bytes: u64,
35 pub mode: PinMode,
36}
37
38impl PinPolicy {
39 pub const fn disabled() -> Self {
40 Self {
41 budget_bytes: 0,
42 mode: PinMode::Mlock,
43 }
44 }
45
46 pub fn is_enabled(&self) -> bool {
47 self.budget_bytes > 0
48 }
49
50 pub fn from_env() -> Self {
58 let budget_mb: u64 = std::env::var("HERMES_PIN_METADATA_BUDGET_MB")
59 .ok()
60 .and_then(|v| v.parse().ok())
61 .unwrap_or(0);
62 let mode = match std::env::var("HERMES_PIN_MODE").as_deref() {
63 Ok("copy") => PinMode::Copy,
64 Ok("mlock") | Err(_) => PinMode::Mlock,
65 Ok(other) => {
66 log::warn!("HERMES_PIN_MODE '{}' unknown; using mlock", other);
67 PinMode::Mlock
68 }
69 };
70 Self {
71 budget_bytes: budget_mb * 1024 * 1024,
72 mode,
73 }
74 }
75}
76
77static PIN_POLICY: OnceLock<PinPolicy> = OnceLock::new();
78
79pub fn set_pin_policy(policy: PinPolicy) -> bool {
83 let ok = PIN_POLICY.set(policy).is_ok();
84 if !ok {
85 log::warn!("pin policy already initialized; set_pin_policy ignored");
86 }
87 ok
88}
89
90pub fn pin_policy() -> &'static PinPolicy {
92 PIN_POLICY.get_or_init(PinPolicy::from_env)
93}
94
95#[derive(Debug, Default, Clone, Copy)]
97pub struct PinReport {
98 pub intended_bytes: u64,
100 pub pinned_bytes: u64,
102 pub skipped_budget_bytes: u64,
104 pub failed_bytes: u64,
106 pub heap_copy_bytes: u64,
109}
110
111struct HeapPinGuard {
116 page_start: *mut libc::c_void,
117 page_len: usize,
118}
119
120unsafe impl Send for HeapPinGuard {}
123unsafe impl Sync for HeapPinGuard {}
124
125impl Drop for HeapPinGuard {
126 fn drop(&mut self) {
127 if unsafe { libc::munlock(self.page_start, self.page_len) } != 0 {
128 log::warn!(
129 "[pin] munlock failed for {} of ANN heap: {}",
130 crate::format_bytes(self.page_len as u64),
131 std::io::Error::last_os_error()
132 );
133 }
134 }
135}
136
137#[derive(Default)]
140pub(crate) struct HeapPinSet {
141 guards: Vec<HeapPinGuard>,
142 owners: Vec<Arc<dyn std::any::Any + Send + Sync>>,
145 report: PinReport,
146}
147
148impl HeapPinSet {
149 pub(crate) fn report(&self) -> PinReport {
150 self.report
151 }
152
153 pub(crate) fn retain_owner<T: std::any::Any + Send + Sync>(&mut self, owner: Arc<T>) {
154 self.owners.push(owner);
155 }
156
157 pub(crate) fn pin_slice<T>(
162 &mut self,
163 slice: &[T],
164 label: &str,
165 mode: PinMode,
166 remaining: &mut u64,
167 ) {
168 let len = std::mem::size_of_val(slice);
169 if len == 0 {
170 return;
171 }
172 let Ok(len_u64) = u64::try_from(len) else {
173 self.report.failed_bytes = u64::MAX;
174 log::warn!("[pin] ANN region {label} is too large to account");
175 return;
176 };
177 self.report.intended_bytes = self.report.intended_bytes.saturating_add(len_u64);
178 if len_u64 > *remaining {
179 self.report.skipped_budget_bytes =
180 self.report.skipped_budget_bytes.saturating_add(len_u64);
181 log::debug!(
182 "[pin] ANN budget exhausted: skipping {} ({}, {} remaining)",
183 label,
184 crate::format_bytes(len_u64),
185 crate::format_bytes(*remaining)
186 );
187 return;
188 }
189
190 if mode == PinMode::Copy {
191 *remaining -= len_u64;
192 self.report.pinned_bytes = self.report.pinned_bytes.saturating_add(len_u64);
193 return;
194 }
195
196 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
197 let page_size = usize::try_from(page_size).ok().filter(|&size| size > 0);
198 let Some(page_size) = page_size else {
199 self.report.failed_bytes = self.report.failed_bytes.saturating_add(len_u64);
200 log::warn!("[pin] cannot determine page size while locking {label}");
201 return;
202 };
203 let address = slice.as_ptr() as usize;
204 let page_start = address / page_size * page_size;
205 let Some(end) = address.checked_add(len) else {
206 self.report.failed_bytes = self.report.failed_bytes.saturating_add(len_u64);
207 log::warn!("[pin] ANN region address overflow while locking {label}");
208 return;
209 };
210 let Some(rounded_end) = end
211 .checked_add(page_size - 1)
212 .map(|value| value / page_size * page_size)
213 else {
214 self.report.failed_bytes = self.report.failed_bytes.saturating_add(len_u64);
215 log::warn!("[pin] ANN region page range overflow while locking {label}");
216 return;
217 };
218 let page_len = rounded_end - page_start;
219 let page_start = page_start as *mut libc::c_void;
220 if unsafe { libc::mlock(page_start.cast_const(), page_len) } == 0 {
221 self.guards.push(HeapPinGuard {
222 page_start,
223 page_len,
224 });
225 *remaining -= len_u64;
226 self.report.pinned_bytes = self.report.pinned_bytes.saturating_add(len_u64);
227 } else {
228 self.report.failed_bytes = self.report.failed_bytes.saturating_add(len_u64);
229 log::warn!(
230 "[pin] mlock failed for ANN {} ({}): {} — check RLIMIT_MEMLOCK/CAP_IPC_LOCK; continuing unpinned",
231 label,
232 crate::format_bytes(len_u64),
233 std::io::Error::last_os_error()
234 );
235 }
236 }
237}
238
239pub(crate) fn pin_section(
246 bytes: &mut OwnedBytes,
247 label: &str,
248 mode: PinMode,
249 remaining: &mut u64,
250 report: &mut PinReport,
251) {
252 if !bytes.is_mmap() || bytes.is_empty() {
253 return;
254 }
255 let len = bytes.len() as u64;
256 report.intended_bytes += len;
257
258 if len > *remaining {
259 report.skipped_budget_bytes += len;
260 log::debug!(
261 "[pin] budget exhausted: skipping {} ({}, {} remaining)",
262 label,
263 crate::format_bytes(len),
264 crate::format_bytes(*remaining)
265 );
266 return;
267 }
268
269 match mode {
270 PinMode::Mlock => {
271 if bytes.mlock() {
272 *remaining -= len;
273 report.pinned_bytes += len;
274 } else {
275 report.failed_bytes += len;
276 log::warn!(
277 "[pin] mlock failed for {} ({}) — check RLIMIT_MEMLOCK; \
278 continuing unpinned",
279 label,
280 crate::format_bytes(len)
281 );
282 }
283 }
284 PinMode::Copy => {
285 *bytes = OwnedBytes::new(bytes.to_vec());
286 *remaining -= len;
287 report.pinned_bytes += len;
288 report.heap_copy_bytes += len;
289 }
290 }
291}