use super::traits::RetryStrategy;
use crate::error::SyncError;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ExponentialBackoffRetry {
max_attempts: u32,
initial_delay: Duration,
max_delay: Duration,
backoff_factor: f64,
retryable_errors: Vec<u32>,
}
impl ExponentialBackoffRetry {
pub fn new() -> Self {
Self {
max_attempts: 5,
initial_delay: Duration::from_secs(1),
max_delay: Duration::from_secs(60),
backoff_factor: 2.0,
retryable_errors: vec![
5000, 6000, 11000, 12000, 16000, ],
}
}
pub fn builder() -> ExponentialBackoffRetryBuilder {
ExponentialBackoffRetryBuilder::new()
}
}
impl RetryStrategy for ExponentialBackoffRetry {
fn should_retry(&self, attempt: u32, error: &SyncError) -> bool {
if attempt >= self.max_attempts {
return false;
}
if error.is_retryable() {
return true;
}
self.retryable_errors.contains(&error.error_code())
}
fn delay_before_retry(&self, attempt: u32) -> Duration {
let delay = self.initial_delay.as_secs_f64() * self.backoff_factor.powi(attempt as i32);
Duration::from_secs_f64(delay.min(self.max_delay.as_secs_f64()))
}
fn max_attempts(&self) -> u32 {
self.max_attempts
}
}
pub struct ExponentialBackoffRetryBuilder {
max_attempts: Option<u32>,
initial_delay: Option<Duration>,
max_delay: Option<Duration>,
backoff_factor: Option<f64>,
retryable_errors: Vec<u32>,
}
impl ExponentialBackoffRetryBuilder {
pub fn new() -> Self {
Self {
max_attempts: None,
initial_delay: None,
max_delay: None,
backoff_factor: None,
retryable_errors: Vec::new(),
}
}
pub fn max_attempts(mut self, attempts: u32) -> Self {
self.max_attempts = Some(attempts);
self
}
pub fn initial_delay(mut self, delay: Duration) -> Self {
self.initial_delay = Some(delay);
self
}
pub fn max_delay(mut self, delay: Duration) -> Self {
self.max_delay = Some(delay);
self
}
pub fn backoff_factor(mut self, factor: f64) -> Self {
self.backoff_factor = Some(factor);
self
}
pub fn retryable_error(mut self, error_code: u32) -> Self {
self.retryable_errors.push(error_code);
self
}
pub fn build(self) -> ExponentialBackoffRetry {
ExponentialBackoffRetry {
max_attempts: self.max_attempts.unwrap_or(5),
initial_delay: self.initial_delay.unwrap_or(Duration::from_secs(1)),
max_delay: self.max_delay.unwrap_or(Duration::from_secs(60)),
backoff_factor: self.backoff_factor.unwrap_or(2.0),
retryable_errors: self.retryable_errors,
}
}
}
pub struct JitterRetry {
base: ExponentialBackoffRetry,
jitter_ratio: f64,
}
impl JitterRetry {
pub fn new() -> Self {
Self {
base: ExponentialBackoffRetry::new(),
jitter_ratio: 0.1,
}
}
fn add_jitter(&self, delay: Duration) -> Duration {
use rand::Rng;
let mut rng = rand::thread_rng();
let jitter = rng.gen_range(-self.jitter_ratio..self.jitter_ratio);
let secs = delay.as_secs_f64() * (1.0 + jitter);
Duration::from_secs_f64(secs)
}
}
impl RetryStrategy for JitterRetry {
fn should_retry(&self, attempt: u32, error: &SyncError) -> bool {
self.base.should_retry(attempt, error)
}
fn delay_before_retry(&self, attempt: u32) -> Duration {
let delay = self.base.delay_before_retry(attempt);
self.add_jitter(delay)
}
fn max_attempts(&self) -> u32 {
self.base.max_attempts()
}
}