Skip to main content

atomr_core/pattern/
backoff.rs

1//! Backoff supervisor — restart child with exponential backoff.
2//! akka.net: `Pattern/BackoffSupervisor.cs`.
3
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
7pub struct BackoffOptions {
8    pub min_backoff: Duration,
9    pub max_backoff: Duration,
10    pub random_factor: f64,
11    pub max_restarts: Option<u32>,
12}
13
14impl Default for BackoffOptions {
15    fn default() -> Self {
16        Self {
17            min_backoff: Duration::from_millis(200),
18            max_backoff: Duration::from_secs(30),
19            random_factor: 0.2,
20            max_restarts: Some(10),
21        }
22    }
23}
24
25impl BackoffOptions {
26    pub fn next_delay(&self, attempt: u32) -> Duration {
27        let base = self.min_backoff.as_secs_f64() * 2f64.powi(attempt as i32);
28        let capped = base.min(self.max_backoff.as_secs_f64());
29        let jitter = 1.0 + (pseudo_random_01(attempt) * self.random_factor);
30        Duration::from_secs_f64(capped * jitter)
31    }
32}
33
34fn pseudo_random_01(seed: u32) -> f64 {
35    // Deterministic stand-in — tests don't depend on true randomness.
36
37    ((seed.wrapping_mul(2654435761)) % 10_000) as f64 / 10_000.0
38}
39
40/// Wrapper around the supervisor logic — held by tests and demonstrations.
41pub struct BackoffSupervisor {
42    pub options: BackoffOptions,
43}
44
45impl BackoffSupervisor {
46    pub fn new(options: BackoffOptions) -> Self {
47        Self { options }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn backoff_grows_but_capped() {
57        let o = BackoffOptions::default();
58        let d0 = o.next_delay(0);
59        let d1 = o.next_delay(1);
60        let huge = o.next_delay(30);
61        assert!(d0 < d1);
62        assert!(huge <= o.max_backoff.mul_f64(1.0 + o.random_factor));
63    }
64}