use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Backoff {
pub first: Duration,
pub longest: Duration,
pub attempts: u32,
}
impl Default for Backoff {
fn default() -> Self {
Self {
first: Duration::from_secs(1),
longest: Duration::from_secs(30),
attempts: 10,
}
}
}
impl Backoff {
#[must_use]
pub fn first_delay() -> Duration {
const SPREAD: Duration = Duration::from_secs(5);
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |since| since.subsec_nanos());
let ceiling = u64::try_from(SPREAD.as_nanos()).unwrap_or(u64::MAX);
Duration::from_nanos(u64::from(nanos) % ceiling.max(1))
}
#[must_use]
pub fn delay(self, attempt: u32) -> Duration {
let doubled = self
.first
.saturating_mul(1_u32.checked_shl(attempt.min(16)).unwrap_or(u32::MAX));
let capped = doubled.min(self.longest);
let spread = capped / 4;
if spread.is_zero() {
return capped;
}
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |since| since.subsec_nanos());
let ceiling = u64::try_from(spread.as_nanos()).unwrap_or(u64::MAX);
capped + Duration::from_nanos(u64::from(nanos) % ceiling.max(1))
}
}