diff --git a/backon/src/backoff/constant.rs b/backon/src/backoff/constant.rs index 39aa918..9f3f308 100644 --- a/backon/src/backoff/constant.rs +++ b/backon/src/backoff/constant.rs @@ -68,6 +68,16 @@ impl ConstantBuilder { self.jitter = true; self } + + /// Set no max times for the backoff. + /// + /// The backoff will not stop by itself. + /// + /// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._ + pub fn without_max_times(mut self) -> Self { + self.max_times = None; + self + } } impl BackoffBuilder for ConstantBuilder { @@ -139,32 +149,32 @@ mod tests { #[test] fn test_constant_default() { - let mut exp = ConstantBuilder::default().build(); + let mut it = ConstantBuilder::default().build(); - assert_eq!(Some(Duration::from_secs(1)), exp.next()); - assert_eq!(Some(Duration::from_secs(1)), exp.next()); - assert_eq!(Some(Duration::from_secs(1)), exp.next()); - assert_eq!(None, exp.next()); + assert_eq!(Some(Duration::from_secs(1)), it.next()); + assert_eq!(Some(Duration::from_secs(1)), it.next()); + assert_eq!(Some(Duration::from_secs(1)), it.next()); + assert_eq!(None, it.next()); } #[test] fn test_constant_with_delay() { - let mut exp = ConstantBuilder::default() + let mut it = ConstantBuilder::default() .with_delay(Duration::from_secs(2)) .build(); - assert_eq!(Some(Duration::from_secs(2)), exp.next()); - assert_eq!(Some(Duration::from_secs(2)), exp.next()); - assert_eq!(Some(Duration::from_secs(2)), exp.next()); - assert_eq!(None, exp.next()); + assert_eq!(Some(Duration::from_secs(2)), it.next()); + assert_eq!(Some(Duration::from_secs(2)), it.next()); + assert_eq!(Some(Duration::from_secs(2)), it.next()); + assert_eq!(None, it.next()); } #[test] fn test_constant_with_times() { - let mut exp = ConstantBuilder::default().with_max_times(1).build(); + let mut it = ConstantBuilder::default().with_max_times(1).build(); - assert_eq!(Some(Duration::from_secs(1)), exp.next()); - assert_eq!(None, exp.next()); + assert_eq!(Some(Duration::from_secs(1)), it.next()); + assert_eq!(None, it.next()); } #[test] @@ -175,4 +185,13 @@ mod tests { fastrand::seed(7); assert!(dur > Duration::from_secs(1)); } + + #[test] + fn test_constant_without_max_times() { + let mut it = ConstantBuilder::default().without_max_times().build(); + + for _ in 0..10_000 { + assert_eq!(Some(Duration::from_secs(1)), it.next()); + } + } } diff --git a/backon/src/backoff/exponential.rs b/backon/src/backoff/exponential.rs index cee9932..44729b8 100644 --- a/backon/src/backoff/exponential.rs +++ b/backon/src/backoff/exponential.rs @@ -91,6 +91,16 @@ impl ExponentialBuilder { self } + /// Set no maximum delay for the backoff. + /// + /// The delay will keep increasing. + /// + /// _The delay will saturate at `Duration::MAX` which is an **unrealistic** delay._ + pub fn without_max_delay(mut self) -> Self { + self.max_delay = None; + self + } + /// Set the maximum number of attempts for the current backoff. /// /// The backoff will stop if the maximum number of attempts is reached. @@ -98,6 +108,16 @@ impl ExponentialBuilder { self.max_times = Some(max_times); self } + + /// Set no maximum number of attempts for the current backoff. + /// + /// The backoff will not stop by itself. + /// + /// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._ + pub fn without_max_times(mut self) -> Self { + self.max_times = None; + self + } } impl BackoffBuilder for ExponentialBuilder { @@ -246,6 +266,21 @@ mod tests { assert_eq!(None, exp.next()); } + #[test] + fn test_exponential_no_max_times_with_default() { + let mut exp = ExponentialBuilder::default() + .with_min_delay(Duration::from_secs(1)) + .with_factor(1_f32) + .without_max_times() + .build(); + + // to fully test we would need to call this `usize::MAX` + // which seems unreasonable for a test as it would take too long... + for _ in 0..10_000 { + assert_eq!(Some(Duration::from_secs(1)), exp.next()); + } + } + #[test] fn test_exponential_max_delay_with_default() { let mut exp = ExponentialBuilder::default() @@ -258,6 +293,22 @@ mod tests { assert_eq!(None, exp.next()); } + #[test] + fn test_exponential_no_max_delay_with_default() { + let mut exp = ExponentialBuilder::default() + .with_min_delay(Duration::from_secs(1)) + .with_factor(10_000_000_000_f32) + .without_max_delay() + .with_max_times(4) + .build(); + + assert_eq!(Some(Duration::from_secs(1)), exp.next()); + assert_eq!(Some(Duration::from_secs(10_000_000_000)), exp.next()); + assert_eq!(Some(Duration::MAX), exp.next()); + assert_eq!(Some(Duration::MAX), exp.next()); + assert_eq!(None, exp.next()); + } + #[test] fn test_exponential_max_delay_without_default_1() { let mut exp = ExponentialBuilder { diff --git a/backon/src/backoff/fibonacci.rs b/backon/src/backoff/fibonacci.rs index 49ec073..feae5d2 100644 --- a/backon/src/backoff/fibonacci.rs +++ b/backon/src/backoff/fibonacci.rs @@ -75,6 +75,16 @@ impl FibonacciBuilder { self } + /// Set no maximum delay for the backoff. + /// + /// The delay will keep increasing. + /// + /// _The delay will saturate at `Duration::MAX` which is an **unrealistic** delay._ + pub fn without_max_delay(mut self) -> Self { + self.max_delay = None; + self + } + /// Set the maximum number of attempts for the current backoff. /// /// The backoff will stop if the maximum number of attempts is reached. @@ -82,6 +92,16 @@ impl FibonacciBuilder { self.max_times = Some(max_times); self } + + /// Set no maximum number of attempts for the current backoff. + /// + /// The backoff will not stop by itself. + /// + /// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._ + pub fn without_max_times(mut self) -> Self { + self.max_times = None; + self + } } impl BackoffBuilder for FibonacciBuilder { @@ -153,7 +173,7 @@ impl Iterator for FibonacciBackoff { // If current delay larger than max delay, we should stop increment anymore. if next < self.max_delay.unwrap_or(Duration::MAX) { if let Some(prev) = self.previous_delay { - next += prev; + next = next.saturating_add(prev); self.current_delay = Some(next); } self.previous_delay = Some(cur); @@ -235,6 +255,27 @@ mod tests { assert_eq!(None, fib.next()); } + #[test] + fn test_fibonacci_no_max_delay() { + let mut fib = FibonacciBuilder::default() + .with_max_times(4) + .with_min_delay(Duration::from_secs(10_000_000_000_000_000_000)) + .without_max_delay() + .build(); + + assert_eq!( + Some(Duration::from_secs(10_000_000_000_000_000_000)), + fib.next() + ); + assert_eq!( + Some(Duration::from_secs(10_000_000_000_000_000_000)), + fib.next() + ); + assert_eq!(Some(Duration::MAX), fib.next()); + assert_eq!(Some(Duration::MAX), fib.next()); + assert_eq!(None, fib.next()); + } + #[test] fn test_fibonacci_max_times() { let mut fib = FibonacciBuilder::default().with_max_times(6).build(); @@ -247,4 +288,18 @@ mod tests { assert_eq!(Some(Duration::from_secs(8)), fib.next()); assert_eq!(None, fib.next()); } + + #[test] + fn test_fibonacci_no_max_times() { + let mut fib = FibonacciBuilder::default() + .with_min_delay(Duration::from_secs(0)) + .without_max_times() + .build(); + + // to fully test we would need to call this `usize::MAX` + // which seems unreasonable for a test as it would take too long... + for _ in 0..10_000 { + assert_eq!(Some(Duration::from_secs(0)), fib.next()); + } + } }