use std::time::Duration;
pub trait Backoff: Send + Sync {
fn delay(&self, attempt: u32) -> Duration;
fn max_delay(&self) -> Duration;
}
#[derive(Debug, Clone)]
pub struct ExponentialBackoff {
pub base_delay: Duration,
pub max_delay: Duration,
pub max_exponent: u32,
}
impl ExponentialBackoff {
pub fn new(base_delay: Duration, max_delay: Duration) -> Self {
Self {
base_delay,
max_delay,
max_exponent: 16,
}
}
pub fn default_settings() -> Self {
Self::new(Duration::from_millis(100), Duration::from_secs(30))
}
pub fn with_max_exponent(mut self, max_exponent: u32) -> Self {
self.max_exponent = max_exponent;
self
}
}
impl Default for ExponentialBackoff {
fn default() -> Self {
Self::default_settings()
}
}
impl Backoff for ExponentialBackoff {
fn delay(&self, attempt: u32) -> Duration {
let exponent = attempt.min(self.max_exponent);
let multiplier = 1u64.checked_shl(exponent).unwrap_or(u64::MAX);
let delay_nanos = self
.base_delay
.as_nanos()
.saturating_mul(u128::from(multiplier));
let delay = Duration::from_nanos(delay_nanos.min(u128::from(u64::MAX)) as u64);
delay.min(self.max_delay)
}
fn max_delay(&self) -> Duration {
self.max_delay
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exponential_sequence() {
let b = ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(30));
assert_eq!(b.delay(0), Duration::from_millis(100));
assert_eq!(b.delay(1), Duration::from_millis(200));
assert_eq!(b.delay(2), Duration::from_millis(400));
assert_eq!(b.delay(3), Duration::from_millis(800));
}
#[test]
fn test_capped_at_max() {
let b = ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(1));
assert_eq!(b.delay(10), Duration::from_secs(1));
assert_eq!(b.delay(20), Duration::from_secs(1));
}
#[test]
fn test_overflow_protection() {
let b = ExponentialBackoff::new(Duration::from_secs(1), Duration::from_secs(3600));
let delay = b.delay(100);
assert!(delay <= Duration::from_secs(3600));
}
}