1use std::collections::HashMap;
36use thiserror::Error;
37
38#[inline]
44pub fn xorshift64(state: &mut u64) -> u64 {
45 let mut x = *state;
46 x ^= x << 13;
47 x ^= x >> 7;
48 x ^= x << 17;
49 *state = x;
50 x
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum ConnState {
60 Idle,
62 InUse {
64 borrowed_at: u64,
66 },
67 Draining,
69 Closed,
71 HealthChecking,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum AcquirePolicy {
82 Fifo,
84 Lifo,
86 LeastUsed,
88 HealthBest,
90 RoundRobin,
92}
93
94#[derive(Debug, Clone)]
100pub struct CpmPoolConfig {
101 pub min_connections: usize,
103 pub max_connections: usize,
105 pub idle_timeout_us: u64,
107 pub max_lifetime_us: u64,
109 pub health_check_interval_us: u64,
111 pub acquire_timeout_us: u64,
113 pub policy: AcquirePolicy,
115}
116
117impl Default for CpmPoolConfig {
118 fn default() -> Self {
119 Self {
120 min_connections: 1,
121 max_connections: 16,
122 idle_timeout_us: 60_000_000,
123 max_lifetime_us: 3_600_000_000,
124 health_check_interval_us: 10_000_000,
125 acquire_timeout_us: 5_000_000,
126 policy: AcquirePolicy::HealthBest,
127 }
128 }
129}
130
131#[derive(Debug, Clone)]
137pub struct PooledConnection {
138 pub id: u64,
140 pub peer_address: String,
142 pub created_at: u64,
144 pub last_used: u64,
146 pub use_count: u64,
148 pub health_score: f64,
150 pub tags: Vec<String>,
152 pub state: ConnState,
154}
155
156impl PooledConnection {
157 fn new(id: u64, peer_address: String, tags: Vec<String>, created_at: u64) -> Self {
158 Self {
159 id,
160 peer_address,
161 created_at,
162 last_used: created_at,
163 use_count: 0,
164 health_score: 1.0,
165 tags,
166 state: ConnState::Idle,
167 }
168 }
169
170 #[inline]
172 pub fn is_idle(&self) -> bool {
173 self.state == ConnState::Idle
174 }
175}
176
177#[derive(Debug, Clone, Default)]
183pub struct CpmPoolStats {
184 pub total_connections: usize,
186 pub idle_connections: usize,
188 pub in_use_connections: usize,
190 pub total_acquires: u64,
192 pub total_releases: u64,
194 pub acquire_failures: u64,
196 pub health_check_failures: u64,
198 pub avg_health_score: f64,
200}
201
202#[derive(Debug, Clone)]
208pub enum PoolEvent {
209 ConnectionAdded(u64),
211 ConnectionRemoved {
213 id: u64,
215 reason: String,
217 },
218 HealthCheckFailed {
220 id: u64,
222 score: f64,
224 },
225 AcquireTimeout,
227 PoolDrained,
229}
230
231#[derive(Debug, Error)]
237pub enum CpmPoolError {
238 #[error("connection pool exhausted (max {0} connections)")]
240 PoolExhausted(usize),
241
242 #[error("connection {0} not found in pool")]
244 ConnectionNotFound(u64),
245
246 #[error("acquire timed out after {0} µs")]
248 AcquireTimeout(u64),
249
250 #[error("health check failed for connection {0}")]
252 HealthCheckFailed(u64),
253
254 #[error("invalid configuration: {0}")]
256 InvalidConfiguration(String),
257}
258
259pub struct ConnectionPoolManager {
266 config: CpmPoolConfig,
267 connections: HashMap<u64, PooledConnection>,
268 next_id: u64,
269 round_robin_cursor: usize,
270 total_acquires: u64,
272 total_releases: u64,
273 acquire_failures: u64,
274 health_check_failures: u64,
275 pending_events: Vec<PoolEvent>,
277}
278
279impl ConnectionPoolManager {
280 pub fn new(config: CpmPoolConfig) -> Self {
284 Self {
285 config,
286 connections: HashMap::new(),
287 next_id: 1,
288 round_robin_cursor: 0,
289 total_acquires: 0,
290 total_releases: 0,
291 acquire_failures: 0,
292 health_check_failures: 0,
293 pending_events: Vec::new(),
294 }
295 }
296
297 pub fn validate_config(config: &CpmPoolConfig) -> Result<(), CpmPoolError> {
299 if config.max_connections == 0 {
300 return Err(CpmPoolError::InvalidConfiguration(
301 "max_connections must be > 0".to_string(),
302 ));
303 }
304 if config.min_connections > config.max_connections {
305 return Err(CpmPoolError::InvalidConfiguration(format!(
306 "min_connections ({}) > max_connections ({})",
307 config.min_connections, config.max_connections
308 )));
309 }
310 Ok(())
311 }
312
313 pub fn add_connection(
322 &mut self,
323 address: String,
324 tags: Vec<String>,
325 ) -> Result<u64, CpmPoolError> {
326 if self.connections.len() >= self.config.max_connections {
327 self.acquire_failures += 1;
328 return Err(CpmPoolError::PoolExhausted(self.config.max_connections));
329 }
330 let id = self.next_id;
331 self.next_id += 1;
332 let conn = PooledConnection::new(id, address, tags, 0);
333 self.connections.insert(id, conn);
334 self.pending_events.push(PoolEvent::ConnectionAdded(id));
335 Ok(id)
336 }
337
338 pub fn add_connection_at(
340 &mut self,
341 address: String,
342 tags: Vec<String>,
343 created_at: u64,
344 ) -> Result<u64, CpmPoolError> {
345 if self.connections.len() >= self.config.max_connections {
346 self.acquire_failures += 1;
347 return Err(CpmPoolError::PoolExhausted(self.config.max_connections));
348 }
349 let id = self.next_id;
350 self.next_id += 1;
351 let mut conn = PooledConnection::new(id, address, tags, created_at);
352 conn.last_used = created_at;
353 self.connections.insert(id, conn);
354 self.pending_events.push(PoolEvent::ConnectionAdded(id));
355 Ok(id)
356 }
357
358 pub fn acquire(&mut self, current_ts: u64) -> Result<u64, CpmPoolError> {
365 let chosen_id = self.select_idle(current_ts)?;
366
367 if let Some(conn) = self.connections.get_mut(&chosen_id) {
368 conn.state = ConnState::InUse {
369 borrowed_at: current_ts,
370 };
371 conn.use_count += 1;
372 conn.last_used = current_ts;
373 }
374 self.total_acquires += 1;
375 Ok(chosen_id)
376 }
377
378 pub fn release(
383 &mut self,
384 conn_id: u64,
385 current_ts: u64,
386 health_degraded: bool,
387 ) -> Result<(), CpmPoolError> {
388 let conn = self
389 .connections
390 .get_mut(&conn_id)
391 .ok_or(CpmPoolError::ConnectionNotFound(conn_id))?;
392
393 conn.state = ConnState::Idle;
394 conn.last_used = current_ts;
395 if health_degraded {
396 conn.health_score = (conn.health_score - 0.1_f64).max(0.0_f64);
397 }
398 self.total_releases += 1;
399 Ok(())
400 }
401
402 pub fn remove_connection(&mut self, conn_id: u64) -> Result<(), CpmPoolError> {
404 if self.connections.remove(&conn_id).is_none() {
405 return Err(CpmPoolError::ConnectionNotFound(conn_id));
406 }
407 self.pending_events.push(PoolEvent::ConnectionRemoved {
408 id: conn_id,
409 reason: "explicit removal".to_string(),
410 });
411 Ok(())
412 }
413
414 pub fn health_check(
424 &mut self,
425 conn_id: u64,
426 score: f64,
427 current_ts: u64,
428 ) -> Result<PoolEvent, CpmPoolError> {
429 let conn = self
430 .connections
431 .get_mut(&conn_id)
432 .ok_or(CpmPoolError::ConnectionNotFound(conn_id))?;
433
434 conn.health_score = score.clamp(0.0, 1.0);
435 conn.last_used = current_ts;
436
437 if score < 0.3 {
438 self.health_check_failures += 1;
439 let event = PoolEvent::HealthCheckFailed { id: conn_id, score };
440 self.pending_events.push(event.clone());
441 Ok(event)
442 } else {
443 Ok(PoolEvent::ConnectionAdded(conn_id))
444 }
445 }
446
447 pub fn run_maintenance(&mut self, current_ts: u64) -> Vec<PoolEvent> {
456 let idle_timeout = self.config.idle_timeout_us;
457 let max_lifetime = self.config.max_lifetime_us;
458
459 let mut to_evict: Vec<(u64, String)> = Vec::new();
461 for (id, conn) in &self.connections {
462 if conn.state == ConnState::Closed {
463 to_evict.push((*id, "already closed".to_string()));
464 continue;
465 }
466 if matches!(conn.state, ConnState::Idle | ConnState::HealthChecking) {
468 let age = current_ts.saturating_sub(conn.created_at);
469 if age >= max_lifetime {
470 to_evict.push((*id, "max lifetime exceeded".to_string()));
471 continue;
472 }
473 let idle_time = current_ts.saturating_sub(conn.last_used);
475 if idle_time >= idle_timeout {
476 to_evict.push((*id, "idle timeout exceeded".to_string()));
477 }
478 }
479 }
480
481 let mut events = Vec::with_capacity(to_evict.len());
482 for (id, reason) in to_evict {
483 self.connections.remove(&id);
484 let ev = PoolEvent::ConnectionRemoved { id, reason };
485 self.pending_events.push(ev.clone());
486 events.push(ev);
487 }
488 events
489 }
490
491 pub fn resize(&mut self, new_max: usize) -> Result<(), CpmPoolError> {
499 if new_max == 0 {
500 return Err(CpmPoolError::InvalidConfiguration(
501 "new_max must be > 0".to_string(),
502 ));
503 }
504 let old_max = self.config.max_connections;
505 self.config.max_connections = new_max;
506
507 if new_max < old_max {
508 let excess = self.connections.len().saturating_sub(new_max);
510 if excess > 0 {
511 let idle_ids: Vec<u64> = self
512 .connections
513 .values()
514 .filter(|c| c.state == ConnState::Idle)
515 .map(|c| c.id)
516 .take(excess)
517 .collect();
518 for id in idle_ids {
519 self.connections.remove(&id);
520 self.pending_events.push(PoolEvent::ConnectionRemoved {
521 id,
522 reason: "pool resize shrink".to_string(),
523 });
524 }
525 }
526 }
527 Ok(())
528 }
529
530 pub fn drain(&mut self) -> Vec<u64> {
536 let mut ids: Vec<u64> = self.connections.keys().copied().collect();
537 ids.sort_unstable(); for &id in &ids {
540 self.pending_events.push(PoolEvent::ConnectionRemoved {
541 id,
542 reason: "pool drain".to_string(),
543 });
544 }
545 self.connections.clear();
546 self.pending_events.push(PoolEvent::PoolDrained);
547 ids
548 }
549
550 pub fn connections_with_tag(&self, tag: &str) -> Vec<&PooledConnection> {
556 self.connections
557 .values()
558 .filter(|c| c.tags.iter().any(|t| t == tag))
559 .collect()
560 }
561
562 pub fn stats(&self) -> CpmPoolStats {
564 let total = self.connections.len();
565 let idle = self
566 .connections
567 .values()
568 .filter(|c| c.state == ConnState::Idle)
569 .count();
570 let in_use = self
571 .connections
572 .values()
573 .filter(|c| matches!(c.state, ConnState::InUse { .. }))
574 .count();
575
576 let avg_health = if total == 0 {
577 f64::NAN
578 } else {
579 let sum: f64 = self.connections.values().map(|c| c.health_score).sum();
580 sum / total as f64
581 };
582
583 CpmPoolStats {
584 total_connections: total,
585 idle_connections: idle,
586 in_use_connections: in_use,
587 total_acquires: self.total_acquires,
588 total_releases: self.total_releases,
589 acquire_failures: self.acquire_failures,
590 health_check_failures: self.health_check_failures,
591 avg_health_score: avg_health,
592 }
593 }
594
595 pub fn drain_events(&mut self) -> Vec<PoolEvent> {
597 std::mem::take(&mut self.pending_events)
598 }
599
600 pub fn connections(&self) -> &HashMap<u64, PooledConnection> {
602 &self.connections
603 }
604
605 pub fn config(&self) -> &CpmPoolConfig {
607 &self.config
608 }
609
610 fn select_idle(&mut self, _current_ts: u64) -> Result<u64, CpmPoolError> {
615 let idle: Vec<(u64, u64, u64, f64)> = self
617 .connections
618 .values()
619 .filter(|c| c.state == ConnState::Idle)
620 .map(|c| (c.id, c.created_at, c.use_count, c.health_score))
621 .collect();
622
623 if idle.is_empty() {
624 self.acquire_failures += 1;
625 if self.connections.len() >= self.config.max_connections {
626 return Err(CpmPoolError::PoolExhausted(self.config.max_connections));
627 }
628 return Err(CpmPoolError::AcquireTimeout(self.config.acquire_timeout_us));
630 }
631
632 let chosen_id = match self.config.policy {
633 AcquirePolicy::Fifo => {
634 idle.iter()
636 .min_by_key(|&&(_, created_at, _, _)| created_at)
637 .map(|&(id, _, _, _)| id)
638 .expect("idle is non-empty")
639 }
640 AcquirePolicy::Lifo => {
641 idle.iter()
643 .max_by_key(|&&(_, created_at, _, _)| created_at)
644 .map(|&(id, _, _, _)| id)
645 .expect("idle is non-empty")
646 }
647 AcquirePolicy::LeastUsed => idle
648 .iter()
649 .min_by_key(|&&(_, _, use_count, _)| use_count)
650 .map(|&(id, _, _, _)| id)
651 .expect("idle is non-empty"),
652 AcquirePolicy::HealthBest => {
653 idle.iter()
655 .max_by(|a, b| {
656 a.3.partial_cmp(&b.3)
657 .unwrap_or(std::cmp::Ordering::Equal)
658 .then_with(|| a.0.cmp(&b.0))
659 })
660 .map(|&(id, _, _, _)| id)
661 .expect("idle is non-empty")
662 }
663 AcquirePolicy::RoundRobin => {
664 let mut sorted: Vec<u64> = idle.iter().map(|&(id, _, _, _)| id).collect();
666 sorted.sort_unstable();
667 let idx = self.round_robin_cursor % sorted.len();
668 self.round_robin_cursor = self.round_robin_cursor.wrapping_add(1);
669 sorted[idx]
670 }
671 };
672
673 Ok(chosen_id)
674 }
675}
676
677#[cfg(test)]
682mod tests {
683 use super::*;
684
685 fn default_config() -> CpmPoolConfig {
690 CpmPoolConfig {
691 min_connections: 1,
692 max_connections: 10,
693 idle_timeout_us: 100_000,
694 max_lifetime_us: 1_000_000,
695 health_check_interval_us: 50_000,
696 acquire_timeout_us: 5_000,
697 policy: AcquirePolicy::Fifo,
698 }
699 }
700
701 fn make_pool() -> ConnectionPoolManager {
702 ConnectionPoolManager::new(default_config())
703 }
704
705 fn populate(pool: &mut ConnectionPoolManager, n: usize) -> Vec<u64> {
706 (0..n)
707 .map(|i| {
708 pool.add_connection(format!("127.0.0.1:{}", 9000 + i), vec![])
709 .expect("add_connection failed")
710 })
711 .collect()
712 }
713
714 #[test]
719 fn test_add_connection_basic() {
720 let mut pool = make_pool();
721 let id = pool
722 .add_connection("127.0.0.1:9000".to_string(), vec![])
723 .expect("test: add_connection should succeed");
724 assert_eq!(id, 1);
725 assert_eq!(pool.connections().len(), 1);
726 }
727
728 #[test]
729 fn test_add_connection_increments_id() {
730 let mut pool = make_pool();
731 let id1 = pool
732 .add_connection("a".to_string(), vec![])
733 .expect("test: add_connection a should succeed");
734 let id2 = pool
735 .add_connection("b".to_string(), vec![])
736 .expect("test: add_connection b should succeed");
737 assert!(id2 > id1);
738 }
739
740 #[test]
741 fn test_add_connection_emits_event() {
742 let mut pool = make_pool();
743 let id = pool
744 .add_connection("x".to_string(), vec![])
745 .expect("test: add_connection should succeed");
746 let events = pool.drain_events();
747 assert!(events
748 .iter()
749 .any(|e| matches!(e, PoolEvent::ConnectionAdded(i) if *i == id)));
750 }
751
752 #[test]
753 fn test_add_connection_at_capacity_fails() {
754 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
755 max_connections: 2,
756 ..default_config()
757 });
758 pool.add_connection("a".to_string(), vec![])
759 .expect("test: first add_connection should succeed");
760 pool.add_connection("b".to_string(), vec![])
761 .expect("test: second add_connection should succeed");
762 let err = pool.add_connection("c".to_string(), vec![]).unwrap_err();
763 assert!(matches!(err, CpmPoolError::PoolExhausted(_)));
764 }
765
766 #[test]
767 fn test_add_connection_new_state_is_idle() {
768 let mut pool = make_pool();
769 let id = pool
770 .add_connection("h".to_string(), vec![])
771 .expect("test: add_connection should succeed");
772 let conn = pool
773 .connections()
774 .get(&id)
775 .expect("test: connection should exist after add");
776 assert_eq!(conn.state, ConnState::Idle);
777 }
778
779 #[test]
780 fn test_add_connection_with_tags() {
781 let mut pool = make_pool();
782 let tags = vec!["region:eu".to_string(), "tier:1".to_string()];
783 let id = pool
784 .add_connection("t".to_string(), tags.clone())
785 .expect("test: add_connection with tags should succeed");
786 let conn = pool
787 .connections()
788 .get(&id)
789 .expect("test: connection should exist after add");
790 assert_eq!(conn.tags, tags);
791 }
792
793 #[test]
798 fn test_acquire_idle_connection() {
799 let mut pool = make_pool();
800 let id = pool
801 .add_connection("a".to_string(), vec![])
802 .expect("test: add_connection should succeed");
803 let acquired = pool
804 .acquire(100)
805 .expect("test: acquire should succeed with idle connection");
806 assert_eq!(acquired, id);
807 let conn = pool
808 .connections()
809 .get(&id)
810 .expect("test: connection should exist");
811 assert!(matches!(conn.state, ConnState::InUse { .. }));
812 }
813
814 #[test]
815 fn test_acquire_increments_use_count() {
816 let mut pool = make_pool();
817 let id = pool
818 .add_connection("a".to_string(), vec![])
819 .expect("test: add_connection should succeed");
820 pool.acquire(100)
821 .expect("test: first acquire should succeed");
822 pool.release(id, 200, false)
823 .expect("test: release should succeed");
824 pool.acquire(300)
825 .expect("test: second acquire should succeed");
826 let conn = pool
827 .connections()
828 .get(&id)
829 .expect("test: connection should exist");
830 assert_eq!(conn.use_count, 2);
831 }
832
833 #[test]
834 fn test_acquire_updates_last_used() {
835 let mut pool = make_pool();
836 let id = pool
837 .add_connection("a".to_string(), vec![])
838 .expect("test: add_connection should succeed");
839 pool.acquire(999).expect("test: acquire should succeed");
840 let conn = pool
841 .connections()
842 .get(&id)
843 .expect("test: connection should exist");
844 assert_eq!(conn.last_used, 999);
845 }
846
847 #[test]
848 fn test_acquire_no_idle_pool_full() {
849 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
850 max_connections: 1,
851 ..default_config()
852 });
853 let id = pool
854 .add_connection("a".to_string(), vec![])
855 .expect("test: add_connection should succeed");
856 pool.acquire(100)
857 .expect("test: first acquire should succeed"); let err = pool.acquire(200).unwrap_err();
859 assert!(matches!(err, CpmPoolError::PoolExhausted(_)));
860 pool.release(id, 300, false)
862 .expect("test: release should succeed");
863 assert!(pool.acquire(400).is_ok());
864 }
865
866 #[test]
867 fn test_release_transitions_to_idle() {
868 let mut pool = make_pool();
869 let id = pool
870 .add_connection("a".to_string(), vec![])
871 .expect("test: add_connection should succeed");
872 pool.acquire(100).expect("test: acquire should succeed");
873 pool.release(id, 200, false)
874 .expect("test: release should succeed");
875 let conn = pool
876 .connections()
877 .get(&id)
878 .expect("test: connection should exist");
879 assert_eq!(conn.state, ConnState::Idle);
880 }
881
882 #[test]
883 fn test_release_health_degraded_reduces_score() {
884 let mut pool = make_pool();
885 let id = pool
886 .add_connection("a".to_string(), vec![])
887 .expect("test: add_connection should succeed");
888 pool.acquire(100).expect("test: acquire should succeed");
889 pool.release(id, 200, true)
890 .expect("test: release with health degraded should succeed");
891 let conn = pool
892 .connections()
893 .get(&id)
894 .expect("test: connection should exist");
895 assert!((conn.health_score - 0.9).abs() < 1e-9);
896 }
897
898 #[test]
899 fn test_release_health_floor_at_zero() {
900 let mut pool = make_pool();
901 let id = pool
902 .add_connection("a".to_string(), vec![])
903 .expect("test: add_connection should succeed");
904 for _ in 0..11 {
906 if pool
907 .connections()
908 .get(&id)
909 .expect("test: connection should exist")
910 .is_idle()
911 {
912 pool.acquire(1).expect("test: acquire should succeed");
913 }
914 pool.release(id, 2, true)
915 .expect("test: release should succeed");
916 }
917 let conn = pool
918 .connections()
919 .get(&id)
920 .expect("test: connection should exist");
921 assert_eq!(conn.health_score, 0.0);
922 }
923
924 #[test]
925 fn test_release_unknown_id_error() {
926 let mut pool = make_pool();
927 let err = pool.release(999, 100, false).unwrap_err();
928 assert!(matches!(err, CpmPoolError::ConnectionNotFound(999)));
929 }
930
931 #[test]
936 fn test_policy_fifo() {
937 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
938 policy: AcquirePolicy::Fifo,
939 ..default_config()
940 });
941 let id1 = pool
942 .add_connection_at("a".to_string(), vec![], 100)
943 .expect("test: add_connection_at a should succeed");
944 let id2 = pool
945 .add_connection_at("b".to_string(), vec![], 200)
946 .expect("test: add_connection_at b should succeed");
947 let acquired = pool
948 .acquire(300)
949 .expect("test: first acquire should succeed");
950 assert_eq!(acquired, id1, "FIFO should pick oldest (id1)");
951 pool.release(id1, 400, false)
952 .expect("test: release should succeed");
953 let acquired2 = pool
954 .acquire(500)
955 .expect("test: second acquire should succeed");
956 assert_eq!(acquired2, id1, "After release, id1 is oldest again");
957 let _ = acquired2;
958 let _ = id2;
959 }
960
961 #[test]
966 fn test_policy_lifo() {
967 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
968 policy: AcquirePolicy::Lifo,
969 ..default_config()
970 });
971 let _id1 = pool
972 .add_connection_at("a".to_string(), vec![], 100)
973 .expect("test: add_connection_at a should succeed");
974 let id2 = pool
975 .add_connection_at("b".to_string(), vec![], 200)
976 .expect("test: add_connection_at b should succeed");
977 let acquired = pool.acquire(300).expect("test: acquire should succeed");
978 assert_eq!(acquired, id2, "LIFO should pick newest (id2)");
979 }
980
981 #[test]
986 fn test_policy_least_used() {
987 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
988 policy: AcquirePolicy::LeastUsed,
989 ..default_config()
990 });
991 let id1 = pool
992 .add_connection("a".to_string(), vec![])
993 .expect("test: add_connection a should succeed");
994 let id2 = pool
995 .add_connection("b".to_string(), vec![])
996 .expect("test: add_connection b should succeed");
997 pool.connections
999 .get_mut(&id1)
1000 .expect("test: connection id1 should exist")
1001 .use_count = 5;
1002 let acquired = pool.acquire(5).expect("test: acquire should succeed");
1004 assert_eq!(acquired, id2);
1005 }
1006
1007 #[test]
1012 fn test_policy_health_best() {
1013 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1014 policy: AcquirePolicy::HealthBest,
1015 ..default_config()
1016 });
1017 let id1 = pool
1018 .add_connection("a".to_string(), vec![])
1019 .expect("test: add_connection a should succeed");
1020 let id2 = pool
1021 .add_connection("b".to_string(), vec![])
1022 .expect("test: add_connection b should succeed");
1023 pool.health_check(id1, 0.4, 0)
1025 .expect("test: health_check id1 should succeed");
1026 pool.health_check(id2, 0.9, 0)
1027 .expect("test: health_check id2 should succeed");
1028 let acquired = pool.acquire(1).expect("test: acquire should succeed");
1029 assert_eq!(acquired, id2, "HealthBest should pick id2 (higher score)");
1030 }
1031
1032 #[test]
1037 fn test_policy_round_robin() {
1038 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1039 policy: AcquirePolicy::RoundRobin,
1040 ..default_config()
1041 });
1042 let id1 = pool
1043 .add_connection("a".to_string(), vec![])
1044 .expect("test: add_connection a should succeed");
1045 let id2 = pool
1046 .add_connection("b".to_string(), vec![])
1047 .expect("test: add_connection b should succeed");
1048 let id3 = pool
1049 .add_connection("c".to_string(), vec![])
1050 .expect("test: add_connection c should succeed");
1051
1052 let a = pool.acquire(1).expect("test: first acquire should succeed");
1054 assert_eq!(a, id1);
1055 pool.release(id1, 2, false)
1056 .expect("test: release id1 should succeed");
1057
1058 let b = pool
1060 .acquire(3)
1061 .expect("test: second acquire should succeed");
1062 assert_eq!(b, id2);
1063 pool.release(id2, 4, false)
1064 .expect("test: release id2 should succeed");
1065
1066 let c = pool.acquire(5).expect("test: third acquire should succeed");
1068 assert_eq!(c, id3);
1069 let _ = c;
1070 }
1071
1072 #[test]
1077 fn test_health_check_update() {
1078 let mut pool = make_pool();
1079 let id = pool
1080 .add_connection("a".to_string(), vec![])
1081 .expect("test: add_connection should succeed");
1082 pool.health_check(id, 0.75, 100)
1083 .expect("test: health_check should succeed");
1084 let conn = pool
1085 .connections()
1086 .get(&id)
1087 .expect("test: connection should exist");
1088 assert!((conn.health_score - 0.75).abs() < 1e-9);
1089 }
1090
1091 #[test]
1092 fn test_health_check_clamps_above_one() {
1093 let mut pool = make_pool();
1094 let id = pool
1095 .add_connection("a".to_string(), vec![])
1096 .expect("test: add_connection should succeed");
1097 pool.health_check(id, 1.5, 0)
1098 .expect("test: health_check with out-of-range score should succeed");
1099 assert!(
1100 (pool
1101 .connections()
1102 .get(&id)
1103 .expect("test: connection should exist")
1104 .health_score
1105 - 1.0)
1106 .abs()
1107 < 1e-9
1108 );
1109 }
1110
1111 #[test]
1112 fn test_health_check_clamps_below_zero() {
1113 let mut pool = make_pool();
1114 let id = pool
1115 .add_connection("a".to_string(), vec![])
1116 .expect("test: add_connection should succeed");
1117 pool.health_check(id, -0.5, 0)
1118 .expect("test: health_check with negative score should succeed");
1119 assert_eq!(
1120 pool.connections()
1121 .get(&id)
1122 .expect("test: connection should exist")
1123 .health_score,
1124 0.0
1125 );
1126 }
1127
1128 #[test]
1129 fn test_health_check_failed_event_below_threshold() {
1130 let mut pool = make_pool();
1131 let id = pool
1132 .add_connection("a".to_string(), vec![])
1133 .expect("test: add_connection should succeed");
1134 let ev = pool
1135 .health_check(id, 0.2, 0)
1136 .expect("test: health_check should succeed");
1137 assert!(matches!(ev, PoolEvent::HealthCheckFailed { id: i, .. } if i == id));
1138 }
1139
1140 #[test]
1141 fn test_health_check_no_failure_at_threshold() {
1142 let mut pool = make_pool();
1143 let id = pool
1144 .add_connection("a".to_string(), vec![])
1145 .expect("test: add_connection should succeed");
1146 let ev = pool
1148 .health_check(id, 0.3, 0)
1149 .expect("test: health_check at threshold should succeed");
1150 assert!(!matches!(ev, PoolEvent::HealthCheckFailed { .. }));
1151 }
1152
1153 #[test]
1154 fn test_health_check_failure_increments_counter() {
1155 let mut pool = make_pool();
1156 let id = pool
1157 .add_connection("a".to_string(), vec![])
1158 .expect("test: add_connection should succeed");
1159 pool.health_check(id, 0.1, 0)
1160 .expect("test: health_check below threshold should succeed");
1161 assert_eq!(pool.stats().health_check_failures, 1);
1162 }
1163
1164 #[test]
1165 fn test_health_check_not_found_error() {
1166 let mut pool = make_pool();
1167 let err = pool.health_check(42, 0.5, 0).unwrap_err();
1168 assert!(matches!(err, CpmPoolError::ConnectionNotFound(42)));
1169 }
1170
1171 #[test]
1176 fn test_maintenance_idle_timeout() {
1177 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1178 idle_timeout_us: 1_000,
1179 max_lifetime_us: 999_999_999,
1180 ..default_config()
1181 });
1182 let id = pool
1183 .add_connection_at("a".to_string(), vec![], 0)
1184 .expect("test: add_connection_at should succeed");
1185 let events = pool.run_maintenance(2_000);
1187 assert!(!events.is_empty(), "should evict idle-timeout connection");
1188 assert!(events.iter().any(|e| matches!(
1189 e,
1190 PoolEvent::ConnectionRemoved { id: i, .. } if *i == id
1191 )));
1192 assert!(!pool.connections().contains_key(&id));
1193 }
1194
1195 #[test]
1196 fn test_maintenance_skips_in_use() {
1197 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1198 idle_timeout_us: 1_000,
1199 max_lifetime_us: 999_999_999,
1200 ..default_config()
1201 });
1202 let id = pool
1203 .add_connection_at("a".to_string(), vec![], 0)
1204 .expect("test: add_connection_at should succeed");
1205 pool.acquire(0).expect("test: acquire should succeed");
1206 let events = pool.run_maintenance(2_000);
1207 assert!(events.is_empty(), "InUse connections should not be evicted");
1208 assert!(pool.connections().contains_key(&id));
1209 }
1210
1211 #[test]
1216 fn test_maintenance_max_lifetime() {
1217 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1218 idle_timeout_us: 999_999_999,
1219 max_lifetime_us: 500,
1220 ..default_config()
1221 });
1222 let id = pool
1223 .add_connection_at("a".to_string(), vec![], 0)
1224 .expect("test: add_connection_at should succeed");
1225 let events = pool.run_maintenance(600);
1227 assert!(!events.is_empty());
1228 assert!(events.iter().any(|e| matches!(
1229 e,
1230 PoolEvent::ConnectionRemoved { id: i, .. } if *i == id
1231 )));
1232 }
1233
1234 #[test]
1235 fn test_maintenance_no_eviction_young_connection() {
1236 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1237 idle_timeout_us: 10_000,
1238 max_lifetime_us: 50_000,
1239 ..default_config()
1240 });
1241 pool.add_connection_at("a".to_string(), vec![], 1_000)
1242 .expect("test: add_connection_at should succeed");
1243 let events = pool.run_maintenance(2_000); assert!(events.is_empty());
1245 }
1246
1247 #[test]
1248 fn test_maintenance_multiple_evictions() {
1249 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1250 idle_timeout_us: 100,
1251 max_lifetime_us: 999_999_999,
1252 ..default_config()
1253 });
1254 pool.add_connection_at("a".to_string(), vec![], 0)
1255 .expect("test: add_connection_at a should succeed");
1256 pool.add_connection_at("b".to_string(), vec![], 0)
1257 .expect("test: add_connection_at b should succeed");
1258 pool.add_connection_at("c".to_string(), vec![], 0)
1259 .expect("test: add_connection_at c should succeed");
1260 let events = pool.run_maintenance(200);
1261 assert_eq!(events.len(), 3);
1262 }
1263
1264 #[test]
1269 fn test_resize_grow() {
1270 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1271 max_connections: 3,
1272 ..default_config()
1273 });
1274 populate(&mut pool, 3);
1275 pool.resize(10).expect("test: resize grow should succeed");
1276 assert_eq!(pool.config().max_connections, 10);
1277 assert!(pool.add_connection("new".to_string(), vec![]).is_ok());
1279 }
1280
1281 #[test]
1282 fn test_resize_shrink_evicts_idle() {
1283 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1284 max_connections: 5,
1285 ..default_config()
1286 });
1287 populate(&mut pool, 5);
1288 pool.resize(3).expect("test: resize shrink should succeed");
1289 assert!(pool.connections().len() <= 3);
1290 }
1291
1292 #[test]
1293 fn test_resize_zero_fails() {
1294 let mut pool = make_pool();
1295 let err = pool.resize(0).unwrap_err();
1296 assert!(matches!(err, CpmPoolError::InvalidConfiguration(_)));
1297 }
1298
1299 #[test]
1300 fn test_resize_same_size_noop() {
1301 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1302 max_connections: 4,
1303 ..default_config()
1304 });
1305 populate(&mut pool, 4);
1306 pool.resize(4)
1307 .expect("test: resize same size should succeed");
1308 assert_eq!(pool.connections().len(), 4);
1309 }
1310
1311 #[test]
1316 fn test_drain_clears_all_connections() {
1317 let mut pool = make_pool();
1318 populate(&mut pool, 5);
1319 let ids = pool.drain();
1320 assert_eq!(ids.len(), 5);
1321 assert!(pool.connections().is_empty());
1322 }
1323
1324 #[test]
1325 fn test_drain_emits_pool_drained_event() {
1326 let mut pool = make_pool();
1327 populate(&mut pool, 2);
1328 pool.drain();
1329 let events = pool.drain_events();
1330 assert!(events.iter().any(|e| matches!(e, PoolEvent::PoolDrained)));
1331 }
1332
1333 #[test]
1334 fn test_drain_ids_sorted_ascending() {
1335 let mut pool = make_pool();
1336 let added = populate(&mut pool, 4);
1337 let drained = pool.drain();
1338 assert_eq!(drained, added);
1339 }
1340
1341 #[test]
1342 fn test_drain_empty_pool() {
1343 let mut pool = make_pool();
1344 let ids = pool.drain();
1345 assert!(ids.is_empty());
1346 let events = pool.drain_events();
1347 assert!(events.iter().any(|e| matches!(e, PoolEvent::PoolDrained)));
1348 }
1349
1350 #[test]
1355 fn test_connections_with_tag_basic() {
1356 let mut pool = make_pool();
1357 pool.add_connection("a".to_string(), vec!["eu".to_string()])
1358 .expect("test: add_connection a with eu tag should succeed");
1359 pool.add_connection("b".to_string(), vec!["us".to_string()])
1360 .expect("test: add_connection b with us tag should succeed");
1361 pool.add_connection("c".to_string(), vec!["eu".to_string(), "tier1".to_string()])
1362 .expect("test: add_connection c with tags should succeed");
1363 let eu = pool.connections_with_tag("eu");
1364 assert_eq!(eu.len(), 2);
1365 }
1366
1367 #[test]
1368 fn test_connections_with_tag_no_match() {
1369 let mut pool = make_pool();
1370 pool.add_connection("a".to_string(), vec!["us".to_string()])
1371 .expect("test: add_connection should succeed");
1372 let result = pool.connections_with_tag("eu");
1373 assert!(result.is_empty());
1374 }
1375
1376 #[test]
1377 fn test_connections_with_tag_multiple_tags() {
1378 let mut pool = make_pool();
1379 let id = pool
1380 .add_connection(
1381 "a".to_string(),
1382 vec!["x".to_string(), "y".to_string(), "z".to_string()],
1383 )
1384 .expect("test: add_connection with multiple tags should succeed");
1385 assert_eq!(pool.connections_with_tag("x").len(), 1);
1386 assert_eq!(pool.connections_with_tag("y").len(), 1);
1387 assert_eq!(pool.connections_with_tag("z").len(), 1);
1388 let _ = id;
1389 }
1390
1391 #[test]
1396 fn test_stats_empty_pool() {
1397 let pool = make_pool();
1398 let s = pool.stats();
1399 assert_eq!(s.total_connections, 0);
1400 assert_eq!(s.idle_connections, 0);
1401 assert_eq!(s.in_use_connections, 0);
1402 assert!(s.avg_health_score.is_nan());
1403 }
1404
1405 #[test]
1406 fn test_stats_after_add() {
1407 let mut pool = make_pool();
1408 populate(&mut pool, 3);
1409 let s = pool.stats();
1410 assert_eq!(s.total_connections, 3);
1411 assert_eq!(s.idle_connections, 3);
1412 assert_eq!(s.in_use_connections, 0);
1413 }
1414
1415 #[test]
1416 fn test_stats_after_acquire() {
1417 let mut pool = make_pool();
1418 populate(&mut pool, 3);
1419 pool.acquire(1).expect("test: acquire should succeed");
1420 let s = pool.stats();
1421 assert_eq!(s.idle_connections, 2);
1422 assert_eq!(s.in_use_connections, 1);
1423 assert_eq!(s.total_acquires, 1);
1424 }
1425
1426 #[test]
1427 fn test_stats_avg_health() {
1428 let mut pool = make_pool();
1429 let id1 = pool
1430 .add_connection("a".to_string(), vec![])
1431 .expect("test: add_connection a should succeed");
1432 let id2 = pool
1433 .add_connection("b".to_string(), vec![])
1434 .expect("test: add_connection b should succeed");
1435 pool.health_check(id1, 0.6, 0)
1436 .expect("test: health_check id1 should succeed");
1437 pool.health_check(id2, 0.4, 0)
1438 .expect("test: health_check id2 should succeed");
1439 let s = pool.stats();
1440 assert!((s.avg_health_score - 0.5).abs() < 1e-9);
1441 }
1442
1443 #[test]
1444 fn test_stats_acquire_failures() {
1445 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1446 max_connections: 1,
1447 ..default_config()
1448 });
1449 let id = pool
1450 .add_connection("a".to_string(), vec![])
1451 .expect("test: add_connection should succeed");
1452 pool.acquire(1).expect("test: acquire should succeed");
1453 let _ = pool.acquire(2); let s = pool.stats();
1455 assert_eq!(s.acquire_failures, 1);
1456 pool.release(id, 3, false)
1457 .expect("test: release should succeed");
1458 }
1459
1460 #[test]
1465 fn test_remove_connection_ok() {
1466 let mut pool = make_pool();
1467 let id = pool
1468 .add_connection("a".to_string(), vec![])
1469 .expect("test: add_connection should succeed");
1470 pool.remove_connection(id)
1471 .expect("test: remove_connection should succeed");
1472 assert!(!pool.connections().contains_key(&id));
1473 }
1474
1475 #[test]
1476 fn test_remove_connection_not_found() {
1477 let mut pool = make_pool();
1478 let err = pool.remove_connection(42).unwrap_err();
1479 assert!(matches!(err, CpmPoolError::ConnectionNotFound(42)));
1480 }
1481
1482 #[test]
1483 fn test_acquire_pool_empty_timeout() {
1484 let mut pool = make_pool();
1485 let err = pool.acquire(100).unwrap_err();
1487 assert!(matches!(err, CpmPoolError::AcquireTimeout(_)));
1488 }
1489
1490 #[test]
1491 fn test_invalid_config_zero_max() {
1492 let result = ConnectionPoolManager::validate_config(&CpmPoolConfig {
1493 max_connections: 0,
1494 ..default_config()
1495 });
1496 assert!(result.is_err());
1497 }
1498
1499 #[test]
1500 fn test_invalid_config_min_gt_max() {
1501 let result = ConnectionPoolManager::validate_config(&CpmPoolConfig {
1502 min_connections: 5,
1503 max_connections: 3,
1504 ..default_config()
1505 });
1506 assert!(result.is_err());
1507 }
1508
1509 #[test]
1514 fn test_drain_events_clears_queue() {
1515 let mut pool = make_pool();
1516 pool.add_connection("a".to_string(), vec![])
1517 .expect("test: add_connection should succeed");
1518 let ev1 = pool.drain_events();
1519 assert!(!ev1.is_empty());
1520 let ev2 = pool.drain_events();
1521 assert!(ev2.is_empty());
1522 }
1523
1524 #[test]
1525 fn test_drain_events_removed_event() {
1526 let mut pool = make_pool();
1527 let id = pool
1528 .add_connection("a".to_string(), vec![])
1529 .expect("test: add_connection should succeed");
1530 pool.drain_events(); pool.remove_connection(id)
1532 .expect("test: remove_connection should succeed");
1533 let events = pool.drain_events();
1534 assert!(events
1535 .iter()
1536 .any(|e| matches!(e, PoolEvent::ConnectionRemoved { id: i, .. } if *i == id)));
1537 }
1538
1539 #[test]
1544 fn test_xorshift64_non_zero() {
1545 let mut state = 12345u64;
1546 let v = xorshift64(&mut state);
1547 assert_ne!(v, 0);
1548 }
1549
1550 #[test]
1551 fn test_xorshift64_sequence_distinct() {
1552 let mut state = 1u64;
1553 let a = xorshift64(&mut state);
1554 let b = xorshift64(&mut state);
1555 assert_ne!(a, b);
1556 }
1557
1558 #[test]
1559 fn test_xorshift64_deterministic() {
1560 let mut s1 = 42u64;
1561 let mut s2 = 42u64;
1562 for _ in 0..100 {
1563 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
1564 }
1565 }
1566
1567 #[test]
1572 fn test_connstate_eq() {
1573 assert_eq!(ConnState::Idle, ConnState::Idle);
1574 assert_ne!(ConnState::Idle, ConnState::Closed);
1575 assert_eq!(
1576 ConnState::InUse { borrowed_at: 10 },
1577 ConnState::InUse { borrowed_at: 10 }
1578 );
1579 assert_ne!(
1580 ConnState::InUse { borrowed_at: 10 },
1581 ConnState::InUse { borrowed_at: 20 }
1582 );
1583 }
1584
1585 #[test]
1590 fn test_full_lifecycle() {
1591 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1592 max_connections: 3,
1593 idle_timeout_us: 10_000,
1594 max_lifetime_us: 100_000,
1595 ..default_config()
1596 });
1597 let id1 = pool
1598 .add_connection_at("a".to_string(), vec![], 0)
1599 .expect("test: add_connection_at a should succeed");
1600 let id2 = pool
1601 .add_connection_at("b".to_string(), vec![], 0)
1602 .expect("test: add_connection_at b should succeed");
1603
1604 let a1 = pool
1606 .acquire(1_000)
1607 .expect("test: first acquire should succeed");
1608 let a2 = pool
1609 .acquire(1_000)
1610 .expect("test: second acquire should succeed");
1611
1612 pool.release(a1, 2_000, true)
1614 .expect("test: release a1 with health degraded should succeed");
1615 pool.release(a2, 2_000, false)
1617 .expect("test: release a2 should succeed");
1618
1619 pool.connections
1621 .get_mut(&id1)
1622 .expect("test: connection id1 should exist")
1623 .state = ConnState::HealthChecking;
1624 pool.health_check(id1, 0.1, 3_000)
1625 .expect("test: health_check should succeed");
1626
1627 let evicted = pool.run_maintenance(15_000);
1630 assert!(!evicted.is_empty());
1631 let _ = (id1, id2);
1632 }
1633
1634 #[test]
1639 fn test_resize_shrink_stats_updated() {
1640 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1641 max_connections: 5,
1642 ..default_config()
1643 });
1644 populate(&mut pool, 5);
1645 pool.resize(2).expect("test: resize shrink should succeed");
1646 let s = pool.stats();
1647 assert!(s.total_connections <= 2);
1648 }
1649
1650 #[test]
1655 fn test_multiple_acquire_release_cycles() {
1656 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1657 max_connections: 3,
1658 ..default_config()
1659 });
1660 let ids = populate(&mut pool, 3);
1661 for cycle in 0..10_u64 {
1662 let ts_base = cycle * 1000;
1663 let acquired: Vec<u64> = ids
1664 .iter()
1665 .map(|_| {
1666 pool.acquire(ts_base)
1667 .expect("test: acquire in cycle should succeed")
1668 })
1669 .collect();
1670 for &id in &acquired {
1671 pool.release(id, ts_base + 1, false)
1672 .expect("test: release in cycle should succeed");
1673 }
1674 }
1675 let s = pool.stats();
1676 assert_eq!(s.total_acquires, 30);
1677 assert_eq!(s.total_releases, 30);
1678 assert_eq!(s.idle_connections, 3);
1679 }
1680
1681 #[test]
1686 fn test_health_check_failure_in_pending_events() {
1687 let mut pool = make_pool();
1688 let id = pool
1689 .add_connection("a".to_string(), vec![])
1690 .expect("test: add_connection should succeed");
1691 pool.drain_events();
1692 pool.health_check(id, 0.05, 0)
1693 .expect("test: health_check below threshold should succeed");
1694 let events = pool.drain_events();
1695 assert!(events
1696 .iter()
1697 .any(|e| matches!(e, PoolEvent::HealthCheckFailed { .. })));
1698 }
1699
1700 #[test]
1705 fn test_pool_exhausted_increments_failure_counter() {
1706 let mut pool = ConnectionPoolManager::new(CpmPoolConfig {
1707 max_connections: 1,
1708 ..default_config()
1709 });
1710 let id = pool
1711 .add_connection("a".to_string(), vec![])
1712 .expect("test: add_connection should succeed");
1713 pool.acquire(1).expect("test: acquire should succeed");
1714 let _ = pool.acquire(2); assert_eq!(pool.stats().acquire_failures, 1);
1716 pool.release(id, 3, false)
1717 .expect("test: release should succeed");
1718 }
1719
1720 #[test]
1725 fn test_released_connection_reacquirable() {
1726 let mut pool = make_pool();
1727 let id = pool
1728 .add_connection("a".to_string(), vec![])
1729 .expect("test: add_connection should succeed");
1730 pool.acquire(1).expect("test: acquire should succeed");
1731 pool.release(id, 2, false)
1732 .expect("test: release should succeed");
1733 let reacquired = pool.acquire(3).expect("test: reacquire should succeed");
1734 assert_eq!(reacquired, id);
1735 }
1736
1737 #[test]
1742 fn test_add_connection_at_timestamps() {
1743 let mut pool = make_pool();
1744 let id = pool
1745 .add_connection_at("a".to_string(), vec![], 9_999)
1746 .expect("test: add_connection_at should succeed");
1747 let conn = pool
1748 .connections()
1749 .get(&id)
1750 .expect("test: connection should exist");
1751 assert_eq!(conn.created_at, 9_999);
1752 assert_eq!(conn.last_used, 9_999);
1753 }
1754}