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 exponential_backoff_config_to_backoff() {
278        let config = BackoffConfig::Exponential {
279            initial_delay: Duration::from_millis(100),
280            factor: 2_f32,
281            max_delay: Duration::from_millis(800),
282            max_retries: 5,
283            max_total_delay: Duration::from_secs(1000),
284            jitter_enabled: false,
285            jitter_seed: None,
286        };
287
288        let backoff = config.build();
289        assert!(matches!(backoff, Backoff::Exponential(_)));
290
291        assert_eq!(
292            backoff
293                .take(100)
294                .map(|duration| duration.as_millis())
295                .collect::<Vec<_>>(),
296            vec![100, 200, 400, 800, 800]
297        );
298    }
299
300    #[test]
301    fn exponential_backoff_config_to_backoff_with_max_total_delay() {
302        let config = BackoffConfig::Exponential {
303            initial_delay: Duration::from_millis(100),
304            factor: 2_f32,
305            max_delay: Duration::from_millis(800),
306            max_retries: 5,
307            max_total_delay: Duration::from_millis(1500 + 1),
308            jitter_enabled: false,
309            jitter_seed: None,
310        };
311
312        let backoff = config.build();
313        assert!(matches!(backoff, Backoff::Exponential(_)));
314
315        assert_eq!(
316            backoff
317                .take(100)
318                .map(|duration| duration.as_millis())
319                .collect::<Vec<_>>(),
320            vec![100, 200, 400, 800]
321        );
322    }
323
324    #[test]
325    fn fibonacci_backoff_config_to_backoff() {
326        let config = BackoffConfig::Fibonacci {
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
334        let backoff = config.build();
335        assert!(matches!(backoff, Backoff::Fibonacci(_)));
336
337        assert_eq!(
338            backoff
339                .take(usize::MAX)
340                .map(|duration| duration.as_millis())
341                .collect::<Vec<_>>(),
342            vec![100, 100, 200, 300, 500]
343        );
344    }
345
346    #[test]
347    fn no_backoff_backoff_config_to_backoff() {
348        let config = BackoffConfig::NoBackoff;
349
350        let mut backoff = config.build();
351        assert!(matches!(backoff, Backoff::NoBackoff));
352
353        assert!(backoff.next().is_none());
354    }
355}