use std::time::Duration;
#[derive(Clone)]
pub struct ExponentialBackoff {
initial: Duration,
max: Duration,
factor: f64,
jitter: f64,
}
impl ExponentialBackoff {
pub fn new(initial: Duration, max: Duration, factor: f64) -> Self {
Self {
initial,
max,
factor,
jitter: 0.1,
}
}
pub fn jitter(mut self, jitter: f64) -> Self {
self.jitter = jitter.clamp(0.0, 1.0);
self
}
pub fn delay_for(&self, attempt: u32) -> Duration {
let base =
self.initial.as_millis() as f64 * self.factor.powi(attempt.saturating_sub(1) as i32);
let clamped = base.min(self.max.as_millis() as f64);
let jittered = if self.jitter > 0.0 {
let jitter_range = clamped * self.jitter;
let jitter_offset = rand::random::<f64>() * jitter_range * 2.0 - jitter_range;
(clamped + jitter_offset).max(0.0)
} else {
clamped
};
Duration::from_millis(jittered as u64)
}
}
impl Default for ExponentialBackoff {
fn default() -> Self {
Self {
initial: Duration::from_millis(100),
max: Duration::from_secs(30),
factor: 2.0,
jitter: 0.1,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exponential_backoff() {
let backoff =
ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(10), 2.0)
.jitter(0.0);
assert_eq!(backoff.delay_for(1), Duration::from_millis(100));
assert_eq!(backoff.delay_for(2), Duration::from_millis(200));
assert_eq!(backoff.delay_for(3), Duration::from_millis(400));
assert_eq!(backoff.delay_for(4), Duration::from_millis(800));
}
#[test]
fn test_exponential_backoff_max() {
let backoff =
ExponentialBackoff::new(Duration::from_millis(100), Duration::from_millis(500), 2.0)
.jitter(0.0);
assert_eq!(backoff.delay_for(10), Duration::from_millis(500));
}
}