atomr_core/pattern/
backoff.rs1use 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 ((seed.wrapping_mul(2654435761)) % 10_000) as f64 / 10_000.0
38}
39
40pub 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}