use std::time::Duration;
use super::object_store::CloudError;
#[derive(Debug, Clone, PartialEq)]
pub struct RetryPolicy {
pub max_attempts: u32,
pub initial_delay_ms: u64,
pub max_delay_ms: u64,
pub backoff_multiplier: f64,
pub jitter_factor: f64,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self::new()
}
}
impl RetryPolicy {
pub fn new() -> Self {
RetryPolicy {
max_attempts: 3,
initial_delay_ms: 100,
max_delay_ms: 30_000,
backoff_multiplier: 2.0,
jitter_factor: 0.1,
}
}
pub fn no_retry() -> Self {
RetryPolicy {
max_attempts: 1,
initial_delay_ms: 0,
max_delay_ms: 0,
backoff_multiplier: 1.0,
jitter_factor: 0.0,
}
}
pub fn aggressive() -> Self {
RetryPolicy {
max_attempts: 5,
initial_delay_ms: 50,
max_delay_ms: 60_000,
backoff_multiplier: 2.0,
jitter_factor: 0.1,
}
}
pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
if self.max_delay_ms == 0 {
return Duration::ZERO;
}
let base = self.initial_delay_ms as f64 * self.backoff_multiplier.powi(attempt as i32);
let capped = base.min(self.max_delay_ms as f64);
let pseudo_rand = lcg_rand(attempt);
let jitter_range = capped * self.jitter_factor;
let jitter = jitter_range * (2.0 * pseudo_rand - 1.0);
let delay_ms = (capped + jitter).max(0.0) as u64;
Duration::from_millis(delay_ms)
}
pub fn is_retryable(error: &CloudError) -> bool {
matches!(
error,
CloudError::RangeOutOfBounds { .. }
)
}
}
fn lcg_rand(attempt: u32) -> f64 {
let state = attempt as u64;
let next = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
let upper = (next >> 32) as f64;
upper / (u32::MAX as f64 + 1.0)
}
pub struct RetryState {
policy: RetryPolicy,
attempt: u32,
}
impl RetryState {
pub fn new(policy: RetryPolicy) -> Self {
RetryState { policy, attempt: 0 }
}
pub fn attempt(&self) -> u32 {
self.attempt
}
pub fn should_retry(&self, error: &CloudError) -> bool {
self.attempt + 1 < self.policy.max_attempts && RetryPolicy::is_retryable(error)
}
pub fn next_delay(&mut self) -> Option<Duration> {
if self.attempt >= self.policy.max_attempts {
return None;
}
let delay = self.policy.delay_for_attempt(self.attempt);
self.attempt += 1;
if self.attempt >= self.policy.max_attempts {
Some(delay)
} else {
Some(delay)
}
}
}