use std::{cmp::min, time::Duration};
pub type BoxedBackoff = Box<dyn Backoff + Send + Sync>;
pub trait Backoff {
fn calculate_backoff(&self, attempts: usize) -> Duration;
}
impl Backoff for BoxedBackoff {
fn calculate_backoff(&self, attempts: usize) -> Duration {
(**self).calculate_backoff(attempts)
}
}
#[derive(Debug, Clone)]
pub struct ExponentialBackoff {
factor: f32,
}
impl ExponentialBackoff {
pub fn new(factor: f32) -> Self {
Self { factor }
}
}
impl Default for ExponentialBackoff {
fn default() -> Self {
Self::new(1.5)
}
}
impl Backoff for ExponentialBackoff {
fn calculate_backoff(&self, attempts: usize) -> Duration {
if attempts <= 1 {
return Duration::from_secs(0);
}
let secs = (f64::from(self.factor)) * ((1usize << min(attempts, 51)) as f64 - 1.0);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
Duration::from_secs(secs.ceil() as u64)
}
}
#[derive(Clone)]
pub struct ConstantBackoff(Duration);
impl ConstantBackoff {
pub fn new(timeout: Duration) -> Self {
Self(timeout)
}
}
impl Backoff for ConstantBackoff {
fn calculate_backoff(&self, attempts: usize) -> Duration {
if attempts <= 1 {
return Duration::from_secs(0);
}
self.0
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn default_backoff() {
let backoff = ExponentialBackoff::default();
assert_eq!(backoff.calculate_backoff(0).as_secs(), 0);
assert_eq!(backoff.calculate_backoff(1).as_secs(), 0);
assert_eq!(backoff.calculate_backoff(2).as_secs(), 5);
assert_eq!(backoff.calculate_backoff(3).as_secs(), 11);
assert_eq!(backoff.calculate_backoff(4).as_secs(), 23);
assert_eq!(backoff.calculate_backoff(5).as_secs(), 47);
assert_eq!(backoff.calculate_backoff(6).as_secs(), 95);
assert_eq!(backoff.calculate_backoff(7).as_secs(), 191);
assert_eq!(backoff.calculate_backoff(8).as_secs(), 383);
assert_eq!(backoff.calculate_backoff(9).as_secs(), 767);
assert_eq!(backoff.calculate_backoff(10).as_secs(), 1535);
assert_eq!(backoff.calculate_backoff(63).as_secs(), 3377699720527871);
assert_eq!(backoff.calculate_backoff(64).as_secs(), 3377699720527871);
assert_eq!(backoff.calculate_backoff(200).as_secs(), 3377699720527871);
}
#[test]
fn zero_backoff() {
let backoff = ExponentialBackoff::new(0.0);
assert_eq!(backoff.calculate_backoff(0).as_secs(), 0);
assert_eq!(backoff.calculate_backoff(1).as_secs(), 0);
assert_eq!(backoff.calculate_backoff(200).as_secs(), 0);
}
}