Skip to main content

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(ConstantBackoffConfig),
12
13    /// Configuration for [Backoff::Exponential].
14    Exponential(ExponentialBackoffConfig),
15
16    /// Configuration for [Backoff::Fibonacci].
17    Fibonacci(FibonacciBackoffConfig),
18
19    /// Configuration for [Backoff::NoBackoff].
20    NoBackoff,
21}
22
23impl From<ConstantBackoffConfig> for BackoffConfig {
24    fn from(config: ConstantBackoffConfig) -> BackoffConfig {
25        BackoffConfig::Constant(config)
26    }
27}
28
29impl From<ExponentialBackoffConfig> for BackoffConfig {
30    fn from(config: ExponentialBackoffConfig) -> BackoffConfig {
31        BackoffConfig::Exponential(config)
32    }
33}
34
35impl From<FibonacciBackoffConfig> for BackoffConfig {
36    fn from(config: FibonacciBackoffConfig) -> BackoffConfig {
37        BackoffConfig::Fibonacci(config)
38    }
39}
40
41#[derive(Debug, smart_default::SmartDefault, Clone, Copy, Deserialize, PartialEq)]
42/// Configuration for [Backoff::Constant].
43pub struct ConstantBackoffConfig {
44    /// Backoff delay.
45    ///
46    /// Defaults to `500 millis` - see [defaults::delay].
47    #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
48    #[default(defaults::delay())]
49    pub delay: Duration,
50
51    /// Maximum amount of retries.
52    ///
53    /// Defaults to `4` - see [defaults::max_retries].
54    #[serde(default = "defaults::max_retries")]
55    #[default(defaults::max_retries())]
56    pub max_retries: usize,
57
58    /// Whether jitter is enabled.
59    ///
60    /// Defaults to `true` - see [defaults::jitter_enabled].
61    #[serde(default = "defaults::jitter_enabled")]
62    #[default(defaults::jitter_enabled())]
63    pub jitter_enabled: bool,
64
65    /// Random seed to initialize the random jitter generator.
66    ///
67    /// Defaults to `None` - see [defaults::jitter_seed].
68    #[serde(default = "defaults::jitter_seed")]
69    #[default(defaults::jitter_seed())]
70    pub jitter_seed: Option<u64>,
71}
72
73#[derive(Debug, smart_default::SmartDefault, Clone, Copy, Deserialize, PartialEq)]
74/// Configuration for [Backoff::Exponential].
75pub struct ExponentialBackoffConfig {
76    /// Initial backoff delay.
77    ///
78    /// Defaults to `500 millis` - see [defaults::delay].
79    #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
80    #[default(defaults::delay())]
81    pub initial_delay: Duration,
82
83    /// Backoff factor.
84    ///
85    /// Defaults to `2.0` - see [defaults::factor].
86    #[serde(default = "defaults::factor")]
87    #[default(defaults::factor())]
88    pub factor: f32,
89
90    /// Maximum backoff delay.
91    ///
92    /// Defaults to `30 seconds` - see [defaults::max_delay].
93    #[serde(
94        default = "defaults::max_delay",
95        deserialize_with = "deserialize_duration"
96    )]
97    #[default(defaults::max_delay())]
98    pub max_delay: Duration,
99
100    /// Maximum amount of retries.
101    ///
102    /// Defaults to `4` - see [defaults::max_retries].
103    #[serde(default = "defaults::max_retries")]
104    #[default(defaults::max_retries())]
105    pub max_retries: usize,
106
107    /// Maximum total backoff delay.
108    ///
109    /// Defaults to `60 seconds` - see [defaults::max_total_delay]
110    #[serde(
111        default = "defaults::max_total_delay",
112        deserialize_with = "deserialize_duration"
113    )]
114    #[default(defaults::max_total_delay())]
115    pub max_total_delay: Duration,
116
117    /// Whether jitter is enabled.
118    ///
119    /// Defaults to `true` - see [defaults::jitter_enabled].
120    #[serde(default = "defaults::jitter_enabled")]
121    #[default(defaults::jitter_enabled())]
122    pub jitter_enabled: bool,
123
124    /// Random seed to initialize the random jitter generator.
125    ///
126    /// Defaults to `None` - see [defaults::jitter_seed].
127    #[serde(default = "defaults::jitter_seed")]
128    #[default(defaults::jitter_seed())]
129    pub jitter_seed: Option<u64>,
130}
131
132#[derive(Debug, smart_default::SmartDefault, Clone, Copy, Deserialize, PartialEq)]
133/// Configuration for [Backoff::Fibonacci].
134pub struct FibonacciBackoffConfig {
135    /// Initial backoff delay.
136    ///
137    /// Defaults to `500 millis` - see [defaults::delay].
138    #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
139    #[default(defaults::delay())]
140    pub initial_delay: Duration,
141
142    /// Maximum backoff delay.
143    ///
144    /// Defaults to `30 seconds` - see [defaults::max_delay].
145    #[serde(
146        default = "defaults::max_delay",
147        deserialize_with = "deserialize_duration"
148    )]
149    #[default(defaults::max_delay())]
150    pub max_delay: Duration,
151
152    /// Maximum amount of retries.
153    ///
154    /// Defaults to `4` - see [defaults::max_retries].
155    #[serde(default = "defaults::max_retries")]
156    #[default(defaults::max_retries())]
157    pub max_retries: usize,
158
159    /// Whether jitter is enabled.
160    ///
161    /// Defaults to `true` - see [defaults::jitter_enabled].
162    #[serde(default = "defaults::jitter_enabled")]
163    #[default(defaults::jitter_enabled())]
164    pub jitter_enabled: bool,
165
166    /// Random seed to initialize the random jitter generator.
167    ///
168    /// Defaults to `None` - see [defaults::jitter_seed].
169    #[serde(default = "defaults::jitter_seed")]
170    #[default(defaults::jitter_seed())]
171    pub jitter_seed: Option<u64>,
172}
173
174impl backon::BackoffBuilder for BackoffConfig {
175    type Backoff = Backoff;
176
177    fn build(self) -> Backoff {
178        match self {
179            BackoffConfig::Constant(ConstantBackoffConfig {
180                delay,
181                max_retries,
182                jitter_enabled,
183                jitter_seed,
184            }) => {
185                let mut builder = backon::ConstantBuilder::new()
186                    .with_delay(delay)
187                    .with_max_times(max_retries);
188
189                if jitter_enabled {
190                    builder = builder.with_jitter();
191                }
192
193                if let Some(jitter_seed) = jitter_seed {
194                    builder = builder.with_jitter_seed(jitter_seed);
195                }
196
197                Backoff::Constant(builder.build())
198            }
199
200            BackoffConfig::Exponential(ExponentialBackoffConfig {
201                initial_delay,
202                factor,
203                max_delay,
204                max_retries,
205                max_total_delay,
206                jitter_enabled,
207                jitter_seed,
208            }) => {
209                let mut builder = backon::ExponentialBuilder::new()
210                    .with_min_delay(initial_delay)
211                    .with_factor(factor)
212                    .with_max_delay(max_delay)
213                    .with_max_times(max_retries)
214                    .with_total_delay(Some(max_total_delay));
215
216                if jitter_enabled {
217                    builder = builder.with_jitter();
218                }
219
220                if let Some(jitter_seed) = jitter_seed {
221                    builder = builder.with_jitter_seed(jitter_seed);
222                }
223
224                Backoff::Exponential(builder.build())
225            }
226
227            BackoffConfig::Fibonacci(FibonacciBackoffConfig {
228                initial_delay,
229                max_delay,
230                max_retries,
231                jitter_enabled,
232                jitter_seed,
233            }) => {
234                let mut builder = backon::FibonacciBuilder::new()
235                    .with_min_delay(initial_delay)
236                    .with_max_delay(max_delay)
237                    .with_max_times(max_retries);
238
239                if jitter_enabled {
240                    builder = builder.with_jitter();
241                }
242
243                if let Some(jitter_seed) = jitter_seed {
244                    builder = builder.with_jitter_seed(jitter_seed);
245                }
246
247                Backoff::Fibonacci(builder.build())
248            }
249
250            BackoffConfig::NoBackoff => Backoff::NoBackoff,
251        }
252    }
253}
254
255/// Contains the defaults used by the [crate::BackoffConfig].
256pub mod defaults {
257    use std::time::Duration;
258
259    /// Default value for constant / initial backoff delay.
260    pub const fn delay() -> Duration {
261        Duration::from_millis(500)
262    }
263
264    /// Default value for max retries.
265    pub const fn max_retries() -> usize {
266        4
267    }
268
269    /// Default value whether jitter is enabled.
270    pub const fn jitter_enabled() -> bool {
271        true
272    }
273
274    /// Default value for jitter seed.
275    pub const fn jitter_seed() -> Option<u64> {
276        None
277    }
278
279    /// Default value for backoff factor.
280    pub const fn factor() -> f32 {
281        2.0
282    }
283
284    /// Default value for max backoff delay.
285    pub const fn max_delay() -> Duration {
286        Duration::from_secs(30)
287    }
288
289    /// Default value for max total backoff delay.
290    pub const fn max_total_delay() -> Duration {
291        Duration::from_secs(60)
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use backon::BackoffBuilder;
299    use std::time::Duration;
300    #[test]
301    fn backoff_config_from() {
302        let constant_config = ConstantBackoffConfig {
303            delay: Duration::from_secs(1),
304            max_retries: 3,
305            jitter_enabled: false,
306            jitter_seed: None,
307        };
308        let backoff_config: BackoffConfig = constant_config.into();
309        assert_eq!(backoff_config, BackoffConfig::Constant(constant_config));
310
311        let exponential_config = ExponentialBackoffConfig {
312            initial_delay: Duration::from_millis(100),
313            factor: 2_f32,
314            max_delay: Duration::from_millis(800),
315            max_retries: 5,
316            max_total_delay: Duration::from_secs(1000),
317            jitter_enabled: false,
318            jitter_seed: None,
319        };
320        let backoff_config: BackoffConfig = exponential_config.into();
321        assert_eq!(
322            backoff_config,
323            BackoffConfig::Exponential(exponential_config)
324        );
325
326        let fibonacci_config = FibonacciBackoffConfig {
327            initial_delay: Duration::from_millis(100),
328            max_delay: Duration::from_millis(800),
329            max_retries: 5,
330            jitter_enabled: false,
331            jitter_seed: None,
332        };
333        let backoff_config: BackoffConfig = fibonacci_config.into();
334        assert_eq!(backoff_config, BackoffConfig::Fibonacci(fibonacci_config));
335    }
336
337    #[test]
338    fn defaults() {
339        let constant = ConstantBackoffConfig::default();
340        assert_eq!(
341            constant,
342            ConstantBackoffConfig {
343                delay: defaults::delay(),
344                max_retries: defaults::max_retries(),
345                jitter_enabled: defaults::jitter_enabled(),
346                jitter_seed: defaults::jitter_seed(),
347            }
348        );
349
350        let exponential = ExponentialBackoffConfig::default();
351        assert_eq!(
352            exponential,
353            ExponentialBackoffConfig {
354                initial_delay: defaults::delay(),
355                factor: defaults::factor(),
356                max_delay: defaults::max_delay(),
357                max_retries: defaults::max_retries(),
358                max_total_delay: defaults::max_total_delay(),
359                jitter_enabled: defaults::jitter_enabled(),
360                jitter_seed: defaults::jitter_seed(),
361            }
362        );
363
364        let fibonacci = FibonacciBackoffConfig::default();
365        assert_eq!(
366            fibonacci,
367            FibonacciBackoffConfig {
368                initial_delay: defaults::delay(),
369                max_delay: defaults::max_delay(),
370                max_retries: defaults::max_retries(),
371                jitter_enabled: defaults::jitter_enabled(),
372                jitter_seed: defaults::jitter_seed(),
373            }
374        );
375    }
376
377    #[test]
378    fn constant_backoff_config_to_backoff() {
379        let config = BackoffConfig::Constant(ConstantBackoffConfig {
380            delay: Duration::from_secs(1),
381            max_retries: 3,
382            jitter_enabled: false,
383            jitter_seed: None,
384        });
385
386        let backoff = config.build();
387        assert!(matches!(backoff, Backoff::Constant(_)));
388
389        assert_eq!(
390            backoff
391                .take(100)
392                .map(|duration| duration.as_millis())
393                .collect::<Vec<_>>(),
394            vec![1000; 3]
395        );
396    }
397
398    #[test]
399    fn constant_backoff_config_to_backoff_with_jitter() {
400        let config = BackoffConfig::Constant(ConstantBackoffConfig {
401            delay: Duration::from_secs(1),
402            max_retries: 3,
403            jitter_enabled: true,
404            jitter_seed: Some(0),
405        });
406
407        let backoff = config.build();
408        assert!(matches!(backoff, Backoff::Constant(_)));
409
410        assert_eq!(
411            backoff
412                .take(100)
413                .map(|duration| duration.as_millis())
414                .collect::<Vec<_>>(),
415            vec![1552, 1096, 1593]
416        );
417    }
418
419    #[test]
420    fn exponential_backoff_config_to_backoff() {
421        let config = BackoffConfig::Exponential(ExponentialBackoffConfig {
422            initial_delay: Duration::from_millis(100),
423            factor: 2_f32,
424            max_delay: Duration::from_millis(800),
425            max_retries: 5,
426            max_total_delay: Duration::from_secs(1000),
427            jitter_enabled: false,
428            jitter_seed: None,
429        });
430
431        let backoff = config.build();
432        assert!(matches!(backoff, Backoff::Exponential(_)));
433
434        assert_eq!(
435            backoff
436                .take(100)
437                .map(|duration| duration.as_millis())
438                .collect::<Vec<_>>(),
439            vec![100, 200, 400, 800, 800]
440        );
441    }
442
443    #[test]
444    fn exponential_backoff_config_to_backoff_with_jitter() {
445        let config = BackoffConfig::Exponential(ExponentialBackoffConfig {
446            initial_delay: Duration::from_millis(100),
447            factor: 2_f32,
448            max_delay: Duration::from_millis(800),
449            max_retries: 5,
450            max_total_delay: Duration::from_secs(1000),
451            jitter_enabled: true,
452            jitter_seed: Some(0),
453        });
454
455        let backoff = config.build();
456        assert!(matches!(backoff, Backoff::Exponential(_)));
457
458        assert_eq!(
459            backoff
460                .take(100)
461                .map(|duration| duration.as_millis())
462                .collect::<Vec<_>>(),
463            vec![155, 219, 637, 923, 1102]
464        );
465    }
466
467    #[test]
468    fn exponential_backoff_config_to_backoff_with_max_total_delay() {
469        let config = BackoffConfig::Exponential(ExponentialBackoffConfig {
470            initial_delay: Duration::from_millis(100),
471            factor: 2_f32,
472            max_delay: Duration::from_millis(800),
473            max_retries: 5,
474            max_total_delay: Duration::from_millis(1500 + 1),
475            jitter_enabled: false,
476            jitter_seed: None,
477        });
478
479        let backoff = config.build();
480        assert!(matches!(backoff, Backoff::Exponential(_)));
481
482        assert_eq!(
483            backoff
484                .take(100)
485                .map(|duration| duration.as_millis())
486                .collect::<Vec<_>>(),
487            vec![100, 200, 400, 800]
488        );
489    }
490
491    #[test]
492    fn fibonacci_backoff_config_to_backoff() {
493        let config = BackoffConfig::Fibonacci(FibonacciBackoffConfig {
494            initial_delay: Duration::from_millis(100),
495            max_delay: Duration::from_millis(800),
496            max_retries: 5,
497            jitter_enabled: false,
498            jitter_seed: None,
499        });
500
501        let backoff = config.build();
502        assert!(matches!(backoff, Backoff::Fibonacci(_)));
503
504        assert_eq!(
505            backoff
506                .take(usize::MAX)
507                .map(|duration| duration.as_millis())
508                .collect::<Vec<_>>(),
509            vec![100, 100, 200, 300, 500]
510        );
511    }
512
513    #[test]
514    fn fibonacci_backoff_config_to_backoff_with_jitter() {
515        let config = BackoffConfig::Fibonacci(FibonacciBackoffConfig {
516            initial_delay: Duration::from_millis(100),
517            max_delay: Duration::from_millis(800),
518            max_retries: 5,
519            jitter_enabled: true,
520            jitter_seed: Some(0),
521        });
522
523        let backoff = config.build();
524        assert!(matches!(backoff, Backoff::Fibonacci(_)));
525
526        assert_eq!(
527            backoff
528                .take(usize::MAX)
529                .map(|duration| duration.as_millis())
530                .collect::<Vec<_>>(),
531            vec![155, 109, 259, 315, 537]
532        );
533    }
534
535    #[test]
536    fn no_backoff_backoff_config_to_backoff() {
537        let config = BackoffConfig::NoBackoff;
538
539        let mut backoff = config.build();
540        assert!(matches!(backoff, Backoff::NoBackoff));
541
542        assert!(backoff.next().is_none());
543    }
544}