Skip to main content

AirLibrary/Resilience/
Timeout.rs

1#![allow(unused_variables, dead_code, unused_imports)]
2
3//! Timeout management with cascading deadlines.
4//!
5//! `TimeoutManager` tracks a per-operation timeout and an optional global
6//! deadline. `effective_timeout()` returns the minimum of the two so every
7//! nested operation respects both the local and cascade budgets.
8//!
9//! All public methods have panic-safe variants (`Remaining`,
10//! `EffectiveTimeout`, `IsExceeded`) that catch panics via `catch_unwind` and
11//! return conservative fallback values so a transient panic never propagates
12//! into the caller.
13
14use std::time::{Duration, Instant};
15
16use crate::dev_log;
17
18/// Timeout manager with optional cascading global deadline.
19pub struct TimeoutManager {
20	global_deadline:Option<Instant>,
21	operation_timeout:Duration,
22}
23
24impl TimeoutManager {
25	/// Create with an operation-scoped timeout and no global deadline.
26	pub fn new(operation_timeout:Duration) -> Self { Self { global_deadline:None, operation_timeout } }
27
28	/// Create with both a global deadline and an operation timeout.
29	pub fn with_deadline(global_deadline:Instant, operation_timeout:Duration) -> Self {
30		Self { global_deadline:Some(global_deadline), operation_timeout }
31	}
32
33	/// Return an error if `timeout` is zero or exceeds one hour.
34	pub fn ValidateTimeout(timeout:Duration) -> Result<(), String> {
35		if timeout.is_zero() {
36			return Err("Timeout must be greater than 0".to_string());
37		}
38		if timeout.as_secs() > 3600 {
39			return Err("Timeout cannot exceed 1 hour".to_string());
40		}
41		Ok(())
42	}
43
44	/// Return `Ok(timeout)` or an error string; used by fallback paths.
45	pub fn ValidateTimeoutResult(timeout:Duration) -> Result<Duration, String> {
46		if timeout.is_zero() {
47			return Err("Timeout must be greater than 0".to_string());
48		}
49		if timeout.as_secs() > 3600 {
50			return Err("Timeout cannot exceed 1 hour".to_string());
51		}
52		Ok(timeout)
53	}
54
55	/// Time remaining until the global deadline, or `None` if none is set.
56	pub fn remaining(&self) -> Option<Duration> {
57		self.global_deadline.map(|deadline| {
58			deadline
59				.checked_duration_since(Instant::now())
60				.unwrap_or(Duration::from_secs(0))
61		})
62	}
63
64	/// Panic-safe `remaining()`. Returns `None` on panic (fail-open).
65	pub fn Remaining(&self) -> Option<Duration> {
66		std::panic::catch_unwind(|| self.remaining()).unwrap_or_else(|e| {
67			dev_log!("resilience", "error: [TimeoutManager] Panic in Remaining: {:?}", e);
68			None
69		})
70	}
71
72	/// Minimum of `operation_timeout` and remaining deadline time.
73	pub fn effective_timeout(&self) -> Duration {
74		match self.remaining() {
75			Some(remaining) => self.operation_timeout.min(remaining),
76			None => self.operation_timeout,
77		}
78	}
79
80	/// Panic-safe `effective_timeout()`. Falls back to 30 s on invalid/panic.
81	pub fn EffectiveTimeout(&self) -> Duration {
82		std::panic::catch_unwind(|| {
83			let timeout = self.effective_timeout();
84			match Self::ValidateTimeoutResult(timeout) {
85				Ok(valid_timeout) => valid_timeout,
86				Err(_) => Duration::from_secs(30),
87			}
88		})
89		.unwrap_or_else(|e| {
90			dev_log!("resilience", "error: [TimeoutManager] Panic in EffectiveTimeout: {:?}", e);
91			Duration::from_secs(30)
92		})
93	}
94
95	/// `true` when the global deadline has passed.
96	pub fn is_exceeded(&self) -> bool { self.global_deadline.map_or(false, |deadline| Instant::now() >= deadline) }
97
98	/// Panic-safe `is_exceeded()`. Returns `true` on panic (fail-safe).
99	pub fn IsExceeded(&self) -> bool {
100		std::panic::catch_unwind(|| self.is_exceeded()).unwrap_or_else(|e| {
101			dev_log!("resilience", "error: [TimeoutManager] Panic in IsExceeded: {:?}", e);
102			true
103		})
104	}
105
106	pub fn GetGlobalDeadline(&self) -> Option<Instant> { self.global_deadline }
107
108	pub fn GetOperationTimeout(&self) -> Duration { self.operation_timeout }
109}