use core::time::Duration;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct SseRetryConfig {
pub max_retries: u32,
pub max_backoff_ms: u32,
pub min_sleep_ms: u32,
pub backoff_multiplier: f32,
pub jitter: bool,
}
impl SseRetryConfig {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
max_retries: 20,
max_backoff_ms: 60_000,
min_sleep_ms: 200,
backoff_multiplier: 2.0,
jitter: true,
}
}
#[inline]
#[must_use]
pub const fn disabled() -> Self {
Self {
max_retries: 0,
..Self::new()
}
}
#[must_use]
pub fn calculate_backoff_with_factor(
&self,
reconnect_time_ms: u32,
attempt: u32,
jitter_factor: f32,
) -> Option<Duration> {
if self.max_retries <= attempt {
return None;
}
assert!(self.min_sleep_ms <= self.max_backoff_ms);
let reconnect_time_ms = reconnect_time_ms.max(self.min_sleep_ms) as f32;
let mut sleep_ms =
match self.backoff_multiplier.is_finite() && 1.0 <= self.backoff_multiplier {
true => reconnect_time_ms * self.backoff_multiplier.powi(attempt as _),
false => reconnect_time_ms,
};
if !sleep_ms.is_finite() || (self.max_backoff_ms as f32) <= sleep_ms {
sleep_ms = self.max_backoff_ms as _;
}
if self.jitter && reconnect_time_ms < sleep_ms {
let jitter_factor =
match jitter_factor.is_finite() && (0.0..=1.0).contains(&jitter_factor) {
true => jitter_factor,
false => 1.0,
};
sleep_ms = reconnect_time_ms + jitter_factor * (sleep_ms - reconnect_time_ms)
}
Some(Duration::from_millis(sleep_ms as _))
}
#[must_use]
#[cfg(feature = "fastrand")]
pub fn calculate_backoff(&self, reconnect_time_ms: u32, attempt: u32) -> Option<Duration> {
self.calculate_backoff_with_factor(reconnect_time_ms, attempt, fastrand::f32())
}
}
impl Default for SseRetryConfig {
fn default() -> Self {
Self::new()
}
}