try_again/delay/
exponential.rs1use crate::StdDuration;
2use crate::tracked_iterator::{FiniteIterator, IntoTrackedIterator};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct ExponentialBackoff {
6 pub initial_delay: StdDuration,
7}
8
9impl ExponentialBackoff {
10 pub fn of_initial_delay(initial_delay: impl Into<StdDuration>) -> Self {
11 Self {
12 initial_delay: initial_delay.into(),
13 }
14 }
15
16 pub fn uncapped(self) -> ExponentialBackoffWithCap {
17 ExponentialBackoffWithCap {
18 initial_delay: self.initial_delay,
19 last_delay: StdDuration::ZERO,
20 max_delay: None,
21 first: true,
22 }
23 }
24
25 pub fn capped_at(self, max_delay: impl Into<StdDuration>) -> ExponentialBackoffWithCap {
26 ExponentialBackoffWithCap {
27 initial_delay: self.initial_delay,
28 last_delay: StdDuration::ZERO,
29 max_delay: Some(max_delay.into()),
30 first: true,
31 }
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct ExponentialBackoffWithCap {
37 pub initial_delay: StdDuration,
38 pub last_delay: StdDuration,
39 pub max_delay: Option<StdDuration>,
40 pub first: bool,
41}
42
43impl ExponentialBackoffWithCap {
44 pub fn take(self, count: usize) -> FiniteIterator<std::iter::Take<ExponentialBackoffWithCap>> {
45 self.into_tracked().take(count)
46 }
47}
48
49impl Iterator for ExponentialBackoffWithCap {
50 type Item = StdDuration;
51
52 fn next(&mut self) -> Option<Self::Item> {
53 if self.first {
54 self.first = false;
55 self.last_delay = self.initial_delay;
56 return Some(self.initial_delay);
57 }
58
59 let mut next = self.last_delay * 2;
60 if let Some(max_delay) = self.max_delay {
61 if next > max_delay {
62 next = max_delay;
63 }
64 }
65 self.last_delay = next;
66 Some(next)
67 }
68}
69
70#[cfg(test)]
71mod test {
72 use super::*;
73 use crate::IntoStdDuration;
74 use assertr::prelude::*;
75
76 #[test]
77 fn uncapped_exponential_backoff_delay_strategy_returns_initial_delay_for_the_first_try_and_doubles_the_delay_for_each_retry_until_reaching_max_tries()
78 {
79 let mut delay = ExponentialBackoff::of_initial_delay(50.millis())
80 .uncapped()
81 .take(4);
82
83 assert_that(delay.next()).is_some().is_equal_to(50.millis());
84 assert_that(delay.next())
85 .is_some()
86 .is_equal_to(100.millis());
87 assert_that(delay.next())
88 .is_some()
89 .is_equal_to(200.millis());
90 assert_that(delay.next())
91 .is_some()
92 .is_equal_to(400.millis());
93 assert_that(delay.next()).is_none();
94 }
95
96 #[test]
97 fn capped_exponential_backoff_delay_strategy_returns_initial_delay_for_the_first_try_and_doubles_the_delay_for_each_retry_until_capping_at_specified_max_delay_before_reaching_max_tries()
98 {
99 let mut delay = ExponentialBackoff::of_initial_delay(50.millis())
100 .capped_at(250.millis())
101 .take(5);
102
103 assert_that(delay.next()).is_some().is_equal_to(50.millis());
104 assert_that(delay.next())
105 .is_some()
106 .is_equal_to(100.millis());
107 assert_that(delay.next())
108 .is_some()
109 .is_equal_to(200.millis());
110 assert_that(delay.next())
111 .is_some()
112 .is_equal_to(250.millis());
113 assert_that(delay.next())
114 .is_some()
115 .is_equal_to(250.millis());
116 assert_that(delay.next()).is_none();
117 }
118}