use std::{iter, time};
use rand::{Rng, rng};
#[derive(Debug, Clone)]
pub struct Backoff {
retries: u32,
min: time::Duration,
max: time::Duration,
jitter: f32,
factor: u32,
}
impl Backoff {
#[inline]
pub fn new(retries: u32) -> Self {
Self {
retries,
min: time::Duration::from_millis(100),
max: time::Duration::from_secs(10),
jitter: 0.3,
factor: 2,
}
}
#[inline]
pub fn timeout_range(mut self, min: time::Duration, max: time::Duration) -> Self {
self.min = min;
self.max = max;
self
}
#[inline]
pub fn jitter(mut self, jitter: f32) -> Self {
assert!(
jitter > 0f32 && jitter < 1f32,
"<exponential-backoff>: jitter must be greater than 0 and less than 1."
);
self.jitter = jitter;
self
}
#[inline]
pub fn factor(mut self, factor: u32) -> Self {
self.factor = factor;
self
}
#[inline]
pub fn iter(self) -> Iter {
Iter::new(self)
}
}
#[derive(Debug, Clone)]
pub struct Iter {
inner: Backoff,
retry_count: u32,
last_exponent: u32,
}
impl Iter {
#[inline]
pub fn new(inner: Backoff) -> Self {
Self {
inner,
retry_count: 0,
last_exponent: 0,
}
}
pub fn retry_count(&self) -> u32 {
self.retry_count
}
}
impl iter::Iterator for Iter {
type Item = time::Duration;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.inner.retries != 0 && self.retry_count >= self.inner.retries {
return None;
}
if self.inner.min * self.last_exponent < self.inner.max {
self.last_exponent = self.inner.factor.pow(self.retry_count);
}
let mut duration = self.inner.min * self.last_exponent;
self.retry_count += 1;
let jitter_factor = (self.inner.jitter * 100f32) as u32;
let random: u32 = rng().random_range(0..(jitter_factor * 2));
duration *= 100;
if random < jitter_factor {
let jitter = (duration * random) / 100;
duration -= jitter;
} else {
let jitter = (duration * (random / 2)) / 100;
duration += jitter;
};
duration /= 100;
duration = duration.min(self.inner.max);
duration = duration.max(self.inner.min);
Some(duration)
}
}
impl iter::FusedIterator for Iter {}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn test_backoff() {
let boff = Backoff::new(15)
.timeout_range(Duration::from_millis(10), Duration::from_secs(1))
.iter();
let mut total = Duration::default();
for dur in boff {
println!("{dur:?}");
total += dur;
}
println!("total: {total:?}");
}
}