backoff_config/
backoff_config.rs

1use crate::*;
2use duration_str::*;
3use serde::Deserialize;
4use std::time::Duration;
5
6#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
7#[serde(tag = "strategy")]
8/// Configuration for [Backoff].
9pub enum BackoffConfig {
10    /// Configuration for [Backoff::Constant].
11    Constant {
12        /// Backoff delay.
13        ///
14        /// Defaults to `500 millis` - see [defaults::delay].
15        #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
16        delay: Duration,
17
18        /// Maximum amount of retries.
19        ///
20        /// Defaults to `4` - see [defaults::max_retries].
21        #[serde(default = "defaults::max_retries")]
22        max_retries: usize,
23
24        /// Whether jitter is enabled.
25        ///
26        /// Defaults to `true` - see [defaults::jitter_enabled].
27        #[serde(default = "defaults::jitter_enabled")]
28        jitter_enabled: bool,
29
30        /// Random seed to initialize the random jitter generator.
31        ///
32        /// Defaults to `None` - see [defaults::jitter_seed].
33        #[serde(default = "defaults::jitter_seed")]
34        jitter_seed: Option<u64>,
35    },
36
37    /// Configuration for [Backoff::Exponential].
38    Exponential {
39        /// Initial backoff delay.
40        ///
41        /// Defaults to `500 millis` - see [defaults::delay].
42        #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
43        initial_delay: Duration,
44
45        /// Backoff factor.
46        ///
47        /// Defaults to `2.0` - see [defaults::factor].
48        #[serde(default = "defaults::factor")]
49        factor: f32,
50
51        /// Maximum backoff delay.
52        ///
53        /// Defaults to `60 seconds` - see [defaults::max_delay].
54        #[serde(
55            default = "defaults::max_delay",
56            deserialize_with = "deserialize_duration"
57        )]
58        max_delay: Duration,
59
60        /// Maximum amount of retries.
61        ///
62        /// Defaults to `4` - see [defaults::max_retries].
63        #[serde(default = "defaults::max_retries")]
64        max_retries: usize,
65
66        /// Maximum total backoff delay.
67        ///
68        /// Defaults to `None` - see [defaults::max_total_delay]
69        #[serde(
70            default = "defaults::max_total_delay",
71            deserialize_with = "deserialize_duration"
72        )]
73        max_total_delay: Duration,
74
75        /// Whether jitter is enabled.
76        ///
77        /// Defaults to `true` - see [defaults::jitter_enabled].
78        #[serde(default = "defaults::jitter_enabled")]
79        jitter_enabled: bool,
80
81        /// Random seed to initialize the random jitter generator.
82        ///
83        /// Defaults to `None` - see [defaults::jitter_seed].
84        #[serde(default = "defaults::jitter_seed")]
85        jitter_seed: Option<u64>,
86    },
87
88    /// Configuration for [Backoff::Fibonacci].
89    Fibonacci {
90        /// Initial backoff delay.
91        ///
92        /// Defaults to `500 millis` - see [defaults::delay].
93        #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
94        initial_delay: Duration,
95
96        /// Maximum backoff delay.
97        ///
98        /// Defaults to `60 seconds` - see [defaults::max_delay].
99        #[serde(
100            default = "defaults::max_delay",
101            deserialize_with = "deserialize_duration"
102        )]
103        max_delay: Duration,
104
105        /// Maximum amount of retries.
106        ///
107        /// Defaults to `4` - see [defaults::max_retries].
108        #[serde(default = "defaults::max_retries")]
109        max_retries: usize,
110
111        /// Whether jitter is enabled.
112        ///
113        /// Defaults to `true` - see [defaults::jitter_enabled].
114        #[serde(default = "defaults::jitter_enabled")]
115        jitter_enabled: bool,
116
117        /// Random seed to initialize the random jitter generator.
118        ///
119        /// Defaults to `None` - see [defaults::jitter_seed].
120        #[serde(default = "defaults::jitter_seed")]
121        jitter_seed: Option<u64>,
122    },
123
124    /// Configuration for [Backoff::NoBackoff].
125    NoBackoff,
126}
127
128impl backon::BackoffBuilder for BackoffConfig {
129    type Backoff = Backoff;
130
131    fn build(self) -> Backoff {
132        match self {
133            BackoffConfig::Constant {
134                delay,
135                max_retries,
136                jitter_enabled,
137                jitter_seed,
138            } => {
139                let mut builder = backon::ConstantBuilder::new()
140                    .with_delay(delay)
141                    .with_max_times(max_retries);
142
143                if jitter_enabled {
144                    builder = builder.with_jitter();
145                }
146
147                if let Some(jitter_seed) = jitter_seed {
148                    builder = builder.with_jitter_seed(jitter_seed);
149                }
150
151                Backoff::Constant(builder.build())
152            }
153
154            BackoffConfig::Exponential {
155                initial_delay,
156                factor,
157                max_delay,
158                max_retries,
159                max_total_delay,
160                jitter_enabled,
161                jitter_seed,
162            } => {
163                let mut builder = backon::ExponentialBuilder::new()
164                    .with_min_delay(initial_delay)
165                    .with_factor(factor)
166                    .with_max_delay(max_delay)
167                    .with_max_times(max_retries)
168                    .with_total_delay(Some(max_total_delay));
169
170                if jitter_enabled {
171                    builder = builder.with_jitter();
172                }
173
174                if let Some(jitter_seed) = jitter_seed {
175                    builder = builder.with_jitter_seed(jitter_seed);
176                }
177
178                Backoff::Exponential(builder.build())
179            }
180
181            BackoffConfig::Fibonacci {
182                initial_delay,
183                max_delay,
184                max_retries,
185                jitter_enabled,
186                jitter_seed,
187            } => {
188                let mut builder = backon::FibonacciBuilder::new()
189                    .with_min_delay(initial_delay)
190                    .with_max_delay(max_delay)
191                    .with_max_times(max_retries);
192
193                if jitter_enabled {
194                    builder = builder.with_jitter();
195                }
196
197                if let Some(jitter_seed) = jitter_seed {
198                    builder = builder.with_jitter_seed(jitter_seed);
199                }
200
201                Backoff::Fibonacci(builder.build())
202            }
203
204            BackoffConfig::NoBackoff => Backoff::NoBackoff,
205        }
206    }
207}
208
209/// Contains the defaults used by the [crate::BackoffConfig].
210pub mod defaults {
211    use std::time::Duration;
212
213    /// Default value for constant / initial backoff delay.
214    pub const fn delay() -> Duration {
215        Duration::from_millis(500)
216    }
217
218    /// Default value for max retries.
219    pub const fn max_retries() -> usize {
220        4
221    }
222
223    /// Default value whether jitter is enabled.
224    pub const fn jitter_enabled() -> bool {
225        true
226    }
227
228    /// Default value for jitter seed.
229    pub const fn jitter_seed() -> Option<u64> {
230        None
231    }
232
233    /// Default value for backoff factor.
234    pub const fn factor() -> f32 {
235        2.0
236    }
237
238    /// Default value for max backoff delay.
239    pub const fn max_delay() -> Duration {
240        Duration::from_secs(30)
241    }
242
243    /// Default value for max total backoff delay.
244    pub const fn max_total_delay() -> Duration {
245        Duration::from_secs(60)
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use backon::BackoffBuilder;
253    use std::time::Duration;
254
255    #[test]
256    fn constant_backoff_config_to_backoff() {
257        let config = BackoffConfig::Constant {
258            delay: Duration::from_secs(1),
259            max_retries: 3,
260            jitter_enabled: false,
261            jitter_seed: None,
262        };
263
264        let backoff = config.build();
265        assert!(matches!(backoff, Backoff::Constant(_)));
266
267        assert_eq!(
268            backoff
269                .take(100)
270                .map(|duration| duration.as_millis())
271                .collect::<Vec<_>>(),
272            vec![1000; 3]
273        );
274    }
275
276    #[test]
277    fn constant_backoff_config_to_backoff_with_jitter() {
278        let config = BackoffConfig::Constant {
279            delay: Duration::from_secs(1),
280            max_retries: 3,
281            jitter_enabled: true,
282            jitter_seed: Some(0),
283        };
284
285        let backoff = config.build();
286        assert!(matches!(backoff, Backoff::Constant(_)));
287
288        assert_eq!(
289            backoff
290                .take(100)
291                .map(|duration| duration.as_millis())
292                .collect::<Vec<_>>(),
293            vec![1552, 1096, 1593]
294        );
295    }
296
297    #[test]
298    fn exponential_backoff_config_to_backoff() {
299        let config = BackoffConfig::Exponential {
300            initial_delay: Duration::from_millis(100),
301            factor: 2_f32,
302            max_delay: Duration::from_millis(800),
303            max_retries: 5,
304            max_total_delay: Duration::from_secs(1000),
305            jitter_enabled: false,
306            jitter_seed: None,
307        };
308
309        let backoff = config.build();
310        assert!(matches!(backoff, Backoff::Exponential(_)));
311
312        assert_eq!(
313            backoff
314                .take(100)
315                .map(|duration| duration.as_millis())
316                .collect::<Vec<_>>(),
317            vec![100, 200, 400, 800, 800]
318        );
319    }
320
321    #[test]
322    fn exponential_backoff_config_to_backoff_with_jitter() {
323        let config = BackoffConfig::Exponential {
324            initial_delay: Duration::from_millis(100),
325            factor: 2_f32,
326            max_delay: Duration::from_millis(800),
327            max_retries: 5,
328            max_total_delay: Duration::from_secs(1000),
329            jitter_enabled: true,
330            jitter_seed: Some(0),
331        };
332
333        let backoff = config.build();
334        assert!(matches!(backoff, Backoff::Exponential(_)));
335
336        assert_eq!(
337            backoff
338                .take(100)
339                .map(|duration| duration.as_millis())
340                .collect::<Vec<_>>(),
341            vec![155, 219, 637, 923, 1102]
342        );
343    }
344
345    #[test]
346    fn exponential_backoff_config_to_backoff_with_max_total_delay() {
347        let config = BackoffConfig::Exponential {
348            initial_delay: Duration::from_millis(100),
349            factor: 2_f32,
350            max_delay: Duration::from_millis(800),
351            max_retries: 5,
352            max_total_delay: Duration::from_millis(1500 + 1),
353            jitter_enabled: false,
354            jitter_seed: None,
355        };
356
357        let backoff = config.build();
358        assert!(matches!(backoff, Backoff::Exponential(_)));
359
360        assert_eq!(
361            backoff
362                .take(100)
363                .map(|duration| duration.as_millis())
364                .collect::<Vec<_>>(),
365            vec![100, 200, 400, 800]
366        );
367    }
368
369    #[test]
370    fn fibonacci_backoff_config_to_backoff() {
371        let config = BackoffConfig::Fibonacci {
372            initial_delay: Duration::from_millis(100),
373            max_delay: Duration::from_millis(800),
374            max_retries: 5,
375            jitter_enabled: false,
376            jitter_seed: None,
377        };
378
379        let backoff = config.build();
380        assert!(matches!(backoff, Backoff::Fibonacci(_)));
381
382        assert_eq!(
383            backoff
384                .take(usize::MAX)
385                .map(|duration| duration.as_millis())
386                .collect::<Vec<_>>(),
387            vec![100, 100, 200, 300, 500]
388        );
389    }
390
391    #[test]
392    fn fibonacci_backoff_config_to_backoff_with_jitter() {
393        let config = BackoffConfig::Fibonacci {
394            initial_delay: Duration::from_millis(100),
395            max_delay: Duration::from_millis(800),
396            max_retries: 5,
397            jitter_enabled: true,
398            jitter_seed: Some(0),
399        };
400
401        let backoff = config.build();
402        assert!(matches!(backoff, Backoff::Fibonacci(_)));
403
404        assert_eq!(
405            backoff
406                .take(usize::MAX)
407                .map(|duration| duration.as_millis())
408                .collect::<Vec<_>>(),
409            vec![155, 109, 259, 315, 537]
410        );
411    }
412
413    #[test]
414    fn no_backoff_backoff_config_to_backoff() {
415        let config = BackoffConfig::NoBackoff;
416
417        let mut backoff = config.build();
418        assert!(matches!(backoff, Backoff::NoBackoff));
419
420        assert!(backoff.next().is_none());
421    }
422}