rusty_common/websocket/
reconnect.rs

1//! WebSocket reconnection strategies
2//!
3//! Provides flexible reconnection strategies with exponential backoff.
4
5use parking_lot::Mutex;
6use rand::Rng;
7use std::time::Duration;
8
9/// Reconnection strategy
10pub struct ReconnectStrategy {
11    /// Configuration
12    config: super::ReconnectConfig,
13
14    /// Current state
15    state: Mutex<ReconnectState>,
16}
17
18/// Internal reconnection state
19struct ReconnectState {
20    /// Current attempt number
21    attempts: u32,
22
23    /// Current delay
24    current_delay: Duration,
25}
26
27impl ReconnectStrategy {
28    /// Create a new reconnection strategy
29    #[must_use]
30    pub const fn new(config: super::ReconnectConfig) -> Self {
31        let initial_delay = config.initial_delay;
32        Self {
33            config,
34            state: Mutex::new(ReconnectState {
35                attempts: 0,
36                current_delay: initial_delay,
37            }),
38        }
39    }
40
41    /// Check if reconnection should be attempted
42    pub fn should_reconnect(&self) -> bool {
43        if !self.config.enabled {
44            return false;
45        }
46
47        let state = self.state.lock();
48        self.config.max_attempts == 0 || state.attempts < self.config.max_attempts
49    }
50
51    /// Get the next reconnection delay
52    pub fn next_delay(&self) -> Option<Duration> {
53        if !self.should_reconnect() {
54            return None;
55        }
56
57        let mut state = self.state.lock();
58        state.attempts += 1;
59
60        // Calculate next delay with exponential backoff
61        let mut delay = state.current_delay;
62
63        // Add jitter if enabled
64        if self.config.jitter {
65            let mut rng = rand::rng();
66            let jitter_range = delay.as_millis() as f64 * 0.1; // 10% jitter
67            let jitter = rng.random_range(-jitter_range..=jitter_range) as i64;
68            let millis = (delay.as_millis() as i64 + jitter).max(0) as u64;
69            delay = Duration::from_millis(millis);
70        }
71
72        // Update current delay for next time
73        let next_delay_ms =
74            (state.current_delay.as_millis() as f64 * self.config.multiplier) as u64;
75        state.current_delay =
76            Duration::from_millis(next_delay_ms.min(self.config.max_delay.as_millis() as u64));
77
78        Some(delay)
79    }
80
81    /// Reset the reconnection state
82    pub fn reset(&self) {
83        let mut state = self.state.lock();
84        state.attempts = 0;
85        state.current_delay = self.config.initial_delay;
86    }
87
88    /// Get the current attempt count
89    pub fn attempts(&self) -> u32 {
90        self.state.lock().attempts
91    }
92
93    /// Mark a reconnection failure
94    pub const fn mark_failure(&self) {
95        // Attempts are already incremented in next_delay()
96        // This method is for any additional failure handling
97    }
98
99    /// Check if max attempts have been reached
100    pub fn exhausted(&self) -> bool {
101        if !self.config.enabled || self.config.max_attempts == 0 {
102            return false;
103        }
104
105        let state = self.state.lock();
106        state.attempts >= self.config.max_attempts
107    }
108}