network_protocol/service/
pool.rs1use std::collections::VecDeque;
19use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
20use std::sync::Arc;
21use std::time::{Duration, Instant};
22use tokio::sync::{Mutex, Semaphore};
23use tracing::{debug, error, warn};
24
25use crate::error::{ProtocolError, Result};
26
27#[derive(Debug, Clone)]
29pub struct PoolConfig {
30 pub min_size: usize,
32 pub max_size: usize,
35 pub idle_timeout: Duration,
37 pub max_lifetime: Duration,
40 pub max_waiters: usize,
42 pub circuit_breaker_threshold: usize,
44 pub circuit_breaker_timeout: Duration,
46}
47
48impl Default for PoolConfig {
49 fn default() -> Self {
50 Self {
51 min_size: 5,
52 max_size: 50,
53 idle_timeout: Duration::from_secs(300), max_lifetime: Duration::from_secs(3600), max_waiters: 1000, circuit_breaker_threshold: 5,
57 circuit_breaker_timeout: Duration::from_secs(10),
58 }
59 }
60}
61
62impl PoolConfig {
63 pub fn validate(&self) -> Result<()> {
65 let mut errors = Vec::new();
66
67 if self.max_size == 0 {
69 errors.push("Pool max_size must be greater than 0".to_string());
70 }
71
72 if self.min_size > self.max_size {
73 errors.push(format!(
74 "Pool min_size ({}) cannot exceed max_size ({})",
75 self.min_size, self.max_size
76 ));
77 }
78
79 if self.max_size > 10_000 {
81 errors.push(format!(
82 "Pool max_size ({}) exceeds recommended limit (10,000)",
83 self.max_size
84 ));
85 }
86
87 if self.max_waiters == 0 {
88 errors.push("Pool max_waiters must be greater than 0".to_string());
89 }
90
91 if self.max_waiters > 1_000_000 {
92 errors.push(format!(
93 "Pool max_waiters ({}) exceeds recommended limit (1,000,000)",
94 self.max_waiters
95 ));
96 }
97
98 if self.idle_timeout.is_zero() {
100 errors.push("Pool idle_timeout must be greater than 0".to_string());
101 }
102
103 if self.max_lifetime.is_zero() {
104 errors.push("Pool max_lifetime must be greater than 0".to_string());
105 }
106
107 if self.idle_timeout >= self.max_lifetime {
108 errors.push(format!(
109 "Pool idle_timeout ({:?}) should be less than max_lifetime ({:?})",
110 self.idle_timeout, self.max_lifetime
111 ));
112 }
113
114 if self.idle_timeout.as_secs() > 3600 {
115 errors.push(format!(
116 "Pool idle_timeout ({} seconds) is unusually long (recommended: < 1 hour)",
117 self.idle_timeout.as_secs()
118 ));
119 }
120
121 if self.max_lifetime.as_secs() > 86400 {
122 errors.push(format!(
123 "Pool max_lifetime ({} seconds) is unusually long (recommended: < 24 hours)",
124 self.max_lifetime.as_secs()
125 ));
126 }
127
128 if self.circuit_breaker_threshold == 0 {
130 errors.push("Circuit breaker threshold must be greater than 0".to_string());
131 }
132
133 if self.circuit_breaker_threshold > 100 {
134 errors.push(format!(
135 "Circuit breaker threshold ({}) is unusually high (recommended: < 100)",
136 self.circuit_breaker_threshold
137 ));
138 }
139
140 if self.circuit_breaker_timeout.is_zero() {
141 errors.push("Circuit breaker timeout must be greater than 0".to_string());
142 }
143
144 if self.circuit_breaker_timeout.as_secs() > 300 {
145 errors.push(format!(
146 "Circuit breaker timeout ({} seconds) is unusually long (recommended: < 5 minutes)",
147 self.circuit_breaker_timeout.as_secs()
148 ));
149 }
150
151 if errors.is_empty() {
153 Ok(())
154 } else {
155 Err(ProtocolError::ConfigError(format!(
156 "Pool configuration validation failed:\n - {}",
157 errors.join("\n - ")
158 )))
159 }
160 }
161}
162
163struct PooledConnection<T> {
165 connection: T,
166 created_at: Instant,
167 last_used_at: Instant,
168}
169
170impl<T> PooledConnection<T> {
171 fn new(connection: T) -> Self {
172 Self::returned(connection, Instant::now())
173 }
174
175 fn returned(connection: T, created_at: Instant) -> Self {
178 Self {
179 connection,
180 created_at,
181 last_used_at: Instant::now(),
182 }
183 }
184
185 fn is_expired(&self, config: &PoolConfig) -> bool {
186 let now = Instant::now();
187 if now.duration_since(self.created_at) > config.max_lifetime {
189 return true;
190 }
191 if now.duration_since(self.last_used_at) > config.idle_timeout {
193 return true;
194 }
195 false
196 }
197
198 fn touch(&mut self) {
199 self.last_used_at = Instant::now();
200 }
201}
202
203pub trait ConnectionFactory<T>: Send + Sync {
205 fn create(&self) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T>> + Send>>;
207
208 fn is_healthy(&self, _conn: &T) -> bool {
210 true
211 }
212}
213
214#[derive(Debug, Default)]
216pub struct PoolMetrics {
217 pub connections_created: AtomicU64,
219 pub connections_reused: AtomicU64,
221 pub connections_evicted: AtomicU64,
223 pub acquisition_errors: AtomicU64,
225 pub active_connections: AtomicUsize,
227 pub idle_connections: AtomicUsize,
229 pub total_wait_time_us: AtomicU64,
231 pub total_acquisitions: AtomicU64,
233}
234
235impl PoolMetrics {
236 pub fn new() -> Self {
237 Self::default()
238 }
239
240 pub fn average_wait_time_us(&self) -> u64 {
241 let total = self.total_acquisitions.load(Ordering::Relaxed);
242 if total == 0 {
243 return 0;
244 }
245 self.total_wait_time_us.load(Ordering::Relaxed) / total
246 }
247
248 pub fn utilization_percent(&self) -> f64 {
249 let active = self.active_connections.load(Ordering::Relaxed) as f64;
250 let idle = self.idle_connections.load(Ordering::Relaxed) as f64;
251 let total = active + idle;
252 if total == 0.0 {
253 return 0.0;
254 }
255 (active / total) * 100.0
256 }
257}
258
259#[derive(Debug)]
261struct CircuitBreaker {
262 consecutive_failures: AtomicUsize,
263 threshold: usize,
264 timeout: Duration,
265 opened_at: Mutex<Option<Instant>>,
266}
267
268impl CircuitBreaker {
269 fn new(threshold: usize, timeout: Duration) -> Self {
270 Self {
271 consecutive_failures: AtomicUsize::new(0),
272 threshold,
273 timeout,
274 opened_at: Mutex::new(None),
275 }
276 }
277
278 async fn check(&self) -> Result<()> {
279 let mut opened_at = self.opened_at.lock().await;
280 if let Some(opened_time) = *opened_at {
281 if opened_time.elapsed() < self.timeout {
283 return Err(ProtocolError::CircuitBreakerOpen);
284 }
285 *opened_at = None;
287 self.consecutive_failures.store(0, Ordering::SeqCst);
288 debug!("Circuit breaker entering half-open state");
289 }
290 Ok(())
291 }
292
293 async fn record_success(&self) {
294 self.consecutive_failures.store(0, Ordering::SeqCst);
295 let mut opened_at = self.opened_at.lock().await;
296 if opened_at.is_some() {
297 *opened_at = None;
298 debug!("Circuit breaker closed after successful operation");
299 }
300 }
301
302 async fn record_failure(&self) {
303 let failures = self.consecutive_failures.fetch_add(1, Ordering::SeqCst) + 1;
304 if failures >= self.threshold {
305 let mut opened_at = self.opened_at.lock().await;
306 *opened_at = Some(Instant::now());
307 error!(
308 "Circuit breaker opened after {} consecutive failures",
309 failures
310 );
311 }
312 }
313}
314
315pub struct ConnectionPool<T> {
317 config: PoolConfig,
318 factory: Arc<dyn ConnectionFactory<T>>,
319 connections: Arc<Mutex<VecDeque<PooledConnection<T>>>>,
320 metrics: Arc<PoolMetrics>,
321 circuit_breaker: Arc<CircuitBreaker>,
322 backpressure: Arc<Semaphore>,
323}
324
325impl<T: Send + 'static> ConnectionPool<T> {
326 pub fn new(factory: Arc<dyn ConnectionFactory<T>>, config: PoolConfig) -> Result<Self> {
328 config.validate()?;
329
330 let metrics = Arc::new(PoolMetrics::new());
331 let circuit_breaker = Arc::new(CircuitBreaker::new(
332 config.circuit_breaker_threshold,
333 config.circuit_breaker_timeout,
334 ));
335
336 let pool = Self {
337 config: config.clone(),
338 factory: factory.clone(),
339 connections: Arc::new(Mutex::new(VecDeque::new())),
340 metrics: metrics.clone(),
341 circuit_breaker,
342 backpressure: Arc::new(Semaphore::new(config.max_waiters)),
343 };
344
345 if config.min_size > 0 {
347 let factory_clone = factory;
348 let connections_clone = pool.connections.clone();
349 let metrics_clone = metrics;
350 let min_size = config.min_size;
351
352 tokio::spawn(async move {
353 debug!("Warming connection pool with {} connections", min_size);
354 for _ in 0..min_size {
355 match factory_clone.create().await {
356 Ok(conn) => {
357 let mut connections = connections_clone.lock().await;
358 connections.push_back(PooledConnection::new(conn));
359 metrics_clone
360 .connections_created
361 .fetch_add(1, Ordering::Relaxed);
362 metrics_clone
363 .idle_connections
364 .fetch_add(1, Ordering::Relaxed);
365 }
366 Err(e) => {
367 warn!("Failed to warm connection: {}", e);
368 break;
369 }
370 }
371 }
372 debug!("Connection pool warming complete");
373 });
374 }
375
376 Ok(pool)
377 }
378
379 pub async fn acquire(&self) -> Result<PooledConnectionGuard<T>> {
381 let start = Instant::now();
382
383 let _permit = self
385 .backpressure
386 .acquire()
387 .await
388 .map_err(|_| ProtocolError::PoolExhausted)?;
389
390 self.circuit_breaker.check().await?;
392
393 let mut connections = self.connections.lock().await;
394
395 while let Some(mut pooled) = connections.pop_back() {
397 if !pooled.is_expired(&self.config) && self.factory.is_healthy(&pooled.connection) {
398 pooled.touch();
399 self.metrics
400 .connections_reused
401 .fetch_add(1, Ordering::Relaxed);
402 self.metrics
403 .idle_connections
404 .fetch_sub(1, Ordering::Relaxed);
405 self.metrics
406 .active_connections
407 .fetch_add(1, Ordering::Relaxed);
408 self.metrics
409 .total_acquisitions
410 .fetch_add(1, Ordering::Relaxed);
411 self.metrics
412 .total_wait_time_us
413 .fetch_add(start.elapsed().as_micros() as u64, Ordering::Relaxed);
414
415 debug!("Reused connection from pool (LRU)");
416 return Ok(PooledConnectionGuard {
417 connection: Some(pooled.connection),
418 created_at: pooled.created_at,
419 max_idle: self.config.max_size,
420 pool: self.connections.clone(),
421 metrics: self.metrics.clone(),
422 });
423 }
424 debug!("Evicted expired/unhealthy connection from pool");
425 self.metrics
426 .connections_evicted
427 .fetch_add(1, Ordering::Relaxed);
428 self.metrics
429 .idle_connections
430 .fetch_sub(1, Ordering::Relaxed);
431 }
432
433 drop(connections); match self.factory.create().await {
437 Ok(new_conn) => {
438 self.circuit_breaker.record_success().await;
439 self.metrics
440 .connections_created
441 .fetch_add(1, Ordering::Relaxed);
442 self.metrics
443 .active_connections
444 .fetch_add(1, Ordering::Relaxed);
445 self.metrics
446 .total_acquisitions
447 .fetch_add(1, Ordering::Relaxed);
448 self.metrics
449 .total_wait_time_us
450 .fetch_add(start.elapsed().as_micros() as u64, Ordering::Relaxed);
451
452 debug!("Created new connection for pool");
453
454 Ok(PooledConnectionGuard {
455 connection: Some(new_conn),
456 created_at: Instant::now(),
457 max_idle: self.config.max_size,
458 pool: self.connections.clone(),
459 metrics: self.metrics.clone(),
460 })
461 }
462 Err(e) => {
463 self.circuit_breaker.record_failure().await;
464 self.metrics
465 .acquisition_errors
466 .fetch_add(1, Ordering::Relaxed);
467 Err(e)
468 }
469 }
470 }
471
472 pub fn metrics(&self) -> Arc<PoolMetrics> {
474 self.metrics.clone()
475 }
476
477 pub async fn size(&self) -> usize {
479 self.connections.lock().await.len()
480 }
481
482 pub async fn clear(&self) {
484 self.connections.lock().await.clear();
485 debug!("Cleared all connections from pool");
486 }
487
488 pub fn config(&self) -> &PoolConfig {
490 &self.config
491 }
492}
493
494pub struct PooledConnectionGuard<T: Send + 'static> {
498 connection: Option<T>,
499 created_at: Instant,
501 max_idle: usize,
503 pool: Arc<Mutex<VecDeque<PooledConnection<T>>>>,
504 metrics: Arc<PoolMetrics>,
505}
506
507impl<T: Send + 'static> PooledConnectionGuard<T> {
508 pub fn get(&self) -> Option<&T> {
510 self.connection.as_ref()
511 }
512
513 pub fn get_mut(&mut self) -> Option<&mut T> {
515 self.connection.as_mut()
516 }
517
518 pub fn into_inner(mut self) -> Option<T> {
520 self.connection.take()
521 }
522}
523
524impl<T: Send + 'static> AsRef<T> for PooledConnectionGuard<T> {
525 #[allow(clippy::expect_used)] fn as_ref(&self) -> &T {
527 self.connection.as_ref().expect("Connection should exist")
528 }
529}
530
531impl<T: Send + 'static> AsMut<T> for PooledConnectionGuard<T> {
532 #[allow(clippy::expect_used)] fn as_mut(&mut self) -> &mut T {
534 self.connection.as_mut().expect("Connection should exist")
535 }
536}
537
538impl<T: Send + 'static> Drop for PooledConnectionGuard<T> {
539 fn drop(&mut self) {
540 if let Some(conn) = self.connection.take() {
541 let pool = self.pool.clone();
542 let metrics = self.metrics.clone();
543 let max_idle = self.max_idle;
544 let pooled = PooledConnection::returned(conn, self.created_at);
545
546 metrics.active_connections.fetch_sub(1, Ordering::Relaxed);
548
549 tokio::spawn(async move {
552 let mut connections = pool.lock().await;
553 if connections.len() < max_idle {
554 connections.push_back(pooled);
555 metrics.idle_connections.fetch_add(1, Ordering::Relaxed);
556 } else {
557 warn!("Connection pool at capacity, discarding connection");
558 }
559 });
560 }
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use std::sync::atomic::{AtomicUsize, Ordering};
568
569 #[allow(dead_code)]
570 struct TestConnection {
571 id: usize,
572 }
573
574 struct TestFactory {
575 counter: Arc<AtomicUsize>,
576 }
577
578 impl TestFactory {
579 fn new() -> Self {
580 Self {
581 counter: Arc::new(AtomicUsize::new(0)),
582 }
583 }
584
585 fn count(&self) -> usize {
586 self.counter.load(Ordering::SeqCst)
587 }
588 }
589
590 impl ConnectionFactory<TestConnection> for TestFactory {
591 fn create(
592 &self,
593 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<TestConnection>> + Send>>
594 {
595 let id = self.counter.fetch_add(1, Ordering::SeqCst);
596 Box::pin(async move { Ok(TestConnection { id }) })
597 }
598 }
599
600 #[tokio::test]
601 async fn test_pool_creation() {
602 let factory = Arc::new(TestFactory::new());
603 let pool = ConnectionPool::new(
604 factory.clone(),
605 PoolConfig {
606 min_size: 2,
607 max_size: 10,
608 idle_timeout: Duration::from_secs(60),
609 max_lifetime: Duration::from_secs(600),
610 ..Default::default()
611 },
612 );
613
614 assert!(pool.is_ok());
615 }
616
617 #[tokio::test]
618 #[allow(clippy::unwrap_used)] async fn test_pool_acquire_creates_connection() {
620 let factory = Arc::new(TestFactory::new());
621 let pool = ConnectionPool::new(factory.clone(), PoolConfig::default()).unwrap();
622
623 let guard = pool.acquire().await.unwrap();
624 assert!(guard.get().is_some());
625 assert_eq!(factory.count(), 1);
626 }
627
628 #[tokio::test]
629 async fn test_config_validation() {
630 let invalid_config = PoolConfig {
631 min_size: 100,
632 max_size: 10,
633 idle_timeout: Duration::from_secs(60),
634 max_lifetime: Duration::from_secs(600),
635 ..Default::default()
636 };
637
638 let factory = Arc::new(TestFactory::new());
639 let result = ConnectionPool::new(factory, invalid_config);
640 assert!(result.is_err());
641 }
642
643 #[tokio::test]
644 async fn test_config_validation_zero_max_size() {
645 let config = PoolConfig {
646 max_size: 0,
647 ..Default::default()
648 };
649 assert!(config.validate().is_err());
650 }
651
652 #[tokio::test]
653 async fn test_config_validation_zero_timeouts() {
654 let config = PoolConfig {
655 idle_timeout: Duration::from_secs(0),
656 ..Default::default()
657 };
658 assert!(config.validate().is_err());
659
660 let config2 = PoolConfig {
661 max_lifetime: Duration::from_secs(0),
662 ..Default::default()
663 };
664 assert!(config2.validate().is_err());
665 }
666
667 #[tokio::test]
668 async fn test_config_validation_idle_exceeds_lifetime() {
669 let config = PoolConfig {
670 idle_timeout: Duration::from_secs(600),
671 max_lifetime: Duration::from_secs(300),
672 ..Default::default()
673 };
674 assert!(config.validate().is_err());
675 }
676
677 #[tokio::test]
678 async fn test_config_validation_circuit_breaker() {
679 let config = PoolConfig {
680 circuit_breaker_threshold: 0,
681 ..Default::default()
682 };
683 assert!(config.validate().is_err());
684
685 let config2 = PoolConfig {
686 circuit_breaker_timeout: Duration::from_secs(0),
687 ..Default::default()
688 };
689 assert!(config2.validate().is_err());
690 }
691
692 #[tokio::test]
693 async fn test_config_validation_valid_config() {
694 let config = PoolConfig::default();
695 assert!(config.validate().is_ok());
696 }
697
698 async fn settle<T: Send + 'static>(pool: &ConnectionPool<T>, expected: usize) -> usize {
700 for _ in 0..100 {
701 if pool.size().await == expected {
702 break;
703 }
704 tokio::time::sleep(Duration::from_millis(5)).await;
705 }
706 pool.size().await
707 }
708
709 #[tokio::test]
710 #[allow(clippy::unwrap_used)] async fn test_released_connections_are_capped_at_max_size() {
712 let factory = Arc::new(TestFactory::new());
713 let pool = ConnectionPool::new(
714 factory.clone(),
715 PoolConfig {
716 min_size: 0,
717 max_size: 2,
718 ..Default::default()
719 },
720 )
721 .unwrap();
722
723 let mut guards = Vec::new();
724 for _ in 0..5 {
725 guards.push(pool.acquire().await.unwrap());
726 }
727 assert_eq!(factory.count(), 5);
728 drop(guards);
729
730 tokio::time::sleep(Duration::from_millis(50)).await;
732 assert_eq!(settle(&pool, 2).await, 2);
733 }
734
735 #[tokio::test]
736 #[allow(clippy::unwrap_used)] async fn test_max_lifetime_counts_from_creation_not_last_release() {
738 let factory = Arc::new(TestFactory::new());
739 let pool = ConnectionPool::new(
740 factory.clone(),
741 PoolConfig {
742 min_size: 0,
743 max_size: 4,
744 idle_timeout: Duration::from_millis(100),
745 max_lifetime: Duration::from_millis(250),
746 ..Default::default()
747 },
748 )
749 .unwrap();
750
751 drop(pool.acquire().await.unwrap());
753 assert_eq!(settle(&pool, 1).await, 1);
754 for _ in 0..8 {
755 tokio::time::sleep(Duration::from_millis(50)).await;
756 drop(pool.acquire().await.unwrap());
757 assert_eq!(settle(&pool, 1).await, 1);
758 }
759
760 assert!(
763 factory.count() >= 2,
764 "connection outlived max_lifetime: created {}",
765 factory.count()
766 );
767 }
768}