use std::time::Duration;
pub(crate) const DEFAULT_INITIAL_BACKOFF: Duration = Duration::from_secs(1);
pub(crate) const DEFAULT_MAX_BACKOFF: Duration = Duration::from_secs(30);
pub(crate) struct ReconnectState {
initial_backoff: Duration,
max_backoff: Duration,
max_attempts: Option<u32>,
pub(crate) current_attempt: u32,
current_backoff: Duration,
}
impl ReconnectState {
pub fn new(
initial_backoff: Duration,
max_backoff: Duration,
max_attempts: Option<u32>,
) -> Self {
Self {
initial_backoff,
max_backoff,
max_attempts,
current_attempt: 0,
current_backoff: initial_backoff,
}
}
pub fn next_backoff(&mut self) -> Option<Duration> {
if let Some(max) = self.max_attempts {
if self.current_attempt >= max {
return None;
}
}
let backoff = self.current_backoff;
self.current_attempt += 1;
self.current_backoff = (self.current_backoff * 2).min(self.max_backoff);
Some(backoff)
}
pub fn reset(&mut self) {
self.current_attempt = 0;
self.current_backoff = self.initial_backoff;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn backoff_sequence_is_correct() {
let mut state = ReconnectState::new(
Duration::from_secs(1),
Duration::from_secs(30),
None, );
assert_eq!(state.next_backoff(), Some(Duration::from_secs(1)));
assert_eq!(state.next_backoff(), Some(Duration::from_secs(2)));
assert_eq!(state.next_backoff(), Some(Duration::from_secs(4)));
assert_eq!(state.next_backoff(), Some(Duration::from_secs(8)));
assert_eq!(state.next_backoff(), Some(Duration::from_secs(16)));
assert_eq!(state.next_backoff(), Some(Duration::from_secs(30))); assert_eq!(state.next_backoff(), Some(Duration::from_secs(30))); }
#[test]
fn max_attempts_returns_none() {
let mut state = ReconnectState::new(
Duration::from_secs(1),
Duration::from_secs(30),
Some(3),
);
assert!(state.next_backoff().is_some()); assert!(state.next_backoff().is_some()); assert!(state.next_backoff().is_some()); assert!(state.next_backoff().is_none()); }
#[test]
fn reset_restores_initial_state() {
let mut state = ReconnectState::new(
Duration::from_secs(1),
Duration::from_secs(30),
Some(5),
);
assert_eq!(state.next_backoff(), Some(Duration::from_secs(1)));
assert_eq!(state.next_backoff(), Some(Duration::from_secs(2)));
assert_eq!(state.current_attempt, 2);
state.reset();
assert_eq!(state.current_attempt, 0);
assert_eq!(state.next_backoff(), Some(Duration::from_secs(1)));
assert_eq!(state.next_backoff(), Some(Duration::from_secs(2)));
}
}