use rand::Rng;
use std::time::Duration;
#[derive(Debug, Clone, Copy)]
pub enum Backoff {
Fixed(Duration),
Linear {
base: Duration,
max: Duration,
},
Exponential {
base: Duration,
factor: f64,
max: Duration,
},
}
impl Backoff {
pub fn delay(&self, attempt: u32) -> Duration {
let attempt = attempt.max(1);
match *self {
Backoff::Fixed(d) => d,
Backoff::Linear { base, max } => base.checked_mul(attempt).unwrap_or(max).min(max),
Backoff::Exponential { base, factor, max } => {
let mult = factor.powi((attempt - 1) as i32);
let secs = base.as_secs_f64() * mult;
let capped = secs.min(max.as_secs_f64());
Duration::from_secs_f64(capped.max(0.0))
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct RetryPolicy {
pub max_attempts: u32,
pub backoff: Backoff,
pub jitter: bool,
}
impl RetryPolicy {
pub fn none() -> Self {
RetryPolicy {
max_attempts: 1,
backoff: Backoff::Fixed(Duration::ZERO),
jitter: false,
}
}
pub fn fixed(max_attempts: u32, delay: Duration) -> Self {
RetryPolicy {
max_attempts,
backoff: Backoff::Fixed(delay),
jitter: false,
}
}
pub fn exponential(max_attempts: u32, base: Duration) -> Self {
RetryPolicy {
max_attempts,
backoff: Backoff::Exponential {
base,
factor: 2.0,
max: Duration::from_secs(60),
},
jitter: true,
}
}
pub fn should_retry(&self, attempts_made: u32) -> bool {
attempts_made < self.max_attempts
}
pub fn delay_for(&self, attempts_made: u32) -> Duration {
let base = self.backoff.delay(attempts_made);
if !self.jitter || base.is_zero() {
return base;
}
let secs = base.as_secs_f64();
let factor = rand::thread_rng().gen_range(0.75..=1.25);
Duration::from_secs_f64(secs * factor)
}
}
impl Default for RetryPolicy {
fn default() -> Self {
RetryPolicy::exponential(3, Duration::from_millis(100))
}
}