use crate::options::SocketOptions;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ReconnectState {
base_interval: Duration,
max_interval: Duration,
attempt: u32,
current_interval: Duration,
}
impl ReconnectState {
pub const fn new(options: &SocketOptions) -> Self {
Self {
base_interval: options.reconnect_ivl,
max_interval: options.reconnect_ivl_max,
attempt: 0,
current_interval: options.reconnect_ivl,
}
}
pub fn next_delay(&mut self) -> Duration {
let delay = self.current_interval;
self.attempt += 1;
self.current_interval = self.base_interval * (1_u32 << self.attempt.min(10));
if self.current_interval > self.max_interval {
self.current_interval = self.max_interval;
}
delay
}
pub fn reset(&mut self) {
self.attempt = 0;
self.current_interval = self.base_interval;
}
#[inline]
#[must_use]
pub const fn attempt(&self) -> u32 {
self.attempt
}
#[inline]
#[must_use]
pub const fn base_interval(&self) -> Duration {
self.base_interval
}
#[inline]
#[must_use]
pub const fn max_interval(&self) -> Duration {
self.max_interval
}
#[inline]
#[must_use]
pub const fn current_interval(&self) -> Duration {
self.current_interval
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReconnectError {
MaxAttemptsReached { attempts: u32 },
ConnectionFailed { message: String },
Cancelled,
}
impl std::fmt::Display for ReconnectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MaxAttemptsReached { attempts } => {
write!(f, "Maximum reconnection attempts reached: {attempts}")
}
Self::ConnectionFailed { message } => {
write!(f, "Connection failed: {message}")
}
Self::Cancelled => {
write!(f, "Reconnection cancelled")
}
}
}
}
impl std::error::Error for ReconnectError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exponential_backoff() {
let options = SocketOptions::default()
.with_reconnect_ivl(Duration::from_millis(100))
.with_reconnect_ivl_max(Duration::from_secs(10));
let mut state = ReconnectState::new(&options);
assert_eq!(state.next_delay(), Duration::from_millis(100));
assert_eq!(state.attempt(), 1);
assert_eq!(state.next_delay(), Duration::from_millis(200));
assert_eq!(state.attempt(), 2);
assert_eq!(state.next_delay(), Duration::from_millis(400));
assert_eq!(state.attempt(), 3);
assert_eq!(state.next_delay(), Duration::from_millis(800));
assert_eq!(state.attempt(), 4);
}
#[test]
fn test_max_interval_cap() {
let options = SocketOptions::default()
.with_reconnect_ivl(Duration::from_millis(100))
.with_reconnect_ivl_max(Duration::from_millis(500));
let mut state = ReconnectState::new(&options);
assert_eq!(state.next_delay(), Duration::from_millis(100));
assert_eq!(state.next_delay(), Duration::from_millis(200));
assert_eq!(state.next_delay(), Duration::from_millis(400));
assert_eq!(state.next_delay(), Duration::from_millis(500));
assert_eq!(state.next_delay(), Duration::from_millis(500));
}
#[test]
fn test_reset() {
let options = SocketOptions::default()
.with_reconnect_ivl(Duration::from_millis(100))
.with_reconnect_ivl_max(Duration::from_secs(10));
let mut state = ReconnectState::new(&options);
state.next_delay();
state.next_delay();
state.next_delay();
assert_eq!(state.attempt(), 3);
state.reset();
assert_eq!(state.attempt(), 0);
assert_eq!(state.next_delay(), Duration::from_millis(100));
}
#[test]
fn test_state_accessors() {
let options = SocketOptions::default()
.with_reconnect_ivl(Duration::from_millis(250))
.with_reconnect_ivl_max(Duration::from_secs(5));
let state = ReconnectState::new(&options);
assert_eq!(state.base_interval(), Duration::from_millis(250));
assert_eq!(state.max_interval(), Duration::from_secs(5));
assert_eq!(state.current_interval(), Duration::from_millis(250));
assert_eq!(state.attempt(), 0);
}
}