pub type RetryRandomFn = fn() -> u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackoffStrategy {
Immediate,
Fixed {
delay_ms: u32,
},
Exponential {
base_delay_ms: u32,
max_delay_ms: u32,
},
Linear {
initial_delay_ms: u32,
increment_ms: u32,
max_delay_ms: u32,
},
}
impl Default for BackoffStrategy {
fn default() -> Self {
Self::Immediate
}
}
impl BackoffStrategy {
pub fn delay_ms_for_retry(&self, retry_attempt: u8) -> u32 {
let attempt = retry_attempt.max(1);
match self {
BackoffStrategy::Immediate => 0,
BackoffStrategy::Fixed { delay_ms } => *delay_ms,
BackoffStrategy::Exponential {
base_delay_ms,
max_delay_ms,
} => {
let shift = (attempt.saturating_sub(1)).min(31);
let factor = 1u32 << shift;
base_delay_ms.saturating_mul(factor).min(*max_delay_ms)
}
BackoffStrategy::Linear {
initial_delay_ms,
increment_ms,
max_delay_ms,
} => {
let growth = increment_ms.saturating_mul((attempt.saturating_sub(1)) as u32);
initial_delay_ms.saturating_add(growth).min(*max_delay_ms)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum JitterStrategy {
#[default]
None,
Percentage {
percent: u8,
},
BoundedMs {
max_jitter_ms: u32,
},
}
impl JitterStrategy {
pub fn apply(self, base_delay_ms: u32, random_fn: Option<RetryRandomFn>) -> u32 {
let delta = match self {
JitterStrategy::None => return base_delay_ms,
JitterStrategy::Percentage { percent } => {
if percent == 0 || base_delay_ms == 0 {
return base_delay_ms;
}
base_delay_ms.saturating_mul((percent.min(100)) as u32) / 100
}
JitterStrategy::BoundedMs { max_jitter_ms } => {
if max_jitter_ms == 0 {
return base_delay_ms;
}
max_jitter_ms
}
};
let random = match random_fn {
Some(cb) => cb(),
None => return base_delay_ms,
};
let span = delta.saturating_mul(2).saturating_add(1);
if span == 0 {
return base_delay_ms;
}
let offset = (random % span) as i64 - delta as i64;
let jittered = base_delay_ms as i64 + offset;
jittered.max(0) as u32
}
}