use core::time::Duration;
use crate::clock::Clock;
use crate::jitter::{Jitter, NoJitter};
use crate::util::saturating_mul_f64;
pub trait BackoffPolicy {
fn next_delay(&mut self) -> Option<Duration>;
fn reset(&mut self);
}
impl<P: BackoffPolicy + ?Sized> BackoffPolicy for &mut P {
#[inline]
fn next_delay(&mut self) -> Option<Duration> {
(**self).next_delay()
}
#[inline]
fn reset(&mut self) {
(**self).reset();
}
}
#[derive(Debug, Clone, Copy)]
pub struct Constant {
delay: Duration,
}
impl Constant {
#[inline]
#[must_use]
pub const fn new(delay: Duration) -> Self {
Self { delay }
}
}
impl BackoffPolicy for Constant {
#[inline]
fn next_delay(&mut self) -> Option<Duration> {
Some(self.delay)
}
#[inline]
fn reset(&mut self) {}
}
#[derive(Debug, Clone, Copy)]
pub struct ExponentialBackoff<J = NoJitter> {
initial: Duration,
current: Duration,
multiplier: f64,
randomization_factor: f64,
jitter: J,
}
impl ExponentialBackoff<NoJitter> {
#[inline]
#[must_use]
pub const fn new(initial: Duration, multiplier: f64) -> Self {
Self {
initial,
current: initial,
multiplier,
randomization_factor: 0.0,
jitter: NoJitter,
}
}
}
impl<J: Jitter> ExponentialBackoff<J> {
#[inline]
#[must_use]
pub fn with_jitter<J2: Jitter>(
self,
jitter: J2,
randomization_factor: f64,
) -> ExponentialBackoff<J2> {
ExponentialBackoff {
initial: self.initial,
current: self.current,
multiplier: self.multiplier,
randomization_factor: randomization_factor.clamp(0.0, 1.0),
jitter,
}
}
}
impl<J: Jitter> BackoffPolicy for ExponentialBackoff<J> {
fn next_delay(&mut self) -> Option<Duration> {
let delay = self.jitter.apply(self.current, self.randomization_factor);
if self.multiplier.is_finite() && self.multiplier > 0.0 {
self.current = saturating_mul_f64(self.current, self.multiplier);
}
Some(delay)
}
#[inline]
fn reset(&mut self) {
self.current = self.initial;
}
}
impl Default for ExponentialBackoff<NoJitter> {
#[inline]
fn default() -> Self {
Self::new(Duration::from_millis(500), 1.5)
}
}
#[derive(Debug, Clone, Copy)]
pub struct MaxAttempts<P> {
inner: P,
max: u32,
count: u32,
}
impl<P> MaxAttempts<P> {
#[inline]
#[must_use]
pub const fn new(inner: P, max: u32) -> Self {
Self {
inner,
max,
count: 0,
}
}
#[inline]
pub fn inner(&self) -> &P {
&self.inner
}
#[inline]
pub fn into_inner(self) -> P {
self.inner
}
}
impl<P: BackoffPolicy> BackoffPolicy for MaxAttempts<P> {
#[inline]
fn next_delay(&mut self) -> Option<Duration> {
if self.count >= self.max {
return None;
}
let delay = self.inner.next_delay()?;
self.count += 1;
Some(delay)
}
#[inline]
fn reset(&mut self) {
self.count = 0;
self.inner.reset();
}
}
#[derive(Debug, Clone, Copy)]
pub struct WithMaxDelay<P> {
inner: P,
max_delay: Duration,
}
impl<P> WithMaxDelay<P> {
#[inline]
#[must_use]
pub const fn new(inner: P, max_delay: Duration) -> Self {
Self { inner, max_delay }
}
#[inline]
pub fn inner(&self) -> &P {
&self.inner
}
#[inline]
pub fn into_inner(self) -> P {
self.inner
}
}
impl<P: BackoffPolicy> BackoffPolicy for WithMaxDelay<P> {
#[inline]
fn next_delay(&mut self) -> Option<Duration> {
self.inner.next_delay().map(|d| d.min(self.max_delay))
}
#[inline]
fn reset(&mut self) {
self.inner.reset();
}
}
#[derive(Debug, Clone, Copy)]
pub struct MaxElapsedTime<P, C: Clock> {
inner: P,
clock: C,
max_elapsed: Duration,
started_at: Option<C::Instant>,
}
impl<P, C: Clock> MaxElapsedTime<P, C> {
#[inline]
pub const fn new(inner: P, clock: C, max_elapsed: Duration) -> Self {
Self {
inner,
clock,
max_elapsed,
started_at: None,
}
}
#[inline]
pub fn inner(&self) -> &P {
&self.inner
}
#[inline]
pub fn into_inner(self) -> P {
self.inner
}
}
impl<P: BackoffPolicy, C: Clock> BackoffPolicy for MaxElapsedTime<P, C> {
fn next_delay(&mut self) -> Option<Duration> {
let now = self.clock.now();
let started_at = *self.started_at.get_or_insert(now);
if self.clock.duration_since(started_at, now) >= self.max_elapsed {
return None;
}
self.inner.next_delay()
}
#[inline]
fn reset(&mut self) {
self.started_at = None;
self.inner.reset();
}
}
pub trait PolicyExt: BackoffPolicy + Sized {
#[inline]
#[must_use]
fn max_attempts(self, max: u32) -> MaxAttempts<Self> {
MaxAttempts::new(self, max)
}
#[inline]
#[must_use]
fn with_max_delay(self, max_delay: Duration) -> WithMaxDelay<Self> {
WithMaxDelay::new(self, max_delay)
}
#[inline]
#[must_use]
fn max_elapsed_time<C: Clock>(
self,
clock: C,
max_elapsed: Duration,
) -> MaxElapsedTime<Self, C> {
MaxElapsedTime::new(self, clock, max_elapsed)
}
}
impl<P: BackoffPolicy> PolicyExt for P {}
#[cfg(test)]
mod tests {
use super::*;
use core::cell::Cell;
#[test]
fn exponential_grows_and_resets() {
let mut p = ExponentialBackoff::new(Duration::from_millis(100), 2.0);
assert_eq!(p.next_delay(), Some(Duration::from_millis(100)));
assert_eq!(p.next_delay(), Some(Duration::from_millis(200)));
assert_eq!(p.next_delay(), Some(Duration::from_millis(400)));
p.reset();
assert_eq!(p.next_delay(), Some(Duration::from_millis(100)));
}
#[test]
fn exponential_never_panics_on_overflow() {
let mut p = ExponentialBackoff::new(Duration::from_secs(u64::MAX / 2), 4.0);
for _ in 0..1000 {
let _ = p.next_delay();
}
assert_eq!(p.next_delay(), Some(Duration::MAX));
}
#[test]
fn degenerate_multiplier_holds_interval_steady() {
for m in [0.0, -1.0, f64::NAN, f64::INFINITY] {
let mut p = ExponentialBackoff::new(Duration::from_millis(100), m);
for _ in 0..5 {
assert_eq!(
p.next_delay(),
Some(Duration::from_millis(100)),
"multiplier {m} should hold the interval steady",
);
}
}
}
#[test]
fn max_attempts_stops() {
let mut p = Constant::new(Duration::from_millis(10)).max_attempts(3);
assert!(p.next_delay().is_some());
assert!(p.next_delay().is_some());
assert!(p.next_delay().is_some());
assert_eq!(p.next_delay(), None);
p.reset();
assert!(p.next_delay().is_some());
}
#[test]
fn with_max_delay_clamps() {
let mut p = ExponentialBackoff::new(Duration::from_millis(100), 10.0)
.with_max_delay(Duration::from_millis(500));
assert_eq!(p.next_delay(), Some(Duration::from_millis(100)));
assert_eq!(p.next_delay(), Some(Duration::from_millis(500))); assert_eq!(p.next_delay(), Some(Duration::from_millis(500)));
}
struct VirtualClock {
now_ms: Cell<u64>,
}
impl Clock for VirtualClock {
type Instant = u64;
fn now(&self) -> u64 {
self.now_ms.get()
}
fn duration_since(&self, earlier: u64, now: u64) -> Duration {
Duration::from_millis(now.saturating_sub(earlier))
}
}
#[test]
fn max_elapsed_time_stops() {
let clock = VirtualClock {
now_ms: Cell::new(0),
};
let mut p = Constant::new(Duration::from_millis(100))
.max_elapsed_time(&clock, Duration::from_millis(250));
assert!(p.next_delay().is_some()); clock.now_ms.set(100);
assert!(p.next_delay().is_some()); clock.now_ms.set(250);
assert_eq!(p.next_delay(), None); }
}