use crate::retry::MonotonicDuration;
use super::{PollContext, ProgressChange};
pub const MAX_BACKOFF_MULTIPLIER: u8 = 16;
pub trait PollBackoff {
type Error;
fn delay(&mut self, context: PollContext) -> Result<MonotonicDuration, Self::Error>;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExponentialBackoffError {
ZeroDelay,
InitialExceedsMaximum,
InvalidMultiplier,
}
impl_static_error!(ExponentialBackoffError,
Self::ZeroDelay => "poll backoff delays must be nonzero",
Self::InitialExceedsMaximum => "initial poll backoff exceeds its maximum",
Self::InvalidMultiplier => "poll backoff multiplier is invalid",
);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExponentialBackoff {
initial: MonotonicDuration,
maximum: MonotonicDuration,
multiplier: u8,
next: MonotonicDuration,
}
impl ExponentialBackoff {
pub const fn new(
initial: MonotonicDuration,
maximum: MonotonicDuration,
multiplier: u8,
) -> Result<Self, ExponentialBackoffError> {
if initial.get() == 0 || maximum.get() == 0 {
return Err(ExponentialBackoffError::ZeroDelay);
}
if initial.get() > maximum.get() {
return Err(ExponentialBackoffError::InitialExceedsMaximum);
}
if multiplier == 0 || multiplier > MAX_BACKOFF_MULTIPLIER {
return Err(ExponentialBackoffError::InvalidMultiplier);
}
Ok(Self {
initial,
maximum,
multiplier,
next: initial,
})
}
#[must_use]
pub const fn next_delay(self) -> MonotonicDuration {
self.next
}
}
impl PollBackoff for ExponentialBackoff {
type Error = core::convert::Infallible;
fn delay(&mut self, context: PollContext) -> Result<MonotonicDuration, Self::Error> {
if matches!(
context.progress_change(),
ProgressChange::Initial | ProgressChange::Advanced | ProgressChange::Reset
) {
self.next = self.initial;
}
let selected = self.next;
let multiplied = selected
.get()
.saturating_mul(u64::from(self.multiplier))
.min(self.maximum.get());
self.next = MonotonicDuration::new(multiplied);
Ok(selected)
}
}