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.base_interval * (1_u32 << self.attempt.min(10));
76
77 if self.current_interval > self.max_interval {
79 self.current_interval = self.max_interval;
80 }
81
82 delay
83 }
84
85 pub fn reset(&mut self) {
89 self.attempt = 0;
90 self.current_interval = self.base_interval;
91 }
92
93 #[inline]
95 #[must_use]
96 pub const fn attempt(&self) -> u32 {
97 self.attempt
98 }
99
100 #[inline]
102 #[must_use]
103 pub const fn base_interval(&self) -> Duration {
104 self.base_interval
105 }
106
107 #[inline]
109 #[must_use]
110 pub const fn max_interval(&self) -> Duration {
111 self.max_interval
112 }
113
114 #[inline]
116 #[must_use]
117 pub const fn current_interval(&self) -> Duration {
118 self.current_interval
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum ReconnectError {
125 MaxAttemptsReached { attempts: u32 },
127 ConnectionFailed { message: String },
129 Cancelled,
131}
132
133impl std::fmt::Display for ReconnectError {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 Self::MaxAttemptsReached { attempts } => {
137 write!(f, "Maximum reconnection attempts reached: {attempts}")
138 }
139 Self::ConnectionFailed { message } => {
140 write!(f, "Connection failed: {message}")
141 }
142 Self::Cancelled => {
143 write!(f, "Reconnection cancelled")
144 }
145 }
146 }
147}
148
149impl std::error::Error for ReconnectError {}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_exponential_backoff() {
157 let options = SocketOptions::default()
158 .with_reconnect_ivl(Duration::from_millis(100))
159 .with_reconnect_ivl_max(Duration::from_secs(10));
160
161 let mut state = ReconnectState::new(&options);
162
163 assert_eq!(state.next_delay(), Duration::from_millis(100));
165 assert_eq!(state.attempt(), 1);
166
167 assert_eq!(state.next_delay(), Duration::from_millis(200));
169 assert_eq!(state.attempt(), 2);
170
171 assert_eq!(state.next_delay(), Duration::from_millis(400));
173 assert_eq!(state.attempt(), 3);
174
175 assert_eq!(state.next_delay(), Duration::from_millis(800));
177 assert_eq!(state.attempt(), 4);
178 }
179
180 #[test]
181 fn test_max_interval_cap() {
182 let options = SocketOptions::default()
183 .with_reconnect_ivl(Duration::from_millis(100))
184 .with_reconnect_ivl_max(Duration::from_millis(500));
185
186 let mut state = ReconnectState::new(&options);
187
188 assert_eq!(state.next_delay(), Duration::from_millis(100));
189 assert_eq!(state.next_delay(), Duration::from_millis(200));
190 assert_eq!(state.next_delay(), Duration::from_millis(400));
191
192 assert_eq!(state.next_delay(), Duration::from_millis(500));
194 assert_eq!(state.next_delay(), Duration::from_millis(500));
195 }
196
197 #[test]
198 fn test_reset() {
199 let options = SocketOptions::default()
200 .with_reconnect_ivl(Duration::from_millis(100))
201 .with_reconnect_ivl_max(Duration::from_secs(10));
202
203 let mut state = ReconnectState::new(&options);
204
205 state.next_delay();
207 state.next_delay();
208 state.next_delay();
209 assert_eq!(state.attempt(), 3);
210
211 state.reset();
213 assert_eq!(state.attempt(), 0);
214 assert_eq!(state.next_delay(), Duration::from_millis(100));
215 }
216
217 #[test]
218 fn test_state_accessors() {
219 let options = SocketOptions::default()
220 .with_reconnect_ivl(Duration::from_millis(250))
221 .with_reconnect_ivl_max(Duration::from_secs(5));
222
223 let state = ReconnectState::new(&options);
224
225 assert_eq!(state.base_interval(), Duration::from_millis(250));
226 assert_eq!(state.max_interval(), Duration::from_secs(5));
227 assert_eq!(state.current_interval(), Duration::from_millis(250));
228 assert_eq!(state.attempt(), 0);
229 }
230}