use backon::{BackoffBuilder, ExponentialBuilder};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct Retry {
pub retries: usize,
pub max_delay: Duration,
pub delay_factor: Duration,
pub randomization_factor: f64,
}
impl Default for Retry {
fn default() -> Self {
Self {
retries: 5,
max_delay: Duration::from_secs(30),
delay_factor: Duration::from_millis(100),
randomization_factor: 0.25,
}
}
}
#[derive(Debug)]
pub struct Backoff {
backoff: backon::ExponentialBackoff,
}
impl Backoff {
pub fn new(retry: &Retry) -> Backoff {
let builder = ExponentialBuilder::new()
.with_min_delay(retry.delay_factor)
.with_max_delay(retry.max_delay)
.with_factor(2.0) .with_max_times(retry.retries);
#[cfg(not(test))]
let builder = if retry.randomization_factor > 0.0 {
builder.with_jitter()
} else {
builder
};
Backoff {
backoff: builder.build(),
}
}
pub fn next_backoff(&mut self) -> Option<Duration> {
self.backoff.next()
}
}
#[cfg(test)]
mod test {
use super::*;
fn approx_eq(a: Option<Duration>, b: Option<Duration>) -> bool {
match (a, b) {
(Some(a), Some(b)) => a.abs_diff(b) < Duration::from_millis(1),
(None, None) => true,
_ => false,
}
}
#[tokio::test]
async fn backoff_three_retries() {
let retry = Retry {
retries: 3,
..Default::default()
};
let mut backoff = Backoff::new(&retry);
assert!(approx_eq(
backoff.next_backoff(),
Some(Duration::from_millis(100))
));
assert!(approx_eq(
backoff.next_backoff(),
Some(Duration::from_millis(200))
));
assert!(approx_eq(
backoff.next_backoff(),
Some(Duration::from_millis(400))
));
assert_eq!(backoff.next_backoff(), None); }
}