1use crate::{DistributedError, Result};
8use std::collections::VecDeque;
9use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
10use std::sync::Arc;
11use std::time::Duration;
12use tokio::sync::{Mutex, Semaphore};
13use tracing::{debug, info, warn};
14
15#[derive(Debug, Clone)]
17pub struct ConnectionPoolConfig {
18 pub endpoint: String,
20 pub max_connections: usize,
22 pub min_idle: usize,
24 pub connect_timeout: Duration,
26 pub max_retries: u32,
28 pub retry_base_delay: Duration,
30 pub retry_max_delay: Duration,
32 pub max_lifetime: Duration,
34 pub health_check_interval: Duration,
36}
37
38impl Default for ConnectionPoolConfig {
39 fn default() -> Self {
40 Self {
41 endpoint: "http://127.0.0.1:50051".to_string(),
42 max_connections: 8,
43 min_idle: 2,
44 connect_timeout: Duration::from_secs(5),
45 max_retries: 3,
46 retry_base_delay: Duration::from_millis(100),
47 retry_max_delay: Duration::from_secs(10),
48 max_lifetime: Duration::from_secs(3600),
49 health_check_interval: Duration::from_secs(30),
50 }
51 }
52}
53
54impl ConnectionPoolConfig {
55 #[must_use]
57 pub fn new(endpoint: &str) -> Self {
58 Self {
59 endpoint: endpoint.to_string(),
60 ..Default::default()
61 }
62 }
63
64 #[must_use]
66 pub fn with_max_connections(mut self, max: usize) -> Self {
67 self.max_connections = max.max(1);
68 self
69 }
70
71 #[must_use]
73 pub fn with_min_idle(mut self, min: usize) -> Self {
74 self.min_idle = min;
75 self
76 }
77
78 #[must_use]
80 pub fn with_max_retries(mut self, retries: u32) -> Self {
81 self.max_retries = retries;
82 self
83 }
84
85 #[must_use]
87 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
88 self.connect_timeout = timeout;
89 self
90 }
91
92 #[must_use]
94 pub fn with_retry_base_delay(mut self, delay: Duration) -> Self {
95 self.retry_base_delay = delay;
96 self
97 }
98
99 #[must_use]
101 pub fn with_max_lifetime(mut self, lifetime: Duration) -> Self {
102 self.max_lifetime = lifetime;
103 self
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct RetryPolicy {
110 max_retries: u32,
112 base_delay: Duration,
114 max_delay: Duration,
116}
117
118impl RetryPolicy {
119 #[must_use]
121 pub fn new(max_retries: u32, base_delay: Duration, max_delay: Duration) -> Self {
122 Self {
123 max_retries,
124 base_delay,
125 max_delay,
126 }
127 }
128
129 #[must_use]
131 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
132 let factor = 2u64.saturating_pow(attempt);
133 let delay_ms = self.base_delay.as_millis() as u64 * factor;
134 let capped = delay_ms.min(self.max_delay.as_millis() as u64);
135 Duration::from_millis(capped)
136 }
137
138 #[must_use]
140 pub fn should_retry(&self, attempt: u32) -> bool {
141 attempt < self.max_retries
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum ConnectionState {
148 Idle,
150 InUse,
152 Broken,
154 Expired,
156}
157
158#[derive(Debug)]
160#[allow(dead_code)]
161pub struct PooledConnection {
162 id: u64,
164 state: ConnectionState,
166 created_at_tick: u64,
168 last_used_tick: u64,
170 requests_served: u64,
172 endpoint: String,
174}
175
176impl PooledConnection {
177 fn new(id: u64, endpoint: &str, now_tick: u64) -> Self {
179 Self {
180 id,
181 state: ConnectionState::Idle,
182 created_at_tick: now_tick,
183 last_used_tick: now_tick,
184 requests_served: 0,
185 endpoint: endpoint.to_string(),
186 }
187 }
188
189 #[must_use]
191 pub fn id(&self) -> u64 {
192 self.id
193 }
194
195 #[must_use]
197 pub fn state(&self) -> ConnectionState {
198 self.state
199 }
200
201 fn checkout(&mut self, now_tick: u64) {
203 self.state = ConnectionState::InUse;
204 self.last_used_tick = now_tick;
205 }
206
207 fn checkin(&mut self, now_tick: u64) {
209 self.state = ConnectionState::Idle;
210 self.last_used_tick = now_tick;
211 self.requests_served += 1;
212 }
213
214 fn mark_broken(&mut self) {
216 self.state = ConnectionState::Broken;
217 }
218
219 fn is_expired(&self, now_tick: u64, max_lifetime_ms: u64) -> bool {
221 now_tick.saturating_sub(self.created_at_tick) >= max_lifetime_ms
222 }
223}
224
225#[derive(Debug, Clone, Default)]
227pub struct PoolStats {
228 pub total_created: u64,
230 pub total_closed: u64,
232 pub total_checkouts: u64,
234 pub total_failures: u64,
236 pub current_size: usize,
238 pub current_idle: usize,
240 pub current_in_use: usize,
242}
243
244pub struct ConnectionPool {
249 config: ConnectionPoolConfig,
250 connections: Arc<Mutex<VecDeque<PooledConnection>>>,
252 #[allow(dead_code)]
254 checkout_semaphore: Arc<Semaphore>,
255 current_tick: Arc<AtomicU64>,
257 next_id: Arc<AtomicU64>,
259 stats: Arc<Mutex<PoolStats>>,
261 is_shutdown: Arc<AtomicBool>,
263 retry_policy: RetryPolicy,
265}
266
267impl ConnectionPool {
268 #[must_use]
270 pub fn new(config: ConnectionPoolConfig) -> Self {
271 let semaphore_permits = config.max_connections;
272 let retry_policy = RetryPolicy::new(
273 config.max_retries,
274 config.retry_base_delay,
275 config.retry_max_delay,
276 );
277
278 Self {
279 config,
280 connections: Arc::new(Mutex::new(VecDeque::new())),
281 checkout_semaphore: Arc::new(Semaphore::new(semaphore_permits)),
282 current_tick: Arc::new(AtomicU64::new(0)),
283 next_id: Arc::new(AtomicU64::new(1)),
284 stats: Arc::new(Mutex::new(PoolStats::default())),
285 is_shutdown: Arc::new(AtomicBool::new(false)),
286 retry_policy,
287 }
288 }
289
290 pub async fn initialize(&self) -> Result<()> {
292 if self.is_shutdown.load(Ordering::Relaxed) {
293 return Err(DistributedError::Worker("Pool is shut down".to_string()));
294 }
295
296 info!(
297 "Initializing connection pool: endpoint={}, max={}, min_idle={}",
298 self.config.endpoint, self.config.max_connections, self.config.min_idle
299 );
300
301 let mut conns = self.connections.lock().await;
302 let mut stats = self.stats.lock().await;
303 let now = self.current_tick.load(Ordering::Relaxed);
304
305 for _ in 0..self.config.min_idle {
306 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
307 let conn = PooledConnection::new(id, &self.config.endpoint, now);
308 conns.push_back(conn);
309 stats.total_created += 1;
310 stats.current_size += 1;
311 stats.current_idle += 1;
312 }
313
314 debug!("Pool initialized with {} connections", conns.len());
315 Ok(())
316 }
317
318 pub async fn checkout(&self) -> Result<u64> {
324 if self.is_shutdown.load(Ordering::Relaxed) {
325 return Err(DistributedError::Worker("Pool is shut down".to_string()));
326 }
327
328 let now = self.current_tick.load(Ordering::Relaxed);
329 let max_lifetime_ms = self.config.max_lifetime.as_millis() as u64;
330
331 let mut conns = self.connections.lock().await;
332 let mut stats = self.stats.lock().await;
333
334 for conn in conns.iter_mut() {
336 if conn.state == ConnectionState::Idle && !conn.is_expired(now, max_lifetime_ms) {
337 conn.checkout(now);
338 stats.total_checkouts += 1;
339 stats.current_idle = stats.current_idle.saturating_sub(1);
340 stats.current_in_use += 1;
341 debug!("Checked out connection {}", conn.id);
342 return Ok(conn.id);
343 }
344 }
345
346 let before_len = conns.len();
348 conns.retain(|c| c.state != ConnectionState::Broken && !c.is_expired(now, max_lifetime_ms));
349 let removed = before_len - conns.len();
350 if removed > 0 {
351 stats.total_closed += removed as u64;
352 stats.current_size = stats.current_size.saturating_sub(removed);
353 stats.current_idle = stats.current_idle.saturating_sub(removed);
354 }
355
356 if conns.len() < self.config.max_connections {
358 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
359 let mut conn = PooledConnection::new(id, &self.config.endpoint, now);
360 conn.checkout(now);
361 conns.push_back(conn);
362 stats.total_created += 1;
363 stats.total_checkouts += 1;
364 stats.current_size += 1;
365 stats.current_in_use += 1;
366 debug!("Created and checked out new connection {}", id);
367 return Ok(id);
368 }
369
370 Err(DistributedError::ResourceExhausted(
372 "Connection pool exhausted".to_string(),
373 ))
374 }
375
376 pub async fn checkin(&self, conn_id: u64) -> Result<()> {
378 let now = self.current_tick.load(Ordering::Relaxed);
379 let mut conns = self.connections.lock().await;
380 let mut stats = self.stats.lock().await;
381
382 for conn in conns.iter_mut() {
383 if conn.id == conn_id {
384 conn.checkin(now);
385 stats.current_in_use = stats.current_in_use.saturating_sub(1);
386 stats.current_idle += 1;
387 debug!("Checked in connection {}", conn_id);
388 return Ok(());
389 }
390 }
391
392 Err(DistributedError::Worker(format!(
393 "Connection {conn_id} not found in pool"
394 )))
395 }
396
397 pub async fn mark_broken(&self, conn_id: u64) -> Result<()> {
399 let mut conns = self.connections.lock().await;
400 let mut stats = self.stats.lock().await;
401
402 for conn in conns.iter_mut() {
403 if conn.id == conn_id {
404 let was_in_use = conn.state == ConnectionState::InUse;
405 conn.mark_broken();
406 stats.total_failures += 1;
407 if was_in_use {
408 stats.current_in_use = stats.current_in_use.saturating_sub(1);
409 } else {
410 stats.current_idle = stats.current_idle.saturating_sub(1);
411 }
412 warn!("Connection {} marked as broken", conn_id);
413 return Ok(());
414 }
415 }
416
417 Err(DistributedError::Worker(format!(
418 "Connection {conn_id} not found in pool"
419 )))
420 }
421
422 pub async fn execute_with_retry<F, T>(&self, mut operation: F) -> Result<T>
428 where
429 F: FnMut(u64) -> Result<T>,
430 {
431 let mut attempt = 0u32;
432
433 loop {
434 let conn_id = self.checkout().await?;
435
436 match operation(conn_id) {
437 Ok(value) => {
438 self.checkin(conn_id).await?;
439 return Ok(value);
440 }
441 Err(e) => {
442 let _ = self.mark_broken(conn_id).await;
443
444 if !self.retry_policy.should_retry(attempt) {
445 return Err(e);
446 }
447
448 let delay = self.retry_policy.delay_for_attempt(attempt);
449 debug!(
450 "Retry attempt {} after {delay:?} for connection failure: {e}",
451 attempt + 1
452 );
453 attempt += 1;
456 }
457 }
458 }
459 }
460
461 pub fn advance_tick(&self, millis: u64) {
463 self.current_tick.fetch_add(millis, Ordering::Relaxed);
464 }
465
466 pub async fn stats(&self) -> PoolStats {
468 self.stats.lock().await.clone()
469 }
470
471 pub async fn size(&self) -> usize {
473 self.connections.lock().await.len()
474 }
475
476 pub async fn idle_count(&self) -> usize {
478 self.connections
479 .lock()
480 .await
481 .iter()
482 .filter(|c| c.state == ConnectionState::Idle)
483 .count()
484 }
485
486 #[must_use]
488 pub fn retry_policy(&self) -> &RetryPolicy {
489 &self.retry_policy
490 }
491
492 pub async fn health_check(&self) -> Result<usize> {
494 let now = self.current_tick.load(Ordering::Relaxed);
495 let max_lifetime_ms = self.config.max_lifetime.as_millis() as u64;
496
497 let mut conns = self.connections.lock().await;
498 let mut stats = self.stats.lock().await;
499
500 let before = conns.len();
501 conns.retain(|c| {
502 if c.state == ConnectionState::Broken {
503 return false;
504 }
505 if c.state == ConnectionState::Idle && c.is_expired(now, max_lifetime_ms) {
506 return false;
507 }
508 true
509 });
510
511 let removed = before - conns.len();
512 stats.total_closed += removed as u64;
513 stats.current_size = stats.current_size.saturating_sub(removed);
514 stats.current_idle = stats.current_idle.saturating_sub(removed);
515
516 let current_idle = conns
518 .iter()
519 .filter(|c| c.state == ConnectionState::Idle)
520 .count();
521 let mut created = 0usize;
522 while current_idle + created < self.config.min_idle
523 && conns.len() + created < self.config.max_connections
524 {
525 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
526 conns.push_back(PooledConnection::new(id, &self.config.endpoint, now));
527 stats.total_created += 1;
528 stats.current_size += 1;
529 stats.current_idle += 1;
530 created += 1;
531 }
532
533 if removed > 0 || created > 0 {
534 debug!(
535 "Health check: removed={}, created={}, pool_size={}",
536 removed,
537 created,
538 conns.len()
539 );
540 }
541
542 Ok(removed)
543 }
544
545 pub async fn shutdown(&self) -> Result<()> {
547 self.is_shutdown.store(true, Ordering::Relaxed);
548 let mut conns = self.connections.lock().await;
549 let mut stats = self.stats.lock().await;
550
551 let count = conns.len();
552 conns.clear();
553 stats.total_closed += count as u64;
554 stats.current_size = 0;
555 stats.current_idle = 0;
556 stats.current_in_use = 0;
557
558 info!("Connection pool shut down, closed {} connections", count);
559 Ok(())
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566
567 fn small_pool(max: usize, min_idle: usize) -> ConnectionPool {
568 ConnectionPool::new(
569 ConnectionPoolConfig::new("http://localhost:50051")
570 .with_max_connections(max)
571 .with_min_idle(min_idle),
572 )
573 }
574
575 #[test]
576 fn test_config_defaults() {
577 let config = ConnectionPoolConfig::default();
578 assert_eq!(config.max_connections, 8);
579 assert_eq!(config.min_idle, 2);
580 assert_eq!(config.max_retries, 3);
581 }
582
583 #[test]
584 fn test_config_builder() {
585 let config = ConnectionPoolConfig::new("http://example.com:50051")
586 .with_max_connections(16)
587 .with_min_idle(4)
588 .with_max_retries(5)
589 .with_connect_timeout(Duration::from_secs(10))
590 .with_retry_base_delay(Duration::from_millis(200))
591 .with_max_lifetime(Duration::from_secs(7200));
592 assert_eq!(config.max_connections, 16);
593 assert_eq!(config.min_idle, 4);
594 assert_eq!(config.max_retries, 5);
595 assert_eq!(config.connect_timeout, Duration::from_secs(10));
596 }
597
598 #[test]
599 fn test_config_max_connections_minimum_one() {
600 let config = ConnectionPoolConfig::new("http://localhost:50051").with_max_connections(0);
601 assert_eq!(config.max_connections, 1);
602 }
603
604 #[test]
605 fn test_retry_policy_delay() {
606 let policy = RetryPolicy::new(5, Duration::from_millis(100), Duration::from_secs(5));
607 assert_eq!(policy.delay_for_attempt(0), Duration::from_millis(100));
608 assert_eq!(policy.delay_for_attempt(1), Duration::from_millis(200));
609 assert_eq!(policy.delay_for_attempt(2), Duration::from_millis(400));
610 assert_eq!(policy.delay_for_attempt(3), Duration::from_millis(800));
611 }
612
613 #[test]
614 fn test_retry_policy_delay_capped() {
615 let policy = RetryPolicy::new(5, Duration::from_millis(100), Duration::from_millis(500));
616 assert_eq!(policy.delay_for_attempt(4), Duration::from_millis(500));
618 }
619
620 #[test]
621 fn test_retry_policy_should_retry() {
622 let policy = RetryPolicy::new(3, Duration::from_millis(100), Duration::from_secs(5));
623 assert!(policy.should_retry(0));
624 assert!(policy.should_retry(1));
625 assert!(policy.should_retry(2));
626 assert!(!policy.should_retry(3));
627 }
628
629 #[test]
630 fn test_connection_state() {
631 let mut conn = PooledConnection::new(1, "http://localhost", 0);
632 assert_eq!(conn.state(), ConnectionState::Idle);
633
634 conn.checkout(10);
635 assert_eq!(conn.state(), ConnectionState::InUse);
636
637 conn.checkin(20);
638 assert_eq!(conn.state(), ConnectionState::Idle);
639 assert_eq!(conn.requests_served, 1);
640
641 conn.mark_broken();
642 assert_eq!(conn.state(), ConnectionState::Broken);
643 }
644
645 #[test]
646 fn test_connection_expiry() {
647 let conn = PooledConnection::new(1, "http://localhost", 100);
648 assert!(!conn.is_expired(200, 1000));
649 assert!(conn.is_expired(1100, 1000));
650 assert!(conn.is_expired(1101, 1000));
651 }
652
653 #[tokio::test]
654 async fn test_pool_initialize() {
655 let pool = small_pool(4, 2);
656 pool.initialize().await.expect("init should succeed");
657 assert_eq!(pool.size().await, 2);
658 assert_eq!(pool.idle_count().await, 2);
659 }
660
661 #[tokio::test]
662 async fn test_pool_checkout_and_checkin() {
663 let pool = small_pool(4, 1);
664 pool.initialize().await.expect("init should succeed");
665
666 let conn_id = pool.checkout().await.expect("checkout should succeed");
667 assert_eq!(pool.idle_count().await, 0);
668
669 pool.checkin(conn_id).await.expect("checkin should succeed");
670 assert_eq!(pool.idle_count().await, 1);
671 }
672
673 #[tokio::test]
674 async fn test_pool_creates_on_demand() {
675 let pool = small_pool(4, 0);
676 pool.initialize().await.expect("init should succeed");
677 assert_eq!(pool.size().await, 0);
678
679 let conn_id = pool.checkout().await.expect("checkout should succeed");
680 assert_eq!(pool.size().await, 1);
681
682 pool.checkin(conn_id).await.expect("checkin should succeed");
683 }
684
685 #[tokio::test]
686 async fn test_pool_exhaustion() {
687 let pool = small_pool(2, 0);
688 pool.initialize().await.expect("init should succeed");
689
690 let _c1 = pool.checkout().await.expect("checkout 1 should succeed");
691 let _c2 = pool.checkout().await.expect("checkout 2 should succeed");
692
693 let result = pool.checkout().await;
694 assert!(result.is_err());
695 }
696
697 #[tokio::test]
698 async fn test_pool_mark_broken() {
699 let pool = small_pool(4, 1);
700 pool.initialize().await.expect("init should succeed");
701
702 let conn_id = pool.checkout().await.expect("checkout should succeed");
703 pool.mark_broken(conn_id)
704 .await
705 .expect("mark_broken should succeed");
706
707 let stats = pool.stats().await;
708 assert_eq!(stats.total_failures, 1);
709 }
710
711 #[tokio::test]
712 async fn test_pool_health_check_removes_broken() {
713 let pool = small_pool(4, 0);
714 pool.initialize().await.expect("init should succeed");
715
716 let c1 = pool.checkout().await.expect("checkout should succeed");
717 pool.mark_broken(c1)
718 .await
719 .expect("mark_broken should succeed");
720
721 let removed = pool
722 .health_check()
723 .await
724 .expect("health_check should succeed");
725 assert_eq!(removed, 1);
726 assert_eq!(pool.size().await, 0);
727 }
728
729 #[tokio::test]
730 async fn test_pool_health_check_removes_expired() {
731 let pool = ConnectionPool::new(
732 ConnectionPoolConfig::new("http://localhost:50051")
733 .with_max_connections(4)
734 .with_min_idle(0)
735 .with_max_lifetime(Duration::from_millis(500)),
736 );
737 pool.initialize().await.expect("init should succeed");
738
739 let c1 = pool.checkout().await.expect("checkout should succeed");
740 pool.checkin(c1).await.expect("checkin should succeed");
741 assert_eq!(pool.size().await, 1);
742
743 pool.advance_tick(600);
745
746 let removed = pool
747 .health_check()
748 .await
749 .expect("health_check should succeed");
750 assert_eq!(removed, 1);
751 assert_eq!(pool.size().await, 0);
752 }
753
754 #[tokio::test]
755 async fn test_pool_health_check_replenishes_min_idle() {
756 let pool = small_pool(4, 2);
757 pool.initialize().await.expect("init should succeed");
758 assert_eq!(pool.size().await, 2);
759
760 let c1 = pool.checkout().await.expect("checkout should succeed");
762 pool.mark_broken(c1)
763 .await
764 .expect("mark_broken should succeed");
765
766 pool.health_check()
768 .await
769 .expect("health_check should succeed");
770
771 assert!(pool.idle_count().await >= 2);
773 }
774
775 #[tokio::test]
776 async fn test_pool_execute_with_retry_success() {
777 let pool = small_pool(4, 1);
778 pool.initialize().await.expect("init should succeed");
779
780 let result = pool
781 .execute_with_retry(|conn_id| -> Result<u64> { Ok(conn_id * 2) })
782 .await
783 .expect("execute should succeed");
784 assert!(result > 0);
785 }
786
787 #[tokio::test]
788 async fn test_pool_execute_with_retry_fails_then_succeeds() {
789 let pool = small_pool(4, 1);
790 pool.initialize().await.expect("init should succeed");
791
792 let call_count = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
793 let count = call_count.clone();
794
795 let result = pool
796 .execute_with_retry(move |conn_id| -> Result<u64> {
797 let attempt = count.fetch_add(1, Ordering::Relaxed);
798 if attempt == 0 {
799 Err(DistributedError::Worker("transient error".to_string()))
800 } else {
801 Ok(conn_id)
802 }
803 })
804 .await
805 .expect("execute should succeed on retry");
806 assert!(result > 0);
807 assert_eq!(call_count.load(Ordering::Relaxed), 2);
808 }
809
810 #[tokio::test]
811 async fn test_pool_execute_with_retry_exhausted() {
812 let pool = ConnectionPool::new(
813 ConnectionPoolConfig::new("http://localhost:50051")
814 .with_max_connections(8)
815 .with_min_idle(0)
816 .with_max_retries(2),
817 );
818 pool.initialize().await.expect("init should succeed");
819
820 let result = pool
821 .execute_with_retry(|_conn_id| -> Result<u64> {
822 Err(DistributedError::Worker("permanent error".to_string()))
823 })
824 .await;
825 assert!(result.is_err());
826 }
827
828 #[tokio::test]
829 async fn test_pool_shutdown() {
830 let pool = small_pool(4, 3);
831 pool.initialize().await.expect("init should succeed");
832 assert_eq!(pool.size().await, 3);
833
834 pool.shutdown().await.expect("shutdown should succeed");
835 assert_eq!(pool.size().await, 0);
836
837 let result = pool.checkout().await;
839 assert!(result.is_err());
840 }
841
842 #[tokio::test]
843 async fn test_pool_stats() {
844 let pool = small_pool(4, 1);
845 pool.initialize().await.expect("init should succeed");
846
847 let c1 = pool.checkout().await.expect("checkout should succeed");
848 pool.checkin(c1).await.expect("checkin should succeed");
849
850 let stats = pool.stats().await;
851 assert!(stats.total_created >= 1);
852 assert!(stats.total_checkouts >= 1);
853 assert_eq!(stats.current_in_use, 0);
854 }
855
856 #[tokio::test]
857 async fn test_pool_checkin_nonexistent() {
858 let pool = small_pool(4, 0);
859 pool.initialize().await.expect("init should succeed");
860
861 let result = pool.checkin(999).await;
862 assert!(result.is_err());
863 }
864
865 #[tokio::test]
866 async fn test_pool_mark_broken_nonexistent() {
867 let pool = small_pool(4, 0);
868 pool.initialize().await.expect("init should succeed");
869
870 let result = pool.mark_broken(999).await;
871 assert!(result.is_err());
872 }
873
874 #[tokio::test]
875 async fn test_pool_multiple_checkouts() {
876 let pool = small_pool(4, 0);
877 pool.initialize().await.expect("init should succeed");
878
879 let c1 = pool.checkout().await.expect("checkout 1 should succeed");
880 let c2 = pool.checkout().await.expect("checkout 2 should succeed");
881 let c3 = pool.checkout().await.expect("checkout 3 should succeed");
882
883 assert_ne!(c1, c2);
884 assert_ne!(c2, c3);
885
886 let stats = pool.stats().await;
887 assert_eq!(stats.current_in_use, 3);
888 assert_eq!(stats.current_size, 3);
889
890 pool.checkin(c1).await.expect("checkin should succeed");
891 pool.checkin(c2).await.expect("checkin should succeed");
892 pool.checkin(c3).await.expect("checkin should succeed");
893
894 let stats = pool.stats().await;
895 assert_eq!(stats.current_in_use, 0);
896 }
897
898 #[tokio::test]
899 async fn test_pool_reuses_idle_connections() {
900 let pool = small_pool(4, 1);
901 pool.initialize().await.expect("init should succeed");
902
903 let c1 = pool.checkout().await.expect("checkout 1 should succeed");
904 pool.checkin(c1).await.expect("checkin should succeed");
905
906 let c2 = pool.checkout().await.expect("checkout 2 should succeed");
907 assert_eq!(c1, c2);
909 pool.checkin(c2).await.expect("checkin should succeed");
910 }
911}