1use std::marker::PhantomData;
2use std::time::Duration;
3#[cfg(not(target_family = "wasm"))]
4use std::time::Instant;
5#[cfg(target_family = "wasm")]
6use web_time::Instant;
7
8use crate::backoff::Backoff;
9use crate::clock::Clock;
10use crate::default;
11
12#[derive(Debug)]
13pub struct ExponentialBackoff<C> {
14 pub current_interval: Duration,
16 pub initial_interval: Duration,
18 pub randomization_factor: f64,
23 pub multiplier: f64,
25 pub max_interval: Duration,
28 pub start_time: Instant,
31 pub max_elapsed_time: Option<Duration>,
34 pub clock: C,
36}
37
38impl<C> Default for ExponentialBackoff<C>
39where
40 C: Clock + Default,
41{
42 fn default() -> ExponentialBackoff<C> {
43 let mut eb = ExponentialBackoff {
44 current_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
45 initial_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
46 randomization_factor: default::RANDOMIZATION_FACTOR,
47 multiplier: default::MULTIPLIER,
48 max_interval: Duration::from_millis(default::MAX_INTERVAL_MILLIS),
49 max_elapsed_time: Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS)),
50 clock: C::default(),
51 start_time: Instant::now(),
52 };
53 eb.reset();
54 eb
55 }
56}
57
58impl<C: Clock> ExponentialBackoff<C> {
59 pub fn get_elapsed_time(&self) -> Duration {
61 self.clock.now().duration_since(self.start_time)
62 }
63
64 fn get_random_value_from_interval(
65 randomization_factor: f64,
66 random: f64,
67 current_interval: Duration,
68 ) -> Duration {
69 let current_interval_nanos = duration_to_nanos(current_interval);
70
71 let delta = randomization_factor * current_interval_nanos;
72 let min_interval = current_interval_nanos - delta;
73 let max_interval = current_interval_nanos + delta;
74 let diff = max_interval - min_interval;
78 let nanos = min_interval + (random * (diff + 1.0));
79 nanos_to_duration(nanos)
80 }
81
82 fn increment_current_interval(&mut self) -> Duration {
83 let current_interval_nanos = duration_to_nanos(self.current_interval);
84 let max_interval_nanos = duration_to_nanos(self.max_interval);
85 if current_interval_nanos >= max_interval_nanos / self.multiplier {
87 self.max_interval
88 } else {
89 let nanos = current_interval_nanos * self.multiplier;
90 nanos_to_duration(nanos)
91 }
92 }
93}
94
95fn duration_to_nanos(d: Duration) -> f64 {
96 d.as_secs() as f64 * 1_000_000_000.0 + f64::from(d.subsec_nanos())
97}
98
99fn nanos_to_duration(nanos: f64) -> Duration {
100 let secs = nanos / 1_000_000_000.0;
101 let nanos = nanos as u64 % 1_000_000_000;
102 Duration::new(secs as u64, nanos as u32)
103}
104
105impl<C> Backoff for ExponentialBackoff<C>
106where
107 C: Clock,
108{
109 fn reset(&mut self) {
110 self.current_interval = self.initial_interval;
111 self.start_time = self.clock.now();
112 }
113
114 fn next_backoff(&mut self) -> Option<Duration> {
115 let elapsed_time = self.get_elapsed_time();
116
117 match self.max_elapsed_time {
118 Some(v) if elapsed_time > v => None,
119 _ => {
120 let random = rand::random::<f64>();
121 let randomized_interval = Self::get_random_value_from_interval(
122 self.randomization_factor,
123 random,
124 self.current_interval,
125 );
126 self.current_interval = self.increment_current_interval();
127
128 if let Some(max_elapsed_time) = self.max_elapsed_time {
129 if elapsed_time + randomized_interval <= max_elapsed_time {
130 Some(randomized_interval)
131 } else {
132 None
133 }
134 } else {
135 Some(randomized_interval)
136 }
137 }
138 }
139 }
140}
141
142impl<C> Clone for ExponentialBackoff<C>
143where
144 C: Clone,
145{
146 fn clone(&self) -> Self {
147 let clock = self.clock.clone();
148 ExponentialBackoff { clock, ..*self }
149 }
150}
151
152#[derive(Debug)]
156pub struct ExponentialBackoffBuilder<C> {
157 initial_interval: Duration,
158 randomization_factor: f64,
159 multiplier: f64,
160 max_interval: Duration,
161 max_elapsed_time: Option<Duration>,
162 _clock: PhantomData<C>,
163}
164
165impl<C> Default for ExponentialBackoffBuilder<C> {
166 fn default() -> Self {
167 Self {
168 initial_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
169 randomization_factor: default::RANDOMIZATION_FACTOR,
170 multiplier: default::MULTIPLIER,
171 max_interval: Duration::from_millis(default::MAX_INTERVAL_MILLIS),
172 max_elapsed_time: Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS)),
173 _clock: PhantomData,
174 }
175 }
176}
177
178impl<C> ExponentialBackoffBuilder<C>
179where
180 C: Clock + Default,
181{
182 pub fn new() -> Self {
183 Default::default()
184 }
185
186 pub fn with_initial_interval(&mut self, initial_interval: Duration) -> &mut Self {
188 self.initial_interval = initial_interval;
189 self
190 }
191
192 pub fn with_randomization_factor(&mut self, randomization_factor: f64) -> &mut Self {
197 self.randomization_factor = randomization_factor;
198 self
199 }
200
201 pub fn with_multiplier(&mut self, multiplier: f64) -> &mut Self {
203 self.multiplier = multiplier;
204 self
205 }
206
207 pub fn with_max_interval(&mut self, max_interval: Duration) -> &mut Self {
210 self.max_interval = max_interval;
211 self
212 }
213
214 pub fn with_max_elapsed_time(&mut self, max_elapsed_time: Option<Duration>) -> &mut Self {
217 self.max_elapsed_time = max_elapsed_time;
218 self
219 }
220
221 pub fn build(&self) -> ExponentialBackoff<C> {
222 ExponentialBackoff {
223 current_interval: self.initial_interval,
224 initial_interval: self.initial_interval,
225 randomization_factor: self.randomization_factor,
226 multiplier: self.multiplier,
227 max_interval: self.max_interval,
228 max_elapsed_time: self.max_elapsed_time,
229 clock: C::default(),
230 start_time: Instant::now(),
231 }
232 }
233}
234
235#[cfg(test)]
236use crate::clock::SystemClock;
237
238#[test]
239fn get_randomized_interval() {
240 let f = ExponentialBackoff::<SystemClock>::get_random_value_from_interval;
242 assert_eq!(Duration::new(0, 1), f(0.5, 0.0, Duration::new(0, 2)));
243 assert_eq!(Duration::new(0, 1), f(0.5, 0.33, Duration::new(0, 2)));
244 assert_eq!(Duration::new(0, 2), f(0.5, 0.34, Duration::new(0, 2)));
246 assert_eq!(Duration::new(0, 2), f(0.5, 0.66, Duration::new(0, 2)));
247 assert_eq!(Duration::new(0, 3), f(0.5, 0.67, Duration::new(0, 2)));
249 assert_eq!(Duration::new(0, 3), f(0.5, 0.99, Duration::new(0, 2)));
250}
251
252#[test]
253fn exponential_backoff_builder() {
254 let initial_interval = Duration::from_secs(1);
255 let max_interval = Duration::from_secs(2);
256 let multiplier = 3.0;
257 let randomization_factor = 4.0;
258 let backoff: ExponentialBackoff<SystemClock> = ExponentialBackoffBuilder::new()
259 .with_initial_interval(initial_interval)
260 .with_multiplier(multiplier)
261 .with_randomization_factor(randomization_factor)
262 .with_max_interval(max_interval)
263 .with_max_elapsed_time(None)
264 .build();
265 assert_eq!(backoff.initial_interval, initial_interval);
266 assert_eq!(backoff.current_interval, initial_interval);
267 assert_eq!(backoff.multiplier, multiplier);
268 assert_eq!(backoff.randomization_factor, randomization_factor);
269 assert_eq!(backoff.max_interval, max_interval);
270 assert_eq!(backoff.max_elapsed_time, None);
271}
272
273#[test]
274fn exponential_backoff_default_builder() {
275 let backoff: ExponentialBackoff<SystemClock> = ExponentialBackoffBuilder::new().build();
276 assert_eq!(
277 backoff.initial_interval,
278 Duration::from_millis(default::INITIAL_INTERVAL_MILLIS)
279 );
280 assert_eq!(
281 backoff.current_interval,
282 Duration::from_millis(default::INITIAL_INTERVAL_MILLIS)
283 );
284 assert_eq!(backoff.multiplier, default::MULTIPLIER);
285 assert_eq!(backoff.randomization_factor, default::RANDOMIZATION_FACTOR);
286 assert_eq!(
287 backoff.max_interval,
288 Duration::from_millis(default::MAX_INTERVAL_MILLIS)
289 );
290 assert_eq!(
291 backoff.max_elapsed_time,
292 Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS))
293 );
294}