use std::time::{Duration, SystemTime};
use http::HeaderMap;
use http::header::RETRY_AFTER;
#[derive(Debug, Clone)]
pub struct RetryPolicy {
max_retries: usize,
base_delay: Duration,
max_delay: Duration,
jitter: bool,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_retries: 3,
base_delay: Duration::from_millis(200),
max_delay: Duration::from_secs(5),
jitter: true,
}
}
}
impl RetryPolicy {
pub fn max_retries(&self) -> usize {
self.max_retries
}
pub fn base_delay(&self) -> Duration {
self.base_delay
}
pub fn max_delay(&self) -> Duration {
self.max_delay
}
pub fn jitter(&self) -> bool {
self.jitter
}
pub fn with_max_retries(mut self, max_retries: usize) -> Self {
self.max_retries = max_retries;
self
}
pub fn with_base_delay(mut self, base_delay: Duration) -> Self {
self.base_delay = base_delay;
self
}
pub fn with_max_delay(mut self, max_delay: Duration) -> Self {
self.max_delay = max_delay;
self
}
pub fn with_jitter(mut self, jitter: bool) -> Self {
self.jitter = jitter;
self
}
}
pub(crate) fn parse_retry_after(headers: &HeaderMap) -> Option<Duration> {
let value = headers.get(RETRY_AFTER)?;
let s = value.to_str().ok()?.trim();
if s.is_empty() {
return None;
}
if let Ok(seconds) = s.parse::<u64>() {
return Some(Duration::from_secs(seconds));
}
if let Ok(when) = httpdate::parse_http_date(s) {
let now = SystemTime::now();
if let Ok(delta) = when.duration_since(now) {
return Some(delta);
}
return Some(Duration::from_secs(0));
}
None
}
pub(crate) fn backoff_delay(policy: &RetryPolicy, attempt: usize) -> Duration {
let exp = 1u64 << (attempt.min(31) as u32);
let base_ms = policy.base_delay.as_millis().min(u128::from(u64::MAX)) as u64;
let mut delay_ms = base_ms.saturating_mul(exp);
let max_ms = policy.max_delay.as_millis().min(u128::from(u64::MAX)) as u64;
if delay_ms > max_ms {
delay_ms = max_ms;
}
let mut delay = Duration::from_millis(delay_ms);
if policy.jitter && delay_ms > 0 {
let jitter_ms = fastrand::u64(0..delay_ms);
delay = Duration::from_millis(jitter_ms);
}
delay
}