oxiz_solver/
resource_limits.rs1#[allow(unused_imports)]
26use crate::prelude::*;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ResourceExhausted {
31 Timeout,
33 MaxConflicts,
35 MaxMemory,
37 MaxDecisions,
39 MaxRestarts,
41 MaxTheoryChecks,
43}
44
45impl core::fmt::Display for ResourceExhausted {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 match self {
48 ResourceExhausted::Timeout => write!(f, "timeout"),
49 ResourceExhausted::MaxConflicts => write!(f, "max conflicts reached"),
50 ResourceExhausted::MaxMemory => write!(f, "max memory reached"),
51 ResourceExhausted::MaxDecisions => write!(f, "max decisions reached"),
52 ResourceExhausted::MaxRestarts => write!(f, "max restarts reached"),
53 ResourceExhausted::MaxTheoryChecks => write!(f, "max theory checks reached"),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Default)]
62pub struct ResourceLimits {
63 pub timeout: Option<core::time::Duration>,
65 pub max_conflicts: Option<u64>,
67 pub max_memory_mb: Option<u64>,
69 pub max_decisions: Option<u64>,
71 pub max_restarts: Option<u64>,
73 pub max_theory_checks: Option<u64>,
75}
76
77impl ResourceLimits {
78 #[must_use]
80 pub fn new() -> Self {
81 Self::default()
82 }
83
84 #[must_use]
86 pub fn with_timeout(mut self, timeout: core::time::Duration) -> Self {
87 self.timeout = Some(timeout);
88 self
89 }
90
91 #[must_use]
93 pub fn with_max_conflicts(mut self, max: u64) -> Self {
94 self.max_conflicts = Some(max);
95 self
96 }
97
98 #[must_use]
100 pub fn with_max_memory_mb(mut self, max: u64) -> Self {
101 self.max_memory_mb = Some(max);
102 self
103 }
104
105 #[must_use]
107 pub fn with_max_decisions(mut self, max: u64) -> Self {
108 self.max_decisions = Some(max);
109 self
110 }
111
112 #[must_use]
114 pub fn with_max_restarts(mut self, max: u64) -> Self {
115 self.max_restarts = Some(max);
116 self
117 }
118
119 #[must_use]
121 pub fn with_max_theory_checks(mut self, max: u64) -> Self {
122 self.max_theory_checks = Some(max);
123 self
124 }
125
126 #[must_use]
128 pub fn has_any_limit(&self) -> bool {
129 self.timeout.is_some()
130 || self.max_conflicts.is_some()
131 || self.max_memory_mb.is_some()
132 || self.max_decisions.is_some()
133 || self.max_restarts.is_some()
134 || self.max_theory_checks.is_some()
135 }
136}
137
138#[derive(Debug, Clone)]
144pub struct ResourceMonitor {
145 limits: ResourceLimits,
147 pub conflicts: u64,
149 pub decisions: u64,
151 pub restarts: u64,
153 pub theory_checks: u64,
155 #[cfg(feature = "std")]
157 start_time: Option<std::time::Instant>,
158}
159
160impl ResourceMonitor {
161 #[must_use]
163 pub fn new(limits: ResourceLimits) -> Self {
164 Self {
165 #[cfg(feature = "std")]
166 start_time: if limits.timeout.is_some() {
167 Some(std::time::Instant::now())
168 } else {
169 None
170 },
171 limits,
172 conflicts: 0,
173 decisions: 0,
174 restarts: 0,
175 theory_checks: 0,
176 }
177 }
178
179 pub fn reset(&mut self) {
181 self.conflicts = 0;
182 self.decisions = 0;
183 self.restarts = 0;
184 self.theory_checks = 0;
185 #[cfg(feature = "std")]
186 {
187 self.start_time = if self.limits.timeout.is_some() {
188 Some(std::time::Instant::now())
189 } else {
190 None
191 };
192 }
193 }
194
195 pub fn record_conflict(&mut self) {
197 self.conflicts += 1;
198 }
199
200 pub fn record_decision(&mut self) {
202 self.decisions += 1;
203 }
204
205 pub fn record_restart(&mut self) {
207 self.restarts += 1;
208 }
209
210 pub fn record_theory_check(&mut self) {
212 self.theory_checks += 1;
213 }
214
215 #[must_use]
220 pub fn check(&self) -> Option<ResourceExhausted> {
221 #[cfg(feature = "std")]
223 if let (Some(timeout), Some(start)) = (self.limits.timeout, self.start_time) {
224 if start.elapsed() >= timeout {
225 return Some(ResourceExhausted::Timeout);
226 }
227 }
228
229 if let Some(max) = self.limits.max_conflicts {
231 if self.conflicts >= max {
232 return Some(ResourceExhausted::MaxConflicts);
233 }
234 }
235
236 if let Some(max) = self.limits.max_decisions {
238 if self.decisions >= max {
239 return Some(ResourceExhausted::MaxDecisions);
240 }
241 }
242
243 if let Some(max) = self.limits.max_restarts {
245 if self.restarts >= max {
246 return Some(ResourceExhausted::MaxRestarts);
247 }
248 }
249
250 if let Some(max) = self.limits.max_theory_checks {
252 if self.theory_checks >= max {
253 return Some(ResourceExhausted::MaxTheoryChecks);
254 }
255 }
256
257 #[cfg(feature = "std")]
259 if let Some(max_mb) = self.limits.max_memory_mb {
260 if let Some(current_mb) = Self::estimate_memory_mb() {
263 if current_mb >= max_mb {
264 return Some(ResourceExhausted::MaxMemory);
265 }
266 }
267 }
268
269 None
270 }
271
272 pub fn check_result(&self) -> core::result::Result<(), ResourceExhausted> {
277 match self.check() {
278 Some(reason) => Err(reason),
279 None => Ok(()),
280 }
281 }
282
283 #[must_use]
285 pub fn limits(&self) -> &ResourceLimits {
286 &self.limits
287 }
288
289 #[cfg(feature = "std")]
291 #[must_use]
292 pub fn elapsed(&self) -> Option<core::time::Duration> {
293 self.start_time.map(|s| s.elapsed())
294 }
295
296 #[cfg(feature = "std")]
298 fn estimate_memory_mb() -> Option<u64> {
299 #[cfg(target_os = "linux")]
301 {
302 if let Ok(contents) = std::fs::read_to_string("/proc/self/statm") {
303 let mut parts = contents.split_whitespace();
305 if let Some(resident_str) = parts.nth(1) {
306 if let Ok(pages) = resident_str.parse::<u64>() {
307 let page_size = 4096u64; return Some(pages * page_size / (1024 * 1024));
309 }
310 }
311 }
312 None
313 }
314
315 #[cfg(target_os = "macos")]
316 {
317 None
321 }
322
323 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
324 {
325 None
326 }
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 use core::time::Duration;
334
335 #[test]
336 fn test_resource_limits_default() {
337 let limits = ResourceLimits::new();
338 assert!(limits.timeout.is_none());
339 assert!(limits.max_conflicts.is_none());
340 assert!(limits.max_memory_mb.is_none());
341 assert!(limits.max_decisions.is_none());
342 assert!(limits.max_restarts.is_none());
343 assert!(limits.max_theory_checks.is_none());
344 assert!(!limits.has_any_limit());
345 }
346
347 #[test]
348 fn test_resource_limits_builder() {
349 let limits = ResourceLimits::new()
350 .with_timeout(Duration::from_secs(30))
351 .with_max_conflicts(10_000)
352 .with_max_decisions(100_000)
353 .with_max_restarts(500)
354 .with_max_theory_checks(50_000)
355 .with_max_memory_mb(1024);
356
357 assert_eq!(limits.timeout, Some(Duration::from_secs(30)));
358 assert_eq!(limits.max_conflicts, Some(10_000));
359 assert_eq!(limits.max_decisions, Some(100_000));
360 assert_eq!(limits.max_restarts, Some(500));
361 assert_eq!(limits.max_theory_checks, Some(50_000));
362 assert_eq!(limits.max_memory_mb, Some(1024));
363 assert!(limits.has_any_limit());
364 }
365
366 #[test]
367 fn test_monitor_no_limits() {
368 let monitor = ResourceMonitor::new(ResourceLimits::new());
369 assert!(monitor.check().is_none());
370 }
371
372 #[test]
373 fn test_monitor_conflict_limit() {
374 let limits = ResourceLimits::new().with_max_conflicts(5);
375 let mut monitor = ResourceMonitor::new(limits);
376
377 for _ in 0..4 {
378 monitor.record_conflict();
379 assert!(monitor.check().is_none());
380 }
381 monitor.record_conflict();
382 assert_eq!(monitor.check(), Some(ResourceExhausted::MaxConflicts));
383 }
384
385 #[test]
386 fn test_monitor_decision_limit() {
387 let limits = ResourceLimits::new().with_max_decisions(3);
388 let mut monitor = ResourceMonitor::new(limits);
389
390 monitor.record_decision();
391 monitor.record_decision();
392 assert!(monitor.check().is_none());
393 monitor.record_decision();
394 assert_eq!(monitor.check(), Some(ResourceExhausted::MaxDecisions));
395 }
396
397 #[test]
398 fn test_monitor_restart_limit() {
399 let limits = ResourceLimits::new().with_max_restarts(2);
400 let mut monitor = ResourceMonitor::new(limits);
401
402 monitor.record_restart();
403 assert!(monitor.check().is_none());
404 monitor.record_restart();
405 assert_eq!(monitor.check(), Some(ResourceExhausted::MaxRestarts));
406 }
407
408 #[test]
409 fn test_monitor_theory_check_limit() {
410 let limits = ResourceLimits::new().with_max_theory_checks(10);
411 let mut monitor = ResourceMonitor::new(limits);
412
413 for _ in 0..9 {
414 monitor.record_theory_check();
415 }
416 assert!(monitor.check().is_none());
417 monitor.record_theory_check();
418 assert_eq!(monitor.check(), Some(ResourceExhausted::MaxTheoryChecks));
419 }
420
421 #[test]
422 fn test_monitor_reset() {
423 let limits = ResourceLimits::new().with_max_conflicts(5);
424 let mut monitor = ResourceMonitor::new(limits);
425
426 for _ in 0..5 {
427 monitor.record_conflict();
428 }
429 assert!(monitor.check().is_some());
430
431 monitor.reset();
432 assert!(monitor.check().is_none());
433 assert_eq!(monitor.conflicts, 0);
434 }
435
436 #[test]
437 fn test_monitor_check_result() {
438 let limits = ResourceLimits::new().with_max_conflicts(1);
439 let mut monitor = ResourceMonitor::new(limits);
440
441 assert!(monitor.check_result().is_ok());
442 monitor.record_conflict();
443 assert!(monitor.check_result().is_err());
444 }
445
446 #[test]
447 fn test_resource_exhausted_display() {
448 assert_eq!(ResourceExhausted::Timeout.to_string(), "timeout");
449 assert_eq!(
450 ResourceExhausted::MaxConflicts.to_string(),
451 "max conflicts reached"
452 );
453 assert_eq!(
454 ResourceExhausted::MaxMemory.to_string(),
455 "max memory reached"
456 );
457 assert_eq!(
458 ResourceExhausted::MaxDecisions.to_string(),
459 "max decisions reached"
460 );
461 assert_eq!(
462 ResourceExhausted::MaxRestarts.to_string(),
463 "max restarts reached"
464 );
465 assert_eq!(
466 ResourceExhausted::MaxTheoryChecks.to_string(),
467 "max theory checks reached"
468 );
469 }
470
471 #[cfg(feature = "std")]
472 #[test]
473 fn test_monitor_timeout_not_hit_immediately() {
474 let limits = ResourceLimits::new().with_timeout(Duration::from_secs(60));
475 let monitor = ResourceMonitor::new(limits);
476 assert!(monitor.check().is_none());
478 }
479
480 #[cfg(feature = "std")]
481 #[test]
482 fn test_monitor_elapsed() {
483 let limits = ResourceLimits::new().with_timeout(Duration::from_secs(60));
484 let monitor = ResourceMonitor::new(limits);
485 let elapsed = monitor.elapsed();
486 assert!(elapsed.is_some());
487 assert!(elapsed.is_some_and(|e| e < Duration::from_secs(1)));
489 }
490}