use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Backoff {
pub initial_ms: u64,
pub max_ms: u64,
next_ms: u64,
#[serde(default)]
terminal_closes: u32,
}
const TERMINAL_CAP: u64 = 6;
impl Default for Backoff {
fn default() -> Self {
Self::new(1_000, 30_000)
}
}
impl Backoff {
pub fn new(initial_ms: u64, max_ms: u64) -> Self {
let initial_ms = initial_ms.max(1);
let max_ms = max_ms.max(initial_ms);
Self { initial_ms, max_ms, next_ms: initial_ms, terminal_closes: 0 }
}
fn bounds(&self) -> (u64, u64) {
let initial = self.initial_ms.max(1);
(initial, self.max_ms.max(initial))
}
pub fn next_delay(&mut self) -> u64 {
let (initial, max) = self.bounds();
let delay = self.next_ms.clamp(initial, max);
self.next_ms = delay.saturating_mul(2).min(max);
delay
}
pub fn next_delay_jittered(&mut self, random: f64) -> u64 {
let delay = self.next_delay();
let half = delay / 2;
(half + spread(delay - half, random)).clamp(1, delay)
}
pub fn next_delay_for(&mut self, retry_after_ms: u64, random: f64) -> u64 {
if retry_after_ms == 0 {
return self.next_delay_jittered(random);
}
let doublings = self.terminal_closes.min(3);
self.terminal_closes = self.terminal_closes.saturating_add(1);
let wait = retry_after_ms.saturating_mul(1 << doublings).min(retry_after_ms.saturating_mul(TERMINAL_CAP));
wait.saturating_add(spread(wait / 4, random))
}
pub fn reset(&mut self) {
self.next_ms = self.bounds().0;
self.terminal_closes = 0;
}
}
fn spread(span: u64, random: f64) -> u64 {
let r = if random.is_nan() { 0.0 } else { random.clamp(0.0, 1.0) };
((span as f64 * r) as u64).min(span)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn doubles_to_the_cap_and_resets() {
let mut b = Backoff::default();
let delays: Vec<u64> = (0..7).map(|_| b.next_delay()).collect();
assert_eq!(delays, [1_000, 2_000, 4_000, 8_000, 16_000, 30_000, 30_000]);
b.reset();
assert_eq!(b.next_delay(), 1_000);
}
#[test]
fn bounds_are_sane() {
let mut b = Backoff::new(0, 0);
assert_eq!((b.initial_ms, b.max_ms), (1, 1));
assert_eq!(b.next_delay(), 1, "never a busy loop");
let mut b = Backoff::new(5_000, 1_000);
assert_eq!((b.initial_ms, b.max_ms), (5_000, 5_000));
assert_eq!(b.next_delay(), 5_000);
let mut b = Backoff::new(u64::MAX, u64::MAX);
assert_eq!((b.next_delay(), b.next_delay()), (u64::MAX, u64::MAX));
let mut b = Backoff { initial_ms: 0, max_ms: 0, ..Default::default() };
b.reset();
assert_eq!(b.next_delay(), 1);
}
#[test]
fn terminal_closes_wait_long_and_longer_until_reset() {
let mut b = Backoff::default();
let waits: Vec<u64> = (0..6).map(|_| b.next_delay_for(300_000, 0.0)).collect();
assert_eq!(waits, [300_000, 600_000, 1_200_000, 1_800_000, 1_800_000, 1_800_000]);
assert_eq!(b.next_delay_for(0, 0.0), 500);
assert_eq!(b.next_delay_for(0, 0.0), 1_000);
b.reset();
assert_eq!(b.next_delay_for(300_000, 0.0), 300_000);
assert_eq!(b.next_delay(), 1_000);
assert_eq!(b.next_delay_for(0, 0.0), 1_000);
assert_eq!(b.next_delay_for(300_000, 0.0), 600_000);
}
#[test]
fn terminal_waits_are_spread_upwards() {
let mut b = Backoff::default();
assert_eq!(b.next_delay_for(300_000, 0.5), 337_500);
assert_eq!(b.next_delay_for(300_000, 0.999_999), 749_999);
assert_eq!(b.next_delay_for(300_000, f64::NAN), 1_200_000);
assert_eq!(b.next_delay_for(300_000, 7.0), 2_250_000);
assert_eq!(b.next_delay_for(300_000, -1.0), 1_800_000);
let mut b = Backoff::default();
assert_eq!(b.next_delay_for(1, 0.9), 1);
assert_eq!(b.next_delay_for(1, 0.9), 2);
let mut b = Backoff::default();
assert_eq!(b.next_delay_for(u64::MAX, 1.0), u64::MAX);
assert_eq!(b.next_delay_for(u64::MAX, 1.0), u64::MAX);
}
#[test]
fn the_terminal_count_survives_serde_and_defaults_to_none() {
let mut b = Backoff::default();
b.next_delay_for(300_000, 0.0);
let mut back: Backoff = serde_json::from_value(serde_json::to_value(&b).unwrap()).unwrap();
assert_eq!(back.next_delay_for(300_000, 0.0), 600_000);
let mut old: Backoff =
serde_json::from_value(serde_json::json!({"initial_ms": 1000, "max_ms": 30000, "next_ms": 1000})).unwrap();
assert_eq!(old.next_delay_for(300_000, 0.0), 300_000);
}
#[test]
fn jitter_spreads_within_the_delay() {
let mut b = Backoff::default();
assert_eq!(b.next_delay_jittered(0.0), 500);
assert_eq!(b.next_delay_jittered(0.5), 1_500);
assert_eq!(b.next_delay_jittered(0.999_999), 3_999);
assert_eq!(b.next_delay_jittered(f64::NAN), 4_000);
assert_eq!(b.next_delay_jittered(7.0), 16_000);
assert_eq!(b.next_delay_jittered(-1.0), 15_000);
let mut b = Backoff::new(1, 1);
assert_eq!(b.next_delay_jittered(0.0), 1);
let mut b = Backoff::new(u64::MAX, u64::MAX);
assert!(b.next_delay_jittered(1.0) >= u64::MAX / 2);
}
}