use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RetryConfig {
pub retries: u32,
pub min_delay: Duration,
pub max_delay: Duration,
pub retry_on_statuses: Vec<u16>,
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
retries: 2,
min_delay: Duration::from_millis(300),
max_delay: Duration::from_secs(5),
retry_on_statuses: vec![429],
}
}
}
impl RetryConfig {
pub fn none() -> Self {
Self {
retries: 0,
..Default::default()
}
}
pub const fn max_attempts(&self) -> u32 {
self.retries + 1
}
pub fn retries_status(&self, status: u16) -> bool {
self.retry_on_statuses.contains(&status)
}
pub fn backoff_delay(&self, attempt: u32) -> Duration {
let exponential = self.min_delay.as_secs_f64() * 2f64.powi(attempt as i32);
let delay = Duration::from_secs_f64(exponential * (0.5 + jitter() * 0.5));
delay.min(self.max_delay)
}
}
pub(crate) async fn sleep(delay: Duration) {
if !delay.is_zero() {
tokio::time::sleep(delay).await;
}
}
fn jitter() -> f64 {
static STATE: AtomicU64 = AtomicU64::new(0);
let mut state = STATE.load(Ordering::Relaxed);
if state == 0 {
state = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|elapsed| elapsed.as_nanos() as u64)
.unwrap_or(0x9E37_79B9_7F4A_7C15)
| 1;
}
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
STATE.store(state, Ordering::Relaxed);
let value = state.wrapping_mul(0x2545_F491_4F6C_DD1D);
(value >> 11) as f64 / (1u64 << 53) as f64
}