1use std::sync::atomic::{AtomicUsize, Ordering};
7
8use parking_lot::Mutex;
9use tracing::{debug, warn};
10
11pub type MemoryGrowthCallback = Box<dyn Fn(MemoryGrowthEvent) + Send + Sync>;
13
14#[derive(Debug, Clone)]
16pub struct MemoryGrowthEvent {
17 pub from_bytes: usize,
19 pub to_bytes: usize,
21 pub max_bytes: usize,
23}
24
25#[derive(Debug, Clone)]
27pub struct LimiterConfig {
28 pub max_memory_bytes: usize,
30 pub max_table_elements: u32,
32 pub max_memories: u32,
34 pub max_tables: u32,
36}
37
38impl Default for LimiterConfig {
39 fn default() -> Self {
40 Self {
41 max_memory_bytes: 64 * 1024 * 1024, max_table_elements: 10_000,
43 max_memories: 1,
44 max_tables: 10,
45 }
46 }
47}
48
49impl LimiterConfig {
50 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn with_max_memory(mut self, bytes: usize) -> Self {
57 self.max_memory_bytes = bytes;
58 self
59 }
60
61 pub fn with_max_table_elements(mut self, elements: u32) -> Self {
63 self.max_table_elements = elements;
64 self
65 }
66}
67
68pub struct AegisResourceLimiter {
73 config: LimiterConfig,
75 current_memory: AtomicUsize,
77 peak_memory: AtomicUsize,
79 allocation_count: AtomicUsize,
81 on_memory_grow: Mutex<Option<MemoryGrowthCallback>>,
83}
84
85impl AegisResourceLimiter {
86 pub fn new(config: LimiterConfig) -> Self {
88 Self {
89 config,
90 current_memory: AtomicUsize::new(0),
91 peak_memory: AtomicUsize::new(0),
92 allocation_count: AtomicUsize::new(0),
93 on_memory_grow: Mutex::new(None),
94 }
95 }
96
97 pub fn with_defaults() -> Self {
99 Self::new(LimiterConfig::default())
100 }
101
102 pub fn set_memory_growth_callback(&self, callback: MemoryGrowthCallback) {
104 *self.on_memory_grow.lock() = Some(callback);
105 }
106
107 pub fn current_memory(&self) -> usize {
109 self.current_memory.load(Ordering::Relaxed)
110 }
111
112 pub fn peak_memory(&self) -> usize {
114 self.peak_memory.load(Ordering::Relaxed)
115 }
116
117 pub fn allocation_count(&self) -> usize {
119 self.allocation_count.load(Ordering::Relaxed)
120 }
121
122 pub fn remaining_memory(&self) -> usize {
124 self.config
125 .max_memory_bytes
126 .saturating_sub(self.current_memory())
127 }
128
129 pub fn max_memory(&self) -> usize {
131 self.config.max_memory_bytes
132 }
133
134 pub fn check_memory_growth(&self, current: usize, desired: usize) -> bool {
138 if desired > self.config.max_memory_bytes {
139 warn!(
140 current_bytes = current,
141 desired_bytes = desired,
142 max_bytes = self.config.max_memory_bytes,
143 "Memory growth denied: exceeds limit"
144 );
145 return false;
146 }
147
148 self.current_memory.store(desired, Ordering::Relaxed);
150 self.allocation_count.fetch_add(1, Ordering::Relaxed);
151
152 let mut peak = self.peak_memory.load(Ordering::Relaxed);
154 while desired > peak {
155 match self.peak_memory.compare_exchange_weak(
156 peak,
157 desired,
158 Ordering::Relaxed,
159 Ordering::Relaxed,
160 ) {
161 Ok(_) => break,
162 Err(current_peak) => peak = current_peak,
163 }
164 }
165
166 if let Some(callback) = self.on_memory_grow.lock().as_ref() {
168 callback(MemoryGrowthEvent {
169 from_bytes: current,
170 to_bytes: desired,
171 max_bytes: self.config.max_memory_bytes,
172 });
173 }
174
175 debug!(
176 from_bytes = current,
177 to_bytes = desired,
178 peak_bytes = self.peak_memory(),
179 "Memory growth permitted"
180 );
181
182 true
183 }
184
185 pub fn check_table_growth(&self, current: u32, desired: u32) -> bool {
187 if desired > self.config.max_table_elements {
188 warn!(
189 current_elements = current,
190 desired_elements = desired,
191 max_elements = self.config.max_table_elements,
192 "Table growth denied: exceeds limit"
193 );
194 return false;
195 }
196
197 debug!(
198 from_elements = current,
199 to_elements = desired,
200 "Table growth permitted"
201 );
202
203 true
204 }
205
206 pub fn reset(&self) {
208 self.current_memory.store(0, Ordering::Relaxed);
209 self.peak_memory.store(0, Ordering::Relaxed);
210 self.allocation_count.store(0, Ordering::Relaxed);
211 }
212
213 pub fn stats(&self) -> LimiterStats {
215 LimiterStats {
216 current_memory: self.current_memory(),
217 peak_memory: self.peak_memory(),
218 allocation_count: self.allocation_count(),
219 max_memory: self.config.max_memory_bytes,
220 }
221 }
222}
223
224#[derive(Debug, Clone)]
226pub struct LimiterStats {
227 pub current_memory: usize,
229 pub peak_memory: usize,
231 pub allocation_count: usize,
233 pub max_memory: usize,
235}
236
237impl LimiterStats {
238 pub fn utilization_percent(&self) -> f64 {
240 if self.max_memory == 0 {
241 0.0
242 } else {
243 (self.peak_memory as f64 / self.max_memory as f64) * 100.0
244 }
245 }
246}
247
248impl std::fmt::Debug for AegisResourceLimiter {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 f.debug_struct("AegisResourceLimiter")
251 .field("config", &self.config)
252 .field("current_memory", &self.current_memory())
253 .field("peak_memory", &self.peak_memory())
254 .field("allocation_count", &self.allocation_count())
255 .finish()
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use std::sync::Arc;
263
264 #[test]
265 fn test_limiter_creation() {
266 let limiter = AegisResourceLimiter::new(LimiterConfig::default());
267 assert_eq!(limiter.current_memory(), 0);
268 assert_eq!(limiter.peak_memory(), 0);
269 }
270
271 #[test]
272 fn test_memory_growth_allowed() {
273 let config = LimiterConfig::default().with_max_memory(1024 * 1024);
274 let limiter = AegisResourceLimiter::new(config);
275
276 assert!(limiter.check_memory_growth(0, 512 * 1024));
277 assert_eq!(limiter.current_memory(), 512 * 1024);
278 }
279
280 #[test]
281 fn test_memory_growth_denied() {
282 let config = LimiterConfig::default().with_max_memory(1024 * 1024);
283 let limiter = AegisResourceLimiter::new(config);
284
285 assert!(!limiter.check_memory_growth(0, 2 * 1024 * 1024));
286 }
287
288 #[test]
289 fn test_peak_memory_tracking() {
290 let config = LimiterConfig::default().with_max_memory(10 * 1024 * 1024);
291 let limiter = AegisResourceLimiter::new(config);
292
293 limiter.check_memory_growth(0, 1024);
294 limiter.check_memory_growth(1024, 2048);
295 limiter.check_memory_growth(2048, 1024); assert_eq!(limiter.peak_memory(), 2048);
298 assert_eq!(limiter.current_memory(), 1024);
299 }
300
301 #[test]
302 fn test_memory_growth_callback() {
303 use std::sync::atomic::AtomicBool;
304
305 let callback_called = Arc::new(AtomicBool::new(false));
306 let callback_called_clone = Arc::clone(&callback_called);
307
308 let limiter = AegisResourceLimiter::with_defaults();
309 limiter.set_memory_growth_callback(Box::new(move |_event| {
310 callback_called_clone.store(true, Ordering::SeqCst);
311 }));
312
313 limiter.check_memory_growth(0, 1024);
314 assert!(callback_called.load(Ordering::SeqCst));
315 }
316
317 #[test]
318 fn test_table_growth() {
319 let config = LimiterConfig::default().with_max_table_elements(1000);
320 let limiter = AegisResourceLimiter::new(config);
321
322 assert!(limiter.check_table_growth(0, 500));
323 assert!(!limiter.check_table_growth(500, 1500));
324 }
325
326 #[test]
327 fn test_stats() {
328 let config = LimiterConfig::default().with_max_memory(1024);
329 let limiter = AegisResourceLimiter::new(config);
330
331 limiter.check_memory_growth(0, 512);
332
333 let stats = limiter.stats();
334 assert_eq!(stats.current_memory, 512);
335 assert_eq!(stats.peak_memory, 512);
336 assert_eq!(stats.max_memory, 1024);
337 assert!((stats.utilization_percent() - 50.0).abs() < 0.01);
338 }
339}