mx_core/resilience/
backoff.rs1use std::time::Duration;
4
5pub trait Backoff: Send + Sync {
7 fn delay(&self, attempt: u32) -> Duration;
9
10 fn max_delay(&self) -> Duration;
12}
13
14#[derive(Debug, Clone)]
16pub struct ExponentialBackoff {
17 pub base_delay: Duration,
18 pub max_delay: Duration,
19 pub max_exponent: u32,
21}
22
23impl ExponentialBackoff {
24 pub fn new(base_delay: Duration, max_delay: Duration) -> Self {
25 Self {
26 base_delay,
27 max_delay,
28 max_exponent: 16,
29 }
30 }
31
32 pub fn default_settings() -> Self {
33 Self::new(Duration::from_millis(100), Duration::from_secs(30))
34 }
35
36 pub fn with_max_exponent(mut self, max_exponent: u32) -> Self {
37 self.max_exponent = max_exponent;
38 self
39 }
40}
41
42impl Default for ExponentialBackoff {
43 fn default() -> Self {
44 Self::default_settings()
45 }
46}
47
48impl Backoff for ExponentialBackoff {
49 fn delay(&self, attempt: u32) -> Duration {
50 let exponent = attempt.min(self.max_exponent);
51 let multiplier = 1u64.checked_shl(exponent).unwrap_or(u64::MAX);
52 let delay_nanos = self
53 .base_delay
54 .as_nanos()
55 .saturating_mul(u128::from(multiplier));
56 let delay = Duration::from_nanos(delay_nanos.min(u128::from(u64::MAX)) as u64);
57 delay.min(self.max_delay)
58 }
59
60 fn max_delay(&self) -> Duration {
61 self.max_delay
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn test_exponential_sequence() {
71 let b = ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(30));
72 assert_eq!(b.delay(0), Duration::from_millis(100));
73 assert_eq!(b.delay(1), Duration::from_millis(200));
74 assert_eq!(b.delay(2), Duration::from_millis(400));
75 assert_eq!(b.delay(3), Duration::from_millis(800));
76 }
77
78 #[test]
79 fn test_capped_at_max() {
80 let b = ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(1));
81 assert_eq!(b.delay(10), Duration::from_secs(1));
82 assert_eq!(b.delay(20), Duration::from_secs(1));
83 }
84
85 #[test]
86 fn test_overflow_protection() {
87 let b = ExponentialBackoff::new(Duration::from_secs(1), Duration::from_secs(3600));
88 let delay = b.delay(100);
89 assert!(delay <= Duration::from_secs(3600));
90 }
91}