exocore_core/utils/
backoff.rs1use crate::time::{Clock, Duration, Instant};
2
3#[derive(Clone, Copy)]
4pub struct BackoffConfig {
5 pub normal_constant: Duration,
6 pub failure_constant: Duration,
7 pub failure_exp_base: f32,
8 pub failure_exp_multiplier: Duration,
9 pub failure_maximum: Duration,
10}
11
12pub struct BackoffCalculator {
13 clock: Clock,
14 config: BackoffConfig,
15 consecutive_failures_count: u32,
16 next_execution_allow: Option<Instant>,
17}
18
19impl BackoffCalculator {
20 pub fn new(clock: Clock, config: BackoffConfig) -> BackoffCalculator {
21 BackoffCalculator {
22 clock,
23 config,
24 consecutive_failures_count: 0,
25 next_execution_allow: None,
26 }
27 }
28
29 pub fn can_execute_now(&self) -> bool {
30 if self.consecutive_failures_count == 0 {
31 return true;
32 }
33
34 self.next_execution_time() <= self.clock.instant()
35 }
36
37 pub fn next_execution_time(&self) -> Instant {
38 self.next_execution_allow
39 .unwrap_or_else(|| self.clock.instant())
40 }
41
42 pub fn increment_failure(&mut self) {
43 self.consecutive_failures_count += 1;
44 self.next_execution_allow = Some(self.clock.instant() + self.backoff_duration());
45 }
46
47 pub fn consecutive_failures_count(&self) -> u32 {
48 self.consecutive_failures_count
49 }
50
51 pub fn reset(&mut self) {
52 self.consecutive_failures_count = 0;
53 self.next_execution_allow = None;
54 }
55
56 pub fn backoff_duration(&self) -> Duration {
57 if self.consecutive_failures_count == 0 {
58 self.config.normal_constant
59 } else {
60 let multiplier =
61 self.config
62 .failure_exp_base
63 .powi((self.consecutive_failures_count - 1) as i32) as u32;
64
65 let duration =
66 self.config.failure_constant + self.config.failure_exp_multiplier * multiplier;
67
68 duration.min(self.config.failure_maximum)
69 }
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn basic_case() {
79 let clock = Clock::new();
80 let mut exp = BackoffCalculator::new(
81 clock,
82 BackoffConfig {
83 normal_constant: Duration::new(0, 0),
84 failure_constant: Duration::from_millis(5),
85 failure_exp_base: 2.0,
86 failure_exp_multiplier: Duration::from_millis(10),
87 failure_maximum: Duration::from_millis(50),
88 },
89 );
90
91 assert!(exp.can_execute_now());
92
93 exp.increment_failure();
94 assert_eq!(exp.backoff_duration().as_millis(), 15); exp.increment_failure();
97 assert_eq!(exp.backoff_duration().as_millis(), 25);
98
99 exp.increment_failure();
100 assert_eq!(exp.backoff_duration().as_millis(), 45);
101
102 exp.increment_failure();
103 assert_eq!(exp.backoff_duration().as_millis(), 50); assert!(!exp.can_execute_now());
106
107 exp.reset();
108
109 assert!(exp.can_execute_now());
110 }
111}