use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ReconnectConfig {
pub initial_delay: Duration,
pub max_delay: Duration,
pub multiplier: f64,
pub jitter: f64,
pub max_attempts: Option<u32>,
}
impl Default for ReconnectConfig {
fn default() -> Self {
Self {
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_secs(30),
multiplier: 2.0,
jitter: 0.2,
max_attempts: None, }
}
}
impl ReconnectConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_initial_delay(mut self, delay: Duration) -> Self {
self.initial_delay = delay;
self
}
pub fn with_max_delay(mut self, delay: Duration) -> Self {
self.max_delay = delay;
self
}
pub fn with_multiplier(mut self, multiplier: f64) -> Self {
self.multiplier = multiplier;
self
}
pub fn with_jitter(mut self, jitter: f64) -> Self {
self.jitter = jitter.clamp(0.0, 1.0);
self
}
pub fn with_max_attempts(mut self, max: u32) -> Self {
self.max_attempts = Some(max);
self
}
pub fn disabled() -> Self {
Self {
max_attempts: Some(0),
..Default::default()
}
}
pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
if attempt == 0 {
return self.initial_delay;
}
let exponent = attempt.saturating_sub(1) as i32;
let delay_ms = self.initial_delay.as_millis() as f64 * self.multiplier.powi(exponent);
let delay = Duration::from_millis(delay_ms as u64);
std::cmp::min(delay, self.max_delay)
}
pub fn apply_jitter(&self, base: Duration) -> Duration {
if self.jitter == 0.0 {
return base;
}
let jitter_range = base.as_millis() as f64 * self.jitter;
let jitter = rand::random::<f64>() * 2.0 * jitter_range - jitter_range;
let adjusted_ms = (base.as_millis() as f64 + jitter).max(0.0) as u64;
Duration::from_millis(adjusted_ms)
}
pub fn delay_with_jitter(&self, attempt: u32) -> Duration {
let base = self.delay_for_attempt(attempt);
self.apply_jitter(base)
}
pub fn should_reconnect(&self, attempt: u32) -> bool {
match self.max_attempts {
Some(max) => attempt < max,
None => true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = ReconnectConfig::default();
assert_eq!(config.initial_delay, Duration::from_millis(100));
assert_eq!(config.max_delay, Duration::from_secs(30));
assert_eq!(config.multiplier, 2.0);
assert!(config.max_attempts.is_none());
}
#[test]
fn test_delay_calculation() {
let config = ReconnectConfig::new()
.with_initial_delay(Duration::from_millis(100))
.with_multiplier(2.0)
.with_max_delay(Duration::from_secs(10))
.with_jitter(0.0);
assert_eq!(config.delay_for_attempt(1), Duration::from_millis(100));
assert_eq!(config.delay_for_attempt(2), Duration::from_millis(200));
assert_eq!(config.delay_for_attempt(3), Duration::from_millis(400));
assert_eq!(config.delay_for_attempt(4), Duration::from_millis(800));
assert_eq!(config.delay_for_attempt(10), Duration::from_secs(10));
}
#[test]
fn test_should_reconnect() {
let unlimited = ReconnectConfig::default();
assert!(unlimited.should_reconnect(0));
assert!(unlimited.should_reconnect(100));
let limited = ReconnectConfig::default().with_max_attempts(3);
assert!(limited.should_reconnect(0));
assert!(limited.should_reconnect(2));
assert!(!limited.should_reconnect(3));
assert!(!limited.should_reconnect(10));
let disabled = ReconnectConfig::disabled();
assert!(!disabled.should_reconnect(0));
}
}