Skip to main content

camel_api/
supervision.rs

1//! Supervision configuration for automatic route restart.
2
3use crate::BackoffConfig;
4use std::time::Duration;
5
6/// Configuration for route supervision (automatic restart on crash).
7#[derive(Debug, Clone, PartialEq)]
8pub struct SupervisionConfig {
9    /// Maximum number of restart attempts. `None` means infinite retries.
10    /// Default: `Some(5)`.
11    pub max_attempts: Option<u32>,
12
13    /// Delay before the first restart attempt.
14    /// Default: 1 second.
15    pub initial_delay: Duration,
16
17    /// Multiplier applied to the delay after each failed attempt.
18    /// Default: 2.0 (doubles each time).
19    pub backoff_multiplier: f64,
20
21    /// Maximum delay cap between restart attempts.
22    /// Default: 60 seconds.
23    pub max_delay: Duration,
24}
25
26impl SupervisionConfig {
27    /// Compute the delay before attempt number `attempt` (1-indexed).
28    ///
29    /// Formula: `min(initial_delay * backoff_multiplier^(attempt-1), max_delay)`
30    pub fn next_delay(&self, attempt: u32) -> Duration {
31        if attempt == 0 {
32            return self.initial_delay;
33        }
34        let backoff = BackoffConfig {
35            initial_delay: self.initial_delay,
36            multiplier: self.backoff_multiplier,
37            max_delay: self.max_delay,
38        };
39        backoff.delay_for_attempt(attempt - 1)
40    }
41}
42
43impl Default for SupervisionConfig {
44    fn default() -> Self {
45        Self {
46            max_attempts: Some(5),
47            initial_delay: Duration::from_secs(1),
48            backoff_multiplier: 2.0,
49            max_delay: Duration::from_secs(60),
50        }
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use std::time::Duration;
58
59    #[test]
60    fn test_supervision_config_defaults() {
61        let cfg = SupervisionConfig::default();
62        assert_eq!(cfg.max_attempts, Some(5));
63        assert_eq!(cfg.initial_delay, Duration::from_secs(1));
64        assert_eq!(cfg.backoff_multiplier, 2.0);
65        assert_eq!(cfg.max_delay, Duration::from_secs(60));
66    }
67
68    #[test]
69    fn test_supervision_config_infinite() {
70        let cfg = SupervisionConfig {
71            max_attempts: None,
72            ..Default::default()
73        };
74        assert!(cfg.max_attempts.is_none());
75    }
76
77    #[test]
78    fn test_supervision_config_next_delay_growth() {
79        let cfg = SupervisionConfig::default();
80        let d1 = cfg.next_delay(1); // attempt 1 → 1s * 2^0 = 1s
81        let d2 = cfg.next_delay(2); // attempt 2 → 1s * 2^1 = 2s
82        let d3 = cfg.next_delay(3); // attempt 3 → 1s * 2^2 = 4s
83        assert_eq!(d1, Duration::from_secs(1));
84        assert_eq!(d2, Duration::from_secs(2));
85        assert_eq!(d3, Duration::from_secs(4));
86    }
87
88    #[test]
89    fn test_supervision_config_next_delay_capped() {
90        let cfg = SupervisionConfig {
91            max_delay: Duration::from_secs(5),
92            ..Default::default()
93        };
94        let d = cfg.next_delay(10); // would be 512s, capped at 5s
95        assert_eq!(d, Duration::from_secs(5));
96    }
97
98    #[test]
99    fn test_next_delay_attempt_zero_returns_initial_delay() {
100        let cfg = SupervisionConfig::default();
101        let d = cfg.next_delay(0);
102        assert_eq!(d, cfg.initial_delay);
103    }
104}