monocoque_core/
reconnect.rs1use crate::options::SocketOptions;
7use std::time::Duration;
8
9#[derive(Debug, Clone)]
39pub struct ReconnectState {
40 base_interval: Duration,
42 max_interval: Duration,
44 attempt: u32,
46 current_interval: Duration,
48}
49
50impl ReconnectState {
51 pub const fn new(options: &SocketOptions) -> Self {
53 Self {
54 base_interval: options.reconnect_ivl,
55 max_interval: options.reconnect_ivl_max,
56 attempt: 0,
57 current_interval: options.reconnect_ivl,
58 }
59 }
60
61 pub fn next_delay(&mut self) -> Duration {
71 let delay = self.current_interval;
72
73 self.attempt += 1;
75 self.current_interval = self
76 .base_interval
77 .checked_mul(1_u32 << self.attempt.min(10))
78 .unwrap_or(self.max_interval);
79
80 if self.current_interval > self.max_interval {
82 self.current_interval = self.max_interval;
83 }
84
85 delay
86 }
87
88 pub fn reset(&mut self) {
92 self.attempt = 0;
93 self.current_interval = self.base_interval;
94 }
95
96 #[inline]
98 #[must_use]
99 pub const fn attempt(&self) -> u32 {
100 self.attempt
101 }
102
103 #[inline]
105 #[must_use]
106 pub const fn base_interval(&self) -> Duration {
107 self.base_interval
108 }
109
110 #[inline]
112 #[must_use]
113 pub const fn max_interval(&self) -> Duration {
114 self.max_interval
115 }
116
117 #[inline]
119 #[must_use]
120 pub const fn current_interval(&self) -> Duration {
121 self.current_interval
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
127pub enum ReconnectError {
128 MaxAttemptsReached { attempts: u32 },
130 ConnectionFailed { message: String },
132 Cancelled,
134}
135
136impl std::fmt::Display for ReconnectError {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 match self {
139 Self::MaxAttemptsReached { attempts } => {
140 write!(f, "Maximum reconnection attempts reached: {attempts}")
141 }
142 Self::ConnectionFailed { message } => {
143 write!(f, "Connection failed: {message}")
144 }
145 Self::Cancelled => {
146 write!(f, "Reconnection cancelled")
147 }
148 }
149 }
150}
151
152impl std::error::Error for ReconnectError {}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_exponential_backoff() {
160 let options = SocketOptions::default()
161 .with_reconnect_ivl(Duration::from_millis(100))
162 .with_reconnect_ivl_max(Duration::from_secs(10));
163
164 let mut state = ReconnectState::new(&options);
165
166 assert_eq!(state.next_delay(), Duration::from_millis(100));
168 assert_eq!(state.attempt(), 1);
169
170 assert_eq!(state.next_delay(), Duration::from_millis(200));
172 assert_eq!(state.attempt(), 2);
173
174 assert_eq!(state.next_delay(), Duration::from_millis(400));
176 assert_eq!(state.attempt(), 3);
177
178 assert_eq!(state.next_delay(), Duration::from_millis(800));
180 assert_eq!(state.attempt(), 4);
181 }
182
183 #[test]
184 fn test_max_interval_cap() {
185 let options = SocketOptions::default()
186 .with_reconnect_ivl(Duration::from_millis(100))
187 .with_reconnect_ivl_max(Duration::from_millis(500));
188
189 let mut state = ReconnectState::new(&options);
190
191 assert_eq!(state.next_delay(), Duration::from_millis(100));
192 assert_eq!(state.next_delay(), Duration::from_millis(200));
193 assert_eq!(state.next_delay(), Duration::from_millis(400));
194
195 assert_eq!(state.next_delay(), Duration::from_millis(500));
197 assert_eq!(state.next_delay(), Duration::from_millis(500));
198 }
199
200 #[test]
201 fn test_next_delay_caps_before_duration_overflow() {
202 let options = SocketOptions::default()
203 .with_reconnect_ivl(Duration::MAX)
204 .with_reconnect_ivl_max(Duration::from_secs(1));
205
206 let mut state = ReconnectState::new(&options);
207 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
208 state.next_delay();
209 }));
210
211 assert!(result.is_ok());
212 }
213
214 #[test]
215 fn test_reset() {
216 let options = SocketOptions::default()
217 .with_reconnect_ivl(Duration::from_millis(100))
218 .with_reconnect_ivl_max(Duration::from_secs(10));
219
220 let mut state = ReconnectState::new(&options);
221
222 state.next_delay();
224 state.next_delay();
225 state.next_delay();
226 assert_eq!(state.attempt(), 3);
227
228 state.reset();
230 assert_eq!(state.attempt(), 0);
231 assert_eq!(state.next_delay(), Duration::from_millis(100));
232 }
233
234 #[test]
235 fn test_state_accessors() {
236 let options = SocketOptions::default()
237 .with_reconnect_ivl(Duration::from_millis(250))
238 .with_reconnect_ivl_max(Duration::from_secs(5));
239
240 let state = ReconnectState::new(&options);
241
242 assert_eq!(state.base_interval(), Duration::from_millis(250));
243 assert_eq!(state.max_interval(), Duration::from_secs(5));
244 assert_eq!(state.current_interval(), Duration::from_millis(250));
245 assert_eq!(state.attempt(), 0);
246 }
247}