1use crate::*;
2use duration_str::*;
3use serde::Deserialize;
4use std::time::Duration;
5
6#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
7#[serde(tag = "strategy")]
8pub enum BackoffConfig {
10 Constant(ConstantBackoffConfig),
12
13 Exponential(ExponentialBackoffConfig),
15
16 Fibonacci(FibonacciBackoffConfig),
18
19 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)]
42pub struct ConstantBackoffConfig {
44 #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
48 #[default(defaults::delay())]
49 pub delay: Duration,
50
51 #[serde(default = "defaults::max_retries")]
55 #[default(defaults::max_retries())]
56 pub max_retries: usize,
57
58 #[serde(default = "defaults::jitter_enabled")]
62 #[default(defaults::jitter_enabled())]
63 pub jitter_enabled: bool,
64
65 #[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)]
74pub struct ExponentialBackoffConfig {
76 #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
80 #[default(defaults::delay())]
81 pub initial_delay: Duration,
82
83 #[serde(default = "defaults::factor")]
87 #[default(defaults::factor())]
88 pub factor: f32,
89
90 #[serde(
94 default = "defaults::max_delay",
95 deserialize_with = "deserialize_duration"
96 )]
97 #[default(defaults::max_delay())]
98 pub max_delay: Duration,
99
100 #[serde(default = "defaults::max_retries")]
104 #[default(defaults::max_retries())]
105 pub max_retries: usize,
106
107 #[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 #[serde(default = "defaults::jitter_enabled")]
121 #[default(defaults::jitter_enabled())]
122 pub jitter_enabled: bool,
123
124 #[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)]
133pub struct FibonacciBackoffConfig {
135 #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
139 #[default(defaults::delay())]
140 pub initial_delay: Duration,
141
142 #[serde(
146 default = "defaults::max_delay",
147 deserialize_with = "deserialize_duration"
148 )]
149 #[default(defaults::max_delay())]
150 pub max_delay: Duration,
151
152 #[serde(default = "defaults::max_retries")]
156 #[default(defaults::max_retries())]
157 pub max_retries: usize,
158
159 #[serde(default = "defaults::jitter_enabled")]
163 #[default(defaults::jitter_enabled())]
164 pub jitter_enabled: bool,
165
166 #[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
255pub mod defaults {
257 use std::time::Duration;
258
259 pub const fn delay() -> Duration {
261 Duration::from_millis(500)
262 }
263
264 pub const fn max_retries() -> usize {
266 4
267 }
268
269 pub const fn jitter_enabled() -> bool {
271 true
272 }
273
274 pub const fn jitter_seed() -> Option<u64> {
276 None
277 }
278
279 pub const fn factor() -> f32 {
281 2.0
282 }
283
284 pub const fn max_delay() -> Duration {
286 Duration::from_secs(30)
287 }
288
289 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}