1use std::collections::HashMap;
42use std::time::{Duration, Instant};
43
44#[derive(Debug, Clone)]
46pub struct ReconnectionConfig {
47 pub base_delay: Duration,
49 pub max_delay: Duration,
51 pub max_attempts: u32,
53 pub check_interval: Duration,
55 pub use_flat_delay: bool,
58 pub reset_on_exhaustion: bool,
61}
62
63impl Default for ReconnectionConfig {
64 fn default() -> Self {
65 Self {
66 base_delay: Duration::from_secs(2),
67 max_delay: Duration::from_secs(60),
68 max_attempts: 10,
69 check_interval: Duration::from_secs(5),
70 use_flat_delay: false,
71 reset_on_exhaustion: false,
72 }
73 }
74}
75
76impl ReconnectionConfig {
77 pub fn new(
79 base_delay: Duration,
80 max_delay: Duration,
81 max_attempts: u32,
82 check_interval: Duration,
83 ) -> Self {
84 Self {
85 base_delay,
86 max_delay,
87 max_attempts,
88 check_interval,
89 use_flat_delay: false,
90 reset_on_exhaustion: false,
91 }
92 }
93
94 pub fn fast() -> Self {
96 Self {
97 base_delay: Duration::from_millis(500),
98 max_delay: Duration::from_secs(5),
99 max_attempts: 5,
100 check_interval: Duration::from_secs(1),
101 use_flat_delay: false,
102 reset_on_exhaustion: false,
103 }
104 }
105
106 pub fn conservative() -> Self {
108 Self {
109 base_delay: Duration::from_secs(5),
110 max_delay: Duration::from_secs(120),
111 max_attempts: 5,
112 check_interval: Duration::from_secs(10),
113 use_flat_delay: false,
114 reset_on_exhaustion: false,
115 }
116 }
117
118 pub fn kotlin_normal() -> Self {
120 Self {
121 base_delay: Duration::from_millis(1000),
122 max_delay: Duration::from_millis(15000),
123 max_attempts: 20,
124 check_interval: Duration::from_secs(5),
125 use_flat_delay: false,
126 reset_on_exhaustion: false,
127 }
128 }
129
130 pub fn kotlin_high_priority() -> Self {
132 Self {
133 base_delay: Duration::from_millis(1000),
134 max_delay: Duration::from_millis(15000),
135 max_attempts: 20,
136 check_interval: Duration::from_secs(5),
137 use_flat_delay: true,
138 reset_on_exhaustion: true,
139 }
140 }
141}
142
143#[derive(Debug, Clone)]
145struct PeerReconnectionState {
146 attempts: u32,
148 last_attempt: Instant,
150 disconnected_at: Instant,
152}
153
154impl PeerReconnectionState {
155 fn new() -> Self {
156 let now = Instant::now();
157 Self {
158 attempts: 0,
159 last_attempt: now,
160 disconnected_at: now,
161 }
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
167pub enum ReconnectionStatus {
168 Ready,
170 Waiting {
172 remaining: Duration,
174 },
175 Exhausted {
177 attempts: u32,
179 },
180 NotTracked,
182}
183
184#[derive(Debug)]
189pub struct ReconnectionManager {
190 config: ReconnectionConfig,
192 peers: HashMap<String, PeerReconnectionState>,
194}
195
196impl ReconnectionManager {
197 pub fn new(config: ReconnectionConfig) -> Self {
199 Self {
200 config,
201 peers: HashMap::new(),
202 }
203 }
204
205 pub fn with_defaults() -> Self {
207 Self::new(ReconnectionConfig::default())
208 }
209
210 pub fn track_disconnection(&mut self, address: String) {
214 use std::collections::hash_map::Entry;
215
216 if let Entry::Vacant(entry) = self.peers.entry(address.clone()) {
217 log::debug!("Tracking {} for reconnection", address);
218 entry.insert(PeerReconnectionState::new());
219 }
220 }
221
222 pub fn is_tracked(&self, address: &str) -> bool {
224 self.peers.contains_key(address)
225 }
226
227 pub fn get_status(&self, address: &str) -> ReconnectionStatus {
229 match self.peers.get(address) {
230 None => ReconnectionStatus::NotTracked,
231 Some(state) => {
232 if state.attempts >= self.config.max_attempts {
233 if self.config.reset_on_exhaustion {
234 return ReconnectionStatus::Ready;
236 }
237 return ReconnectionStatus::Exhausted {
238 attempts: state.attempts,
239 };
240 }
241
242 if state.attempts == 0 {
244 return ReconnectionStatus::Ready;
245 }
246
247 let delay = self.calculate_delay(state.attempts);
249 let elapsed = state.last_attempt.elapsed();
250
251 if elapsed >= delay {
252 ReconnectionStatus::Ready
253 } else {
254 ReconnectionStatus::Waiting {
255 remaining: delay - elapsed,
256 }
257 }
258 }
259 }
260 }
261
262 fn calculate_delay(&self, attempts: u32) -> Duration {
267 if self.config.use_flat_delay {
268 return self.config.base_delay;
269 }
270 let multiplier = 1u64 << attempts.min(30); let delay_ms = self.config.base_delay.as_millis() as u64 * multiplier;
272 let max_ms = self.config.max_delay.as_millis() as u64;
273 Duration::from_millis(delay_ms.min(max_ms))
274 }
275
276 pub fn get_peers_to_reconnect(&mut self) -> Vec<String> {
282 if self.config.reset_on_exhaustion {
284 let max = self.config.max_attempts;
285 for state in self.peers.values_mut() {
286 if state.attempts >= max {
287 log::debug!("Auto-resetting exhausted peer (reset_on_exhaustion)");
288 state.attempts = 0;
289 state.last_attempt = Instant::now();
290 }
291 }
292 }
293
294 self.peers
295 .iter()
296 .filter_map(|(address, state)| {
297 if state.attempts >= self.config.max_attempts {
298 return None;
299 }
300
301 if state.attempts == 0 {
303 return Some(address.clone());
304 }
305
306 let delay = self.calculate_delay(state.attempts);
308 if state.last_attempt.elapsed() >= delay {
309 Some(address.clone())
310 } else {
311 None
312 }
313 })
314 .collect()
315 }
316
317 pub fn record_attempt(&mut self, address: &str) {
321 let attempts = if let Some(state) = self.peers.get_mut(address) {
322 state.attempts += 1;
323 state.last_attempt = Instant::now();
324 Some(state.attempts)
325 } else {
326 None
327 };
328
329 if let Some(attempts) = attempts {
330 let next_delay = self.calculate_delay(attempts);
331 log::debug!(
332 "Reconnection attempt {} for {} (next delay: {:?})",
333 attempts,
334 address,
335 next_delay
336 );
337 }
338 }
339
340 pub fn on_connection_success(&mut self, address: &str) {
344 if self.peers.remove(address).is_some() {
345 log::debug!(
346 "Connection succeeded for {}, removed from reconnection tracking",
347 address
348 );
349 }
350 }
351
352 pub fn stop_tracking(&mut self, address: &str) {
354 if self.peers.remove(address).is_some() {
355 log::debug!("Stopped tracking {} for reconnection", address);
356 }
357 }
358
359 pub fn clear(&mut self) {
361 let count = self.peers.len();
362 self.peers.clear();
363 if count > 0 {
364 log::debug!("Cleared reconnection tracking for {} peers", count);
365 }
366 }
367
368 pub fn tracked_count(&self) -> usize {
370 self.peers.len()
371 }
372
373 pub fn get_peer_stats(&self, address: &str) -> Option<PeerReconnectionStats> {
375 self.peers.get(address).map(|state| PeerReconnectionStats {
376 attempts: state.attempts,
377 max_attempts: self.config.max_attempts,
378 disconnected_duration: state.disconnected_at.elapsed(),
379 next_attempt_delay: if state.attempts >= self.config.max_attempts {
380 Duration::MAX } else if state.attempts == 0 {
382 Duration::ZERO } else {
384 self.calculate_delay(state.attempts)
385 },
386 })
387 }
388
389 pub fn check_interval(&self) -> Duration {
391 self.config.check_interval
392 }
393}
394
395#[derive(Debug, Clone)]
397pub struct PeerReconnectionStats {
398 pub attempts: u32,
400 pub max_attempts: u32,
402 pub disconnected_duration: Duration,
404 pub next_attempt_delay: Duration,
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn test_exponential_backoff() {
416 let config = ReconnectionConfig {
417 base_delay: Duration::from_secs(2),
418 max_delay: Duration::from_secs(60),
419 max_attempts: 10,
420 check_interval: Duration::from_secs(5),
421 use_flat_delay: false,
422 reset_on_exhaustion: false,
423 };
424 let manager = ReconnectionManager::new(config);
425
426 assert_eq!(manager.calculate_delay(0), Duration::from_secs(2));
428 assert_eq!(manager.calculate_delay(1), Duration::from_secs(4));
429 assert_eq!(manager.calculate_delay(2), Duration::from_secs(8));
430 assert_eq!(manager.calculate_delay(3), Duration::from_secs(16));
431 assert_eq!(manager.calculate_delay(4), Duration::from_secs(32));
432 assert_eq!(manager.calculate_delay(5), Duration::from_secs(60)); assert_eq!(manager.calculate_delay(6), Duration::from_secs(60));
434 }
435
436 #[test]
437 fn test_track_and_status() {
438 let mut manager = ReconnectionManager::new(ReconnectionConfig::fast());
439
440 assert_eq!(
442 manager.get_status("00:11:22:33:44:55"),
443 ReconnectionStatus::NotTracked
444 );
445
446 manager.track_disconnection("00:11:22:33:44:55".to_string());
448 assert!(manager.is_tracked("00:11:22:33:44:55"));
449
450 assert_eq!(
452 manager.get_status("00:11:22:33:44:55"),
453 ReconnectionStatus::Ready
454 );
455 }
456
457 #[test]
458 fn test_connection_success_clears_tracking() {
459 let mut manager = ReconnectionManager::with_defaults();
460
461 manager.track_disconnection("00:11:22:33:44:55".to_string());
462 assert!(manager.is_tracked("00:11:22:33:44:55"));
463
464 manager.on_connection_success("00:11:22:33:44:55");
465 assert!(!manager.is_tracked("00:11:22:33:44:55"));
466 assert_eq!(
467 manager.get_status("00:11:22:33:44:55"),
468 ReconnectionStatus::NotTracked
469 );
470 assert_eq!(manager.tracked_count(), 0);
471 }
472
473 #[test]
474 fn test_max_attempts_exhaustion() {
475 let config = ReconnectionConfig {
476 base_delay: Duration::from_millis(1),
477 max_delay: Duration::from_millis(10),
478 max_attempts: 3,
479 check_interval: Duration::from_millis(1),
480 use_flat_delay: false,
481 reset_on_exhaustion: false,
482 };
483 let mut manager = ReconnectionManager::new(config);
484
485 manager.track_disconnection("test".to_string());
486
487 for _ in 0..3 {
489 manager.record_attempt("test");
490 }
491
492 assert_eq!(
494 manager.get_status("test"),
495 ReconnectionStatus::Exhausted { attempts: 3 }
496 );
497 }
498
499 #[test]
502 fn test_kotlin_normal_config_backoff() {
503 let config = ReconnectionConfig::kotlin_normal();
506 assert_eq!(config.base_delay, Duration::from_millis(1000));
507 assert_eq!(config.max_delay, Duration::from_millis(15000));
508 assert_eq!(config.max_attempts, 20);
509 assert!(!config.use_flat_delay);
510 assert!(!config.reset_on_exhaustion);
511
512 let manager = ReconnectionManager::new(config);
513
514 assert_eq!(manager.calculate_delay(0), Duration::from_millis(1000));
517 assert_eq!(manager.calculate_delay(1), Duration::from_millis(2000));
519 assert_eq!(manager.calculate_delay(2), Duration::from_millis(4000));
521 assert_eq!(manager.calculate_delay(3), Duration::from_millis(8000));
523 assert_eq!(manager.calculate_delay(4), Duration::from_millis(15000));
525 assert_eq!(manager.calculate_delay(5), Duration::from_millis(15000));
527 }
528
529 #[test]
530 fn test_flat_delay_mode() {
531 let config = ReconnectionConfig::kotlin_high_priority();
533 assert!(config.use_flat_delay);
534
535 let manager = ReconnectionManager::new(config);
536
537 assert_eq!(manager.calculate_delay(0), Duration::from_millis(1000));
539 assert_eq!(manager.calculate_delay(1), Duration::from_millis(1000));
540 assert_eq!(manager.calculate_delay(5), Duration::from_millis(1000));
541 assert_eq!(manager.calculate_delay(19), Duration::from_millis(1000));
542 }
543
544 #[test]
545 fn test_reset_on_exhaustion() {
546 let config = ReconnectionConfig {
547 base_delay: Duration::from_millis(1),
548 max_delay: Duration::from_millis(10),
549 max_attempts: 3,
550 check_interval: Duration::from_millis(1),
551 use_flat_delay: true,
552 reset_on_exhaustion: true,
553 };
554 let mut manager = ReconnectionManager::new(config);
555
556 manager.track_disconnection("test".to_string());
557
558 for _ in 0..3 {
560 manager.record_attempt("test");
561 }
562
563 assert_eq!(manager.get_status("test"), ReconnectionStatus::Ready);
565
566 std::thread::sleep(Duration::from_millis(5));
568 let peers = manager.get_peers_to_reconnect();
569 assert!(peers.contains(&"test".to_string()));
570
571 let stats = manager.get_peer_stats("test").unwrap();
573 assert_eq!(stats.attempts, 0);
574 }
575
576 #[test]
577 fn test_stop_tracking_matches_reset() {
578 let mut manager = ReconnectionManager::with_defaults();
581
582 manager.track_disconnection("peer1".to_string());
583 manager.track_disconnection("peer2".to_string());
584 assert_eq!(manager.tracked_count(), 2);
585
586 manager.stop_tracking("peer1");
587 assert!(!manager.is_tracked("peer1"));
588 assert_eq!(manager.get_status("peer1"), ReconnectionStatus::NotTracked);
589 assert_eq!(manager.tracked_count(), 1);
590
591 manager.on_connection_success("peer2");
593 assert!(!manager.is_tracked("peer2"));
594 assert_eq!(manager.get_status("peer2"), ReconnectionStatus::NotTracked);
595 assert_eq!(manager.tracked_count(), 0);
596 }
597}