1use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum DrainState {
13 Active,
15 Draining,
17 Drained,
19}
20
21#[derive(Debug, Clone)]
23pub struct DrainableConnection {
24 pub conn_id: u64,
26 pub peer_id: String,
28 pub pending_requests: u32,
30 pub state: DrainState,
32 pub drain_started_at: Option<u64>,
34}
35
36#[derive(Debug, Clone)]
38pub struct DrainerConfig {
39 pub drain_timeout_ms: u64,
41 pub max_drain_wait_ms: u64,
43 pub reject_new_requests: bool,
45 pub graceful_close_delay_ms: u64,
47}
48
49impl Default for DrainerConfig {
50 fn default() -> Self {
51 Self {
52 drain_timeout_ms: 30_000,
53 max_drain_wait_ms: 60_000,
54 reject_new_requests: true,
55 graceful_close_delay_ms: 500,
56 }
57 }
58}
59
60#[derive(Debug, Clone, Default)]
62pub struct DrainerStats {
63 pub total_drained: u64,
65 pub total_timed_out: u64,
67 pub total_rejected: u64,
69 pub avg_drain_time_ms: f64,
71 pub currently_draining: u64,
73}
74
75pub struct ConnectionDrainer {
80 config: DrainerConfig,
81 connections: HashMap<u64, DrainableConnection>,
82 drain_order: Vec<u64>,
83 next_conn_id: u64,
84 stats: DrainerStats,
85 drain_duration_sum_ms: u64,
87}
88
89impl ConnectionDrainer {
90 pub fn new(config: DrainerConfig) -> Self {
92 Self {
93 config,
94 connections: HashMap::new(),
95 drain_order: Vec::new(),
96 next_conn_id: 1,
97 stats: DrainerStats::default(),
98 drain_duration_sum_ms: 0,
99 }
100 }
101
102 pub fn register_connection(&mut self, peer_id: &str) -> u64 {
104 let conn_id = self.next_conn_id;
105 self.next_conn_id = self.next_conn_id.wrapping_add(1);
106
107 let conn = DrainableConnection {
108 conn_id,
109 peer_id: peer_id.to_string(),
110 pending_requests: 0,
111 state: DrainState::Active,
112 drain_started_at: None,
113 };
114 self.connections.insert(conn_id, conn);
115 conn_id
116 }
117
118 pub fn start_drain(&mut self, conn_id: u64) -> Result<(), String> {
123 let conn = self
124 .connections
125 .get_mut(&conn_id)
126 .ok_or_else(|| format!("connection {conn_id} not found"))?;
127
128 match conn.state {
129 DrainState::Active => {
130 conn.state = DrainState::Draining;
131 conn.drain_started_at = Some(0);
134 self.drain_order.push(conn_id);
135 self.stats.currently_draining += 1;
136
137 if conn.pending_requests == 0 {
139 conn.state = DrainState::Drained;
140 self.stats.currently_draining = self.stats.currently_draining.saturating_sub(1);
141 self.stats.total_drained += 1;
142 }
143 Ok(())
144 }
145 DrainState::Draining => Err(format!("connection {conn_id} is already draining")),
146 DrainState::Drained => Err(format!("connection {conn_id} is already drained")),
147 }
148 }
149
150 pub fn start_drain_all(&mut self) {
152 let active_ids: Vec<u64> = self
153 .connections
154 .iter()
155 .filter(|(_, c)| c.state == DrainState::Active)
156 .map(|(id, _)| *id)
157 .collect();
158
159 for id in active_ids {
160 let _ = self.start_drain(id);
162 }
163 }
164
165 pub fn complete_request(&mut self, conn_id: u64) -> Result<(), String> {
170 let conn = self
171 .connections
172 .get_mut(&conn_id)
173 .ok_or_else(|| format!("connection {conn_id} not found"))?;
174
175 if conn.pending_requests == 0 {
176 return Err(format!("connection {conn_id} has no pending requests"));
177 }
178
179 conn.pending_requests -= 1;
180
181 if conn.state == DrainState::Draining && conn.pending_requests == 0 {
182 conn.state = DrainState::Drained;
183 self.stats.currently_draining = self.stats.currently_draining.saturating_sub(1);
184 self.stats.total_drained += 1;
185 if let Some(start) = conn.drain_started_at {
188 if start > 0 {
189 self.drain_duration_sum_ms += 0; }
192 }
193 }
194
195 Ok(())
196 }
197
198 pub fn add_request(&mut self, conn_id: u64) -> Result<(), String> {
203 let conn = self
204 .connections
205 .get_mut(&conn_id)
206 .ok_or_else(|| format!("connection {conn_id} not found"))?;
207
208 match conn.state {
209 DrainState::Active => {
210 conn.pending_requests += 1;
211 Ok(())
212 }
213 DrainState::Draining => {
214 if self.config.reject_new_requests {
215 self.stats.total_rejected += 1;
216 Err(format!(
217 "connection {conn_id} is draining; new requests rejected"
218 ))
219 } else {
220 conn.pending_requests += 1;
221 Ok(())
222 }
223 }
224 DrainState::Drained => {
225 self.stats.total_rejected += 1;
226 Err(format!("connection {conn_id} is already drained"))
227 }
228 }
229 }
230
231 pub fn check_drained(&mut self, conn_id: u64) -> Option<DrainState> {
237 let conn = self.connections.get_mut(&conn_id)?;
238
239 if conn.state == DrainState::Draining && conn.pending_requests == 0 {
240 conn.state = DrainState::Drained;
241 self.stats.currently_draining = self.stats.currently_draining.saturating_sub(1);
242 self.stats.total_drained += 1;
243 }
244
245 Some(conn.state.clone())
246 }
247
248 pub fn check_timeouts(&mut self, now: u64) -> Vec<u64> {
253 let timeout = self.config.drain_timeout_ms;
254 let timed_out: Vec<u64> = self
255 .connections
256 .iter()
257 .filter(|(_, c)| c.state == DrainState::Draining)
258 .filter(|(_, c)| {
259 c.drain_started_at
260 .map(|s| now.saturating_sub(s) >= timeout)
261 .unwrap_or(false)
262 })
263 .map(|(id, _)| *id)
264 .collect();
265
266 for id in &timed_out {
267 if let Some(conn) = self.connections.get_mut(id) {
268 conn.state = DrainState::Drained;
269 self.stats.currently_draining = self.stats.currently_draining.saturating_sub(1);
270 self.stats.total_timed_out += 1;
271
272 if let Some(start) = conn.drain_started_at {
273 let elapsed = now.saturating_sub(start);
274 self.drain_duration_sum_ms += elapsed;
275 let total_completed = self.stats.total_drained + self.stats.total_timed_out;
276 if total_completed > 0 {
277 self.stats.avg_drain_time_ms =
278 self.drain_duration_sum_ms as f64 / total_completed as f64;
279 }
280 }
281 }
282 }
283
284 timed_out
285 }
286
287 pub fn remove_drained(&mut self) -> usize {
289 let before = self.connections.len();
290 self.connections
291 .retain(|_, c| c.state != DrainState::Drained);
292 self.drain_order
293 .retain(|id| self.connections.contains_key(id));
294 before - self.connections.len()
295 }
296
297 pub fn get_connection(&self, conn_id: u64) -> Option<&DrainableConnection> {
299 self.connections.get(&conn_id)
300 }
301
302 pub fn active_count(&self) -> usize {
304 self.connections
305 .values()
306 .filter(|c| c.state == DrainState::Active)
307 .count()
308 }
309
310 pub fn draining_count(&self) -> usize {
312 self.connections
313 .values()
314 .filter(|c| c.state == DrainState::Draining)
315 .count()
316 }
317
318 pub fn is_fully_drained(&self) -> bool {
320 self.connections
321 .values()
322 .all(|c| c.state == DrainState::Drained)
323 }
324
325 pub fn stats(&self) -> &DrainerStats {
327 &self.stats
328 }
329
330 pub fn set_drain_start_time(&mut self, conn_id: u64, timestamp_ms: u64) {
335 if let Some(conn) = self.connections.get_mut(&conn_id) {
336 if conn.state == DrainState::Draining || conn.state == DrainState::Drained {
337 conn.drain_started_at = Some(timestamp_ms);
338 }
339 }
340 }
341}
342
343#[cfg(test)]
347mod tests {
348 use super::*;
349
350 fn default_config() -> DrainerConfig {
351 DrainerConfig::default()
352 }
353
354 #[test]
356 fn test_register_returns_unique_ids() {
357 let mut d = ConnectionDrainer::new(default_config());
358 let a = d.register_connection("peer-a");
359 let b = d.register_connection("peer-b");
360 assert_ne!(a, b);
361 }
362
363 #[test]
365 fn test_registered_connection_is_active() {
366 let mut d = ConnectionDrainer::new(default_config());
367 let id = d.register_connection("peer-a");
368 let conn = d.get_connection(id).expect("should exist");
369 assert_eq!(conn.state, DrainState::Active);
370 assert_eq!(conn.pending_requests, 0);
371 }
372
373 #[test]
375 fn test_start_drain_active() {
376 let mut d = ConnectionDrainer::new(default_config());
377 let id = d.register_connection("peer-a");
378 d.add_request(id).expect("add ok");
379 d.start_drain(id).expect("drain ok");
380 let conn = d.get_connection(id).expect("exists");
381 assert_eq!(conn.state, DrainState::Draining);
382 }
383
384 #[test]
386 fn test_start_drain_zero_pending_immediate() {
387 let mut d = ConnectionDrainer::new(default_config());
388 let id = d.register_connection("peer-a");
389 d.start_drain(id).expect("drain ok");
390 let conn = d.get_connection(id).expect("exists");
391 assert_eq!(conn.state, DrainState::Drained);
392 }
393
394 #[test]
396 fn test_duplicate_drain_fails() {
397 let mut d = ConnectionDrainer::new(default_config());
398 let id = d.register_connection("peer-a");
399 d.add_request(id).expect("add ok");
400 d.start_drain(id).expect("first ok");
401 assert!(d.start_drain(id).is_err());
402 }
403
404 #[test]
406 fn test_drain_nonexistent() {
407 let mut d = ConnectionDrainer::new(default_config());
408 assert!(d.start_drain(999).is_err());
409 }
410
411 #[test]
413 fn test_start_drain_all() {
414 let mut d = ConnectionDrainer::new(default_config());
415 let a = d.register_connection("peer-a");
416 let b = d.register_connection("peer-b");
417 d.add_request(a).expect("ok");
418 d.add_request(b).expect("ok");
419 d.start_drain_all();
420 assert_eq!(d.active_count(), 0);
421 assert_eq!(d.draining_count(), 2);
422 }
423
424 #[test]
426 fn test_reject_request_during_drain() {
427 let mut d = ConnectionDrainer::new(default_config());
428 let id = d.register_connection("peer-a");
429 d.add_request(id).expect("ok");
430 d.start_drain(id).expect("ok");
431 assert!(d.add_request(id).is_err());
432 assert_eq!(d.stats().total_rejected, 1);
433 }
434
435 #[test]
437 fn test_allow_request_during_drain_when_not_rejecting() {
438 let cfg = DrainerConfig {
439 reject_new_requests: false,
440 ..default_config()
441 };
442 let mut d = ConnectionDrainer::new(cfg);
443 let id = d.register_connection("peer-a");
444 d.add_request(id).expect("ok");
445 d.start_drain(id).expect("ok");
446 d.add_request(id).expect("should be allowed");
447 let conn = d.get_connection(id).expect("exists");
448 assert_eq!(conn.pending_requests, 2);
449 }
450
451 #[test]
453 fn test_complete_request_decrements() {
454 let mut d = ConnectionDrainer::new(default_config());
455 let id = d.register_connection("peer-a");
456 d.add_request(id).expect("ok");
457 d.add_request(id).expect("ok");
458 d.complete_request(id).expect("ok");
459 let conn = d.get_connection(id).expect("exists");
460 assert_eq!(conn.pending_requests, 1);
461 }
462
463 #[test]
465 fn test_complete_request_auto_drain() {
466 let mut d = ConnectionDrainer::new(default_config());
467 let id = d.register_connection("peer-a");
468 d.add_request(id).expect("ok");
469 d.start_drain(id).expect("ok");
470 d.complete_request(id).expect("ok");
471 let conn = d.get_connection(id).expect("exists");
472 assert_eq!(conn.state, DrainState::Drained);
473 }
474
475 #[test]
477 fn test_complete_request_zero_pending_error() {
478 let mut d = ConnectionDrainer::new(default_config());
479 let id = d.register_connection("peer-a");
480 assert!(d.complete_request(id).is_err());
481 }
482
483 #[test]
485 fn test_complete_request_nonexistent() {
486 let mut d = ConnectionDrainer::new(default_config());
487 assert!(d.complete_request(999).is_err());
488 }
489
490 #[test]
492 fn test_check_drained_transitions() {
493 let mut d = ConnectionDrainer::new(default_config());
494 let id = d.register_connection("peer-a");
495 d.add_request(id).expect("ok");
496 d.start_drain(id).expect("ok");
497 d.complete_request(id).expect("ok");
498 let state = d.check_drained(id);
500 assert_eq!(state, Some(DrainState::Drained));
501 }
502
503 #[test]
505 fn test_check_drained_unknown() {
506 let mut d = ConnectionDrainer::new(default_config());
507 assert!(d.check_drained(999).is_none());
508 }
509
510 #[test]
512 fn test_check_timeouts() {
513 let cfg = DrainerConfig {
514 drain_timeout_ms: 100,
515 ..default_config()
516 };
517 let mut d = ConnectionDrainer::new(cfg);
518 let id = d.register_connection("peer-a");
519 d.add_request(id).expect("ok");
520 d.start_drain(id).expect("ok");
521 d.set_drain_start_time(id, 1000);
522
523 let timed = d.check_timeouts(1050);
525 assert!(timed.is_empty());
526
527 let timed = d.check_timeouts(1100);
529 assert_eq!(timed, vec![id]);
530 let conn = d.get_connection(id).expect("exists");
531 assert_eq!(conn.state, DrainState::Drained);
532 assert_eq!(d.stats().total_timed_out, 1);
533 }
534
535 #[test]
537 fn test_remove_drained() {
538 let mut d = ConnectionDrainer::new(default_config());
539 let a = d.register_connection("peer-a");
540 let b = d.register_connection("peer-b");
541 d.start_drain(a).expect("ok"); let removed = d.remove_drained();
543 assert_eq!(removed, 1);
544 assert!(d.get_connection(a).is_none());
545 assert!(d.get_connection(b).is_some());
546 }
547
548 #[test]
550 fn test_active_count() {
551 let mut d = ConnectionDrainer::new(default_config());
552 d.register_connection("a");
553 d.register_connection("b");
554 assert_eq!(d.active_count(), 2);
555 }
556
557 #[test]
559 fn test_draining_count() {
560 let mut d = ConnectionDrainer::new(default_config());
561 let a = d.register_connection("a");
562 let b = d.register_connection("b");
563 d.add_request(a).expect("ok");
564 d.add_request(b).expect("ok");
565 d.start_drain(a).expect("ok");
566 assert_eq!(d.draining_count(), 1);
567 }
568
569 #[test]
571 fn test_is_fully_drained_empty() {
572 let d = ConnectionDrainer::new(default_config());
573 assert!(d.is_fully_drained());
574 }
575
576 #[test]
578 fn test_is_fully_drained_with_active() {
579 let mut d = ConnectionDrainer::new(default_config());
580 d.register_connection("a");
581 assert!(!d.is_fully_drained());
582 }
583
584 #[test]
586 fn test_is_fully_drained_all_drained() {
587 let mut d = ConnectionDrainer::new(default_config());
588 let a = d.register_connection("a");
589 let b = d.register_connection("b");
590 d.start_drain(a).expect("ok");
591 d.start_drain(b).expect("ok");
592 assert!(d.is_fully_drained());
593 }
594
595 #[test]
597 fn test_stats_total_drained() {
598 let mut d = ConnectionDrainer::new(default_config());
599 let a = d.register_connection("a");
600 let b = d.register_connection("b");
601 d.start_drain(a).expect("ok");
602 d.start_drain(b).expect("ok");
603 assert_eq!(d.stats().total_drained, 2);
604 }
605
606 #[test]
608 fn test_stats_currently_draining() {
609 let mut d = ConnectionDrainer::new(default_config());
610 let a = d.register_connection("a");
611 d.add_request(a).expect("ok");
612 d.start_drain(a).expect("ok");
613 assert_eq!(d.stats().currently_draining, 1);
614 d.complete_request(a).expect("ok");
615 assert_eq!(d.stats().currently_draining, 0);
616 }
617
618 #[test]
620 fn test_avg_drain_time_on_timeout() {
621 let cfg = DrainerConfig {
622 drain_timeout_ms: 50,
623 ..default_config()
624 };
625 let mut d = ConnectionDrainer::new(cfg);
626 let id = d.register_connection("a");
627 d.add_request(id).expect("ok");
628 d.start_drain(id).expect("ok");
629 d.set_drain_start_time(id, 100);
630 d.check_timeouts(200);
631 assert!(d.stats().avg_drain_time_ms > 0.0);
633 }
634
635 #[test]
637 fn test_peer_id_stored() {
638 let mut d = ConnectionDrainer::new(default_config());
639 let id = d.register_connection("QmPeer123");
640 let conn = d.get_connection(id).expect("exists");
641 assert_eq!(conn.peer_id, "QmPeer123");
642 }
643
644 #[test]
646 fn test_full_lifecycle() {
647 let mut d = ConnectionDrainer::new(default_config());
648 let id = d.register_connection("peer");
649 d.add_request(id).expect("ok");
650 d.add_request(id).expect("ok");
651 d.add_request(id).expect("ok");
652 d.start_drain(id).expect("ok");
653 assert_eq!(d.draining_count(), 1);
654
655 d.complete_request(id).expect("ok");
656 d.complete_request(id).expect("ok");
657 assert_eq!(d.get_connection(id).expect("e").state, DrainState::Draining);
658
659 d.complete_request(id).expect("ok");
660 assert_eq!(d.get_connection(id).expect("e").state, DrainState::Drained);
661 assert!(d.is_fully_drained());
662
663 let removed = d.remove_drained();
664 assert_eq!(removed, 1);
665 assert!(d.get_connection(id).is_none());
666 }
667
668 #[test]
670 fn test_add_request_on_drained() {
671 let mut d = ConnectionDrainer::new(default_config());
672 let id = d.register_connection("peer");
673 d.start_drain(id).expect("ok");
674 assert!(d.add_request(id).is_err());
675 assert_eq!(d.stats().total_rejected, 1);
676 }
677
678 #[test]
680 fn test_add_request_nonexistent() {
681 let mut d = ConnectionDrainer::new(default_config());
682 assert!(d.add_request(999).is_err());
683 }
684
685 #[test]
687 fn test_drain_all_idempotent() {
688 let mut d = ConnectionDrainer::new(default_config());
689 let a = d.register_connection("a");
690 d.add_request(a).expect("ok");
691 d.start_drain(a).expect("ok");
692 d.start_drain_all();
694 assert_eq!(d.draining_count(), 1);
695 }
696
697 #[test]
699 fn test_check_timeouts_empty() {
700 let mut d = ConnectionDrainer::new(default_config());
701 d.register_connection("a");
702 let timed = d.check_timeouts(999_999);
703 assert!(timed.is_empty());
704 }
705
706 #[test]
708 fn test_remove_drained_empty() {
709 let mut d = ConnectionDrainer::new(default_config());
710 assert_eq!(d.remove_drained(), 0);
711 }
712
713 #[test]
715 fn test_default_config() {
716 let cfg = DrainerConfig::default();
717 assert_eq!(cfg.drain_timeout_ms, 30_000);
718 assert_eq!(cfg.max_drain_wait_ms, 60_000);
719 assert!(cfg.reject_new_requests);
720 assert_eq!(cfg.graceful_close_delay_ms, 500);
721 }
722
723 #[test]
725 fn test_drainer_stats_default() {
726 let s = DrainerStats::default();
727 assert_eq!(s.total_drained, 0);
728 assert_eq!(s.total_timed_out, 0);
729 assert_eq!(s.total_rejected, 0);
730 assert_eq!(s.currently_draining, 0);
731 assert!((s.avg_drain_time_ms - 0.0).abs() < f64::EPSILON);
732 }
733
734 #[test]
736 fn test_set_drain_start_time_active_noop() {
737 let mut d = ConnectionDrainer::new(default_config());
738 let id = d.register_connection("peer");
739 d.set_drain_start_time(id, 12345);
740 let conn = d.get_connection(id).expect("exists");
742 assert!(conn.drain_started_at.is_none());
743 }
744}