ringkernel_core/resource/
guard.rs1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
4use std::sync::OnceLock;
5
6use super::error::{ResourceError, ResourceResult};
7use super::estimate::MemoryEstimate;
8use super::system::get_available_memory;
9use super::{DEFAULT_MAX_MEMORY_BYTES, SYSTEM_MEMORY_MARGIN};
10
11#[derive(Debug)]
15pub struct ResourceGuard {
16 max_memory_bytes: AtomicU64,
18 current_memory_bytes: AtomicU64,
20 reserved_bytes: AtomicU64,
22 enforce_limits: AtomicBool,
24 safety_margin: f32,
26}
27
28impl Default for ResourceGuard {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl ResourceGuard {
35 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 max_memory_bytes: AtomicU64::new(DEFAULT_MAX_MEMORY_BYTES),
40 current_memory_bytes: AtomicU64::new(0),
41 reserved_bytes: AtomicU64::new(0),
42 enforce_limits: AtomicBool::new(true),
43 safety_margin: 0.3, }
45 }
46
47 #[must_use]
49 pub fn with_max_memory(max_bytes: u64) -> Self {
50 Self {
51 max_memory_bytes: AtomicU64::new(max_bytes),
52 current_memory_bytes: AtomicU64::new(0),
53 reserved_bytes: AtomicU64::new(0),
54 enforce_limits: AtomicBool::new(true),
55 safety_margin: 0.3,
56 }
57 }
58
59 #[must_use]
61 pub fn with_safety_margin(mut self, margin: f32) -> Self {
62 self.safety_margin = margin.clamp(0.0, 0.9);
63 self
64 }
65
66 #[must_use]
68 pub fn unguarded() -> Self {
69 let guard = Self::new();
70 guard.enforce_limits.store(false, Ordering::SeqCst);
71 guard
72 }
73
74 pub fn set_max_memory(&self, max_bytes: u64) {
76 self.max_memory_bytes.store(max_bytes, Ordering::SeqCst);
77 }
78
79 #[must_use]
81 pub fn max_memory(&self) -> u64 {
82 self.max_memory_bytes.load(Ordering::SeqCst)
83 }
84
85 #[must_use]
87 pub fn current_memory(&self) -> u64 {
88 self.current_memory_bytes.load(Ordering::SeqCst)
89 }
90
91 #[must_use]
93 pub fn reserved_memory(&self) -> u64 {
94 self.reserved_bytes.load(Ordering::SeqCst)
95 }
96
97 #[must_use]
99 pub fn available_memory(&self) -> u64 {
100 let max = self.max_memory();
101 let current = self.current_memory();
102 let reserved = self.reserved_memory();
103 max.saturating_sub(current).saturating_sub(reserved)
104 }
105
106 #[must_use]
108 pub fn can_allocate(&self, bytes: u64) -> bool {
109 if !self.enforce_limits.load(Ordering::SeqCst) {
110 return true;
111 }
112
113 bytes <= self.available_memory()
114 }
115
116 pub fn can_allocate_safe(&self, bytes: u64) -> ResourceResult<()> {
118 if !self.enforce_limits.load(Ordering::SeqCst) {
119 return Ok(());
120 }
121
122 let current = self.current_memory();
124 let reserved = self.reserved_memory();
125 let max = self.max_memory();
126 let used = current.saturating_add(reserved);
127
128 if used.saturating_add(bytes) > max {
129 return Err(ResourceError::MemoryLimitExceeded {
130 requested: bytes,
131 current: used,
132 max,
133 });
134 }
135
136 if let Some(available) = get_available_memory() {
138 if bytes > available.saturating_sub(SYSTEM_MEMORY_MARGIN) {
139 return Err(ResourceError::InsufficientSystemMemory {
140 requested: bytes,
141 available,
142 margin: SYSTEM_MEMORY_MARGIN,
143 });
144 }
145 }
146
147 Ok(())
148 }
149
150 pub fn record_allocation(&self, bytes: u64) {
152 self.current_memory_bytes.fetch_add(bytes, Ordering::SeqCst);
153 }
154
155 pub fn record_deallocation(&self, bytes: u64) {
157 self.current_memory_bytes.fetch_sub(bytes, Ordering::SeqCst);
158 }
159
160 pub fn reserve(&self, bytes: u64) -> ResourceResult<ReservationGuard<'_>> {
164 self.can_allocate_safe(bytes)?;
165 self.reserved_bytes.fetch_add(bytes, Ordering::SeqCst);
166 Ok(ReservationGuard {
167 guard: self,
168 bytes,
169 committed: false,
170 })
171 }
172
173 pub fn validate(&self, estimate: &MemoryEstimate) -> ResourceResult<()> {
175 self.can_allocate_safe(estimate.peak_bytes)
176 }
177
178 #[must_use]
180 pub fn max_safe_elements(&self, bytes_per_element: usize) -> usize {
181 if bytes_per_element == 0 {
182 return usize::MAX;
183 }
184
185 let available = self.available_memory();
186 let safe_bytes = (available as f64 * (1.0 - self.safety_margin as f64)) as u64;
187
188 (safe_bytes / bytes_per_element as u64) as usize
189 }
190
191 pub fn set_enforce_limits(&self, enforce: bool) {
193 self.enforce_limits.store(enforce, Ordering::SeqCst);
194 }
195
196 #[must_use]
198 pub fn is_enforcing(&self) -> bool {
199 self.enforce_limits.load(Ordering::SeqCst)
200 }
201}
202
203#[derive(Debug)]
207pub struct ReservationGuard<'a> {
208 guard: &'a ResourceGuard,
209 bytes: u64,
210 committed: bool,
211}
212
213impl<'a> ReservationGuard<'a> {
214 #[must_use]
216 pub fn bytes(&self) -> u64 {
217 self.bytes
218 }
219
220 pub fn commit(mut self) {
224 self.guard
225 .reserved_bytes
226 .fetch_sub(self.bytes, Ordering::SeqCst);
227 self.guard.record_allocation(self.bytes);
228 self.committed = true;
229 }
230
231 pub fn release(mut self) {
233 self.guard
234 .reserved_bytes
235 .fetch_sub(self.bytes, Ordering::SeqCst);
236 self.committed = true; }
238}
239
240impl<'a> Drop for ReservationGuard<'a> {
241 fn drop(&mut self) {
242 if !self.committed {
243 self.guard
244 .reserved_bytes
245 .fetch_sub(self.bytes, Ordering::SeqCst);
246 }
247 }
248}
249
250static GLOBAL_GUARD: OnceLock<ResourceGuard> = OnceLock::new();
255
256pub fn global_guard() -> &'static ResourceGuard {
258 GLOBAL_GUARD.get_or_init(ResourceGuard::new)
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_resource_guard_new() {
267 let guard = ResourceGuard::new();
268 assert_eq!(guard.current_memory(), 0);
269 assert_eq!(guard.max_memory(), DEFAULT_MAX_MEMORY_BYTES);
270 }
271
272 #[test]
273 fn test_resource_guard_allocation() {
274 let guard = ResourceGuard::with_max_memory(1_000_000);
275
276 assert!(guard.can_allocate(100_000));
278
279 assert!(!guard.can_allocate(2_000_000));
281
282 guard.record_allocation(500_000);
284 assert_eq!(guard.current_memory(), 500_000);
285
286 assert!(!guard.can_allocate(600_000));
288 assert!(guard.can_allocate(400_000));
289 }
290
291 #[test]
292 fn test_resource_guard_deallocation() {
293 let guard = ResourceGuard::with_max_memory(1_000_000);
294
295 guard.record_allocation(500_000);
296 assert_eq!(guard.current_memory(), 500_000);
297
298 guard.record_deallocation(200_000);
299 assert_eq!(guard.current_memory(), 300_000);
300 }
301
302 #[test]
303 fn test_reservation_guard() {
304 let guard = ResourceGuard::with_max_memory(1_000_000);
305
306 {
307 let reservation = guard.reserve(500_000).unwrap();
308 assert_eq!(guard.reserved_memory(), 500_000);
309 assert_eq!(reservation.bytes(), 500_000);
310
311 assert!(!guard.can_allocate(600_000));
313 } assert_eq!(guard.reserved_memory(), 0);
316 assert!(guard.can_allocate(600_000));
317 }
318
319 #[test]
320 fn test_reservation_commit() {
321 let guard = ResourceGuard::with_max_memory(1_000_000);
322
323 let reservation = guard.reserve(500_000).unwrap();
324 reservation.commit();
325
326 assert_eq!(guard.reserved_memory(), 0);
327 assert_eq!(guard.current_memory(), 500_000);
328 }
329
330 #[test]
331 fn test_reservation_release() {
332 let guard = ResourceGuard::with_max_memory(1_000_000);
333
334 let reservation = guard.reserve(500_000).unwrap();
335 reservation.release();
336
337 assert_eq!(guard.reserved_memory(), 0);
338 assert_eq!(guard.current_memory(), 0);
339 }
340
341 #[test]
342 fn test_max_safe_elements() {
343 let guard = ResourceGuard::with_max_memory(1_000_000);
344
345 let max_elements = guard.max_safe_elements(100);
349 assert!(
350 (6999..=7001).contains(&max_elements),
351 "max_elements {} not in range [6999, 7001]",
352 max_elements
353 );
354 }
355
356 #[test]
357 fn test_unguarded() {
358 let guard = ResourceGuard::unguarded();
359
360 assert!(guard.can_allocate(u64::MAX / 2));
362 }
363
364 #[test]
365 fn test_global_guard() {
366 let guard = global_guard();
367 assert_eq!(guard.current_memory(), 0);
368 }
369}