Skip to main content

trellis_runner/engine/policy/
timeout.rs

1//! # Timeout policy
2//!
3//! Terminates execution after a wall-clock duration has elapsed.
4//!
5//! ## Behaviour
6//!
7//! - Compares `EngineContext.elapsed` against a fixed timeout.
8//! - Does not inspect progress or iteration state.
9//!
10//! ## Termination
11//!
12//! Returns [`Termination::Timeout`] when elapsed >= timeout.
13//!
14//! ## Notes
15//!
16//! This policy is orthogonal to convergence logic and should always be
17//! included for safety in long-running computations.
18use std::time::Duration;
19
20use super::EnginePolicy;
21
22use crate::{
23    engine::{EngineAction, EngineContext, EventBatch},
24    Termination,
25};
26
27pub struct TimeoutPolicy {
28    timeout: Duration,
29}
30
31impl TimeoutPolicy {
32    pub fn new(timeout: Duration) -> Self {
33        Self { timeout }
34    }
35}
36
37impl<F> EnginePolicy<F> for TimeoutPolicy {
38    fn decide(&mut self, _batch: &EventBatch<F>, context: &EngineContext) -> EngineAction {
39        if context.elapsed >= self.timeout {
40            return EngineAction::Stop(Termination::Timeout);
41        }
42
43        EngineAction::Continue
44    }
45}
46
47#[cfg(test)]
48mod test {
49    use super::*;
50
51    use crate::engine::policy::PolicyStack;
52    use crate::progress::Progress;
53
54    #[test]
55    fn timeout_policy_terminates_for_durations_greater_than_limit() {
56        let mut stack = PolicyStack::<f64>::new().add(TimeoutPolicy::new(Duration::new(5, 0)));
57
58        let batch: EventBatch<f64> = EventBatch::new().add(Progress::Complete);
59        let ctx = EngineContext {
60            elapsed: Duration::new(6, 0),
61            ..Default::default()
62        };
63
64        assert!(matches!(
65            stack.decide(&batch, &ctx),
66            EngineAction::Stop(crate::Termination::Timeout)
67        ))
68    }
69
70    #[test]
71    fn timeout_policy_continues_for_durations_less_than_limit() {
72        let mut stack = PolicyStack::<f64>::new().add(TimeoutPolicy::new(Duration::new(5, 0)));
73
74        let batch: EventBatch<f64> = EventBatch::new().add(Progress::Complete);
75        let ctx = EngineContext {
76            elapsed: Duration::new(4, 0),
77            ..Default::default()
78        };
79
80        assert!(matches!(stack.decide(&batch, &ctx), EngineAction::Continue))
81    }
82}