1use std::collections::HashMap;
13use std::error::Error;
14use std::hash::Hash;
15use std::panic::{AssertUnwindSafe, catch_unwind};
16use std::sync::{Arc, Condvar, Mutex, OnceLock, Weak};
17use std::thread::JoinHandle;
18use std::time::Duration;
19
20use crate::core::clock::{Clock, SystemClock};
21use crate::core::connection::{CloseReason, PooledConnection};
22use crate::core::errors::{FactoryError, PoolError};
23use crate::core::timeouts::{Timeout, remaining};
24use crate::factories::sync_factory::ConnectionFactory;
25use crate::observability::events::{Listeners, PoolListener};
26use crate::observability::stats::{PoolStats, StatsCollector, StatsGauges};
27use crate::policy::config::PoolConfig;
28use crate::policy::lifecycle::{
29 borrow_reason, needs_validation, new_connection, pop_idle, rotation_reason, sweep,
30};
31use crate::resilience::breaker::{BreakerState, CircuitBreaker};
32use crate::rng::{Rng, SystemRng};
33
34pub struct PoolOptions<T> {
36 pub name: Option<String>,
38 pub clock: Option<Arc<dyn Clock>>,
40 pub rng: Option<Box<dyn Rng>>,
42 pub listeners: Vec<Arc<dyn PoolListener<T>>>,
44}
45
46impl<T> Default for PoolOptions<T> {
47 fn default() -> Self {
48 Self {
49 name: None,
50 clock: None,
51 rng: None,
52 listeners: Vec::new(),
53 }
54 }
55}
56
57struct State<T> {
58 idle: Vec<PooledConnection<T>>,
59 checked_out: HashMap<T, PooledConnection<T>>,
60 size: usize,
61 waiters: usize,
62 closed: bool,
63 opened: bool,
64}
65
66struct MaintStop {
67 flag: Mutex<bool>,
68 cv: Condvar,
69}
70
71pub(crate) struct Inner<T> {
72 name: String,
73 factory: Arc<dyn ConnectionFactory<T>>,
74 cfg: PoolConfig,
75 clock: Arc<dyn Clock>,
76 rng: Mutex<Box<dyn Rng>>,
77
78 state: Mutex<State<T>>,
79 cond: Condvar,
80
81 stats: Arc<StatsCollector<T>>,
82 events: Listeners<T>,
83 breaker: OnceLock<CircuitBreaker>,
84
85 stop: Arc<MaintStop>,
86 handle: Mutex<Option<JoinHandle<()>>>,
87}
88
89pub struct ConnectionPool<T>(Arc<Inner<T>>);
91
92impl<T> Clone for ConnectionPool<T> {
93 fn clone(&self) -> Self {
94 ConnectionPool(self.0.clone())
95 }
96}
97
98impl<T> ConnectionPool<T>
99where
100 T: Clone + Eq + Hash + Send + Sync + 'static,
101{
102 pub fn new(
104 factory: impl ConnectionFactory<T> + 'static,
105 config: PoolConfig,
106 options: Option<PoolOptions<T>>,
107 ) -> Result<Self, PoolError> {
108 config.validate()?;
109 let options = options.unwrap_or_default();
110 let clock: Arc<dyn Clock> = options.clock.unwrap_or_else(|| Arc::new(SystemClock::new()));
111 let rng: Box<dyn Rng> = options.rng.unwrap_or_else(|| Box::new(SystemRng::new()));
112 let name = options.name.unwrap_or_else(|| "pool".to_string());
113
114 let stats = Arc::new(StatsCollector::new());
115 let mut listeners: Vec<Arc<dyn PoolListener<T>>> = vec![stats.clone()];
116 listeners.extend(options.listeners);
117 let events = Listeners::new(listeners);
118
119 let inner = Arc::new(Inner {
120 name,
121 factory: Arc::new(factory),
122 cfg: config,
123 clock,
124 rng: Mutex::new(rng),
125 state: Mutex::new(State {
126 idle: Vec::new(),
127 checked_out: HashMap::new(),
128 size: 0,
129 waiters: 0,
130 closed: false,
131 opened: false,
132 }),
133 cond: Condvar::new(),
134 stats,
135 events,
136 breaker: OnceLock::new(),
137 stop: Arc::new(MaintStop {
138 flag: Mutex::new(false),
139 cv: Condvar::new(),
140 }),
141 handle: Mutex::new(None),
142 });
143
144 if let Some(bc) = inner.cfg.breaker {
145 let weak = Arc::downgrade(&inner);
146 let cb = Box::new(move |old: BreakerState, new: BreakerState| {
147 if let Some(inner) = weak.upgrade() {
148 inner.events.breaker_change(old, new);
149 }
150 });
151 let breaker = CircuitBreaker::new(bc, Some(inner.clock.clone()), Some(cb));
152 let _ = inner.breaker.set(breaker);
153 }
154
155 Ok(ConnectionPool(inner))
156 }
157
158 pub fn name(&self) -> &str {
160 &self.0.name
161 }
162
163 pub fn open(&self) -> Result<(), PoolError> {
166 let start_thread = {
167 let mut g = self.0.state.lock().unwrap();
168 if g.closed {
169 return Err(PoolError::Closed(format!("pool {:?} is closed", self.0.name)));
170 }
171 if g.opened {
172 return Ok(());
173 }
174 g.opened = true;
175 self.0.cfg.maintenance_interval
176 };
177 self.0.fill_to_min();
178 if let Some(interval) = start_thread {
179 self.start_maintenance(interval);
180 }
181 Ok(())
182 }
183
184 fn start_maintenance(&self, interval: f64) {
185 let weak: Weak<Inner<T>> = Arc::downgrade(&self.0);
186 let stop = self.0.stop.clone();
187 let handle = std::thread::spawn(move || {
188 loop {
189 {
190 let g = stop.flag.lock().unwrap();
191 let (g, _) = stop
192 .cv
193 .wait_timeout_while(g, Duration::from_secs_f64(interval), |s| !*s)
194 .unwrap();
195 if *g {
196 return;
197 }
198 }
199 match weak.upgrade() {
200 Some(inner) => inner.run_maintenance_safe(),
201 None => return,
202 }
203 }
204 });
205 *self.0.handle.lock().unwrap() = Some(handle);
206 }
207
208 pub fn close(&self, timeout: Option<f64>) {
215 self.0.close(timeout);
216 }
217
218 pub fn is_closed(&self) -> bool {
220 self.0.state.lock().unwrap().closed
221 }
222
223 pub fn acquire(&self, timeout: Timeout) -> Result<T, PoolError> {
226 self.0.acquire(timeout)
227 }
228
229 pub fn release(&self, conn: T) -> Result<(), PoolError> {
231 self.0.release(conn, false, None)
232 }
233
234 pub fn discard(&self, conn: T) -> Result<(), PoolError> {
236 self.0.release(conn, true, None)
237 }
238
239 pub fn release_with_error(
242 &self,
243 conn: T,
244 error: &(dyn Error + 'static),
245 ) -> Result<(), PoolError> {
246 self.0.release(conn, false, Some(error))
247 }
248
249 pub fn with_connection<R>(
251 &self,
252 timeout: Timeout,
253 f: impl FnOnce(&T) -> R,
254 ) -> Result<R, PoolError> {
255 let conn = self.acquire(timeout)?;
256 let out = f(&conn);
257 self.release(conn)?;
258 Ok(out)
259 }
260
261 pub fn stats(&self) -> PoolStats {
263 self.0.stats_snapshot()
264 }
265
266 pub fn run_maintenance(&self) {
270 self.0.run_maintenance();
271 }
272}
273
274enum Taken<T> {
275 Reused(T),
276 Reserved,
277}
278
279impl<T> Inner<T>
280where
281 T: Clone + Eq + Hash + Send + Sync + 'static,
282{
283 fn is_closed(&self) -> bool {
284 self.state.lock().unwrap().closed
285 }
286
287 fn signal(&self) {
288 self.cond.notify_all();
289 }
290
291 fn new_conn(&self, raw: T, now: f64) -> PooledConnection<T> {
292 let mut rng = self.rng.lock().unwrap();
293 new_connection(raw, &self.cfg, now, &mut **rng)
294 }
295
296 fn retry_delay(&self, attempt: u32) -> f64 {
297 let mut rng = self.rng.lock().unwrap();
298 self.cfg.retry.delay_for(attempt, &mut **rng)
299 }
300
301 fn close(&self, timeout: Option<f64>) {
302 let idle = {
303 let mut g = self.state.lock().unwrap();
304 if g.closed {
305 return;
306 }
307 g.closed = true;
308 let idle = std::mem::take(&mut g.idle);
309 self.cond.notify_all();
310 if let Some(t) = timeout {
311 let deadline = self.clock.monotonic() + t;
312 while !g.checked_out.is_empty() {
313 let left = deadline - self.clock.monotonic();
314 if left <= 0.0 {
315 break;
316 }
317 let (g2, _) = self
318 .cond
319 .wait_timeout(g, Duration::from_secs_f64(left))
320 .unwrap();
321 g = g2;
322 }
323 }
324 idle
325 };
326 {
327 *self.stop.flag.lock().unwrap() = true;
328 self.stop.cv.notify_all();
329 }
330 if let Some(h) = self.handle.lock().unwrap().take() {
331 let _ = h.join();
332 }
333 for pooled in idle {
334 self.destroy_detached(pooled, CloseReason::PoolClosed);
335 }
336 }
337
338 fn acquire(&self, timeout: Timeout) -> Result<T, PoolError> {
339 let started = self.clock.monotonic();
340 let deadline = timeout.compute_deadline(self.cfg.acquire_timeout, started);
341 let key = match self.acquire_loop(deadline) {
342 Ok(k) => k,
343 Err(e) => {
344 if matches!(e, PoolError::Timeout(_)) {
345 let waited = self.clock.monotonic() - started;
346 self.events.safe(|l| l.on_acquire_timeout(waited));
347 }
348 return Err(e);
349 }
350 };
351 let now = self.clock.monotonic();
352 let (raw, ev, wait) = {
353 let mut g = self.state.lock().unwrap();
354 let pooled = g.checked_out.get_mut(&key).expect("acquired conn present");
355 pooled.mark_checked_out(now);
356 (pooled.raw.clone(), pooled.clone(), now - started)
357 };
358 self.events.safe(|l| l.on_acquired(&ev, wait));
359 Ok(raw)
360 }
361
362 fn acquire_loop(&self, deadline: Option<f64>) -> Result<T, PoolError> {
363 loop {
364 match self.take_idle_or_reserve(deadline)? {
365 Taken::Reserved => {
366 let (pooled, created_in) = self.create(deadline)?;
367 let key = self.register_new(pooled, created_in)?;
368 return Ok(key);
369 }
370 Taken::Reused(key) => match self.check_borrow(&key) {
371 None => return Ok(key),
372 Some(reason) => self.destroy_by_key(&key, reason),
373 },
374 }
375 }
376 }
377
378 fn take_idle_or_reserve(&self, deadline: Option<f64>) -> Result<Taken<T>, PoolError> {
379 let mut g = self.state.lock().unwrap();
380 loop {
381 if g.closed {
382 return Err(PoolError::Closed(format!("pool {:?} is closed", self.name)));
383 }
384 if let Some(conn) = pop_idle(&mut g.idle, self.cfg.idle_strategy) {
385 let key = conn.raw.clone();
386 g.checked_out.insert(key.clone(), conn);
387 return Ok(Taken::Reused(key));
388 }
389 if g.size < self.cfg.max_size {
390 g.size += 1;
391 return Ok(Taken::Reserved);
392 }
393 let left = remaining(deadline, self.clock.monotonic());
394 if let Some(l) = left {
395 if l <= 0.0 {
396 return Err(PoolError::Timeout(format!(
397 "pool {:?}: no connection available (max_size={})",
398 self.name, self.cfg.max_size
399 )));
400 }
401 }
402 if let Some(max) = self.cfg.max_waiters {
403 if g.waiters >= max {
404 return Err(PoolError::Exhausted(format!(
405 "pool {:?}: waiter queue full (max_waiters={})",
406 self.name, max
407 )));
408 }
409 }
410 g.waiters += 1;
411 g = match left {
412 None => self.cond.wait(g).unwrap(),
413 Some(l) => {
414 let (g2, _) = self
415 .cond
416 .wait_timeout(g, Duration::from_secs_f64(l.max(0.0)))
417 .unwrap();
418 g2
419 }
420 };
421 g.waiters -= 1;
422 }
423 }
424
425 fn check_borrow(&self, key: &T) -> Option<CloseReason> {
426 let now = self.clock.monotonic();
427 let (reason, need_validate, raw) = {
428 let g = self.state.lock().unwrap();
429 let pooled = g.checked_out.get(key).expect("conn present");
430 (
431 borrow_reason(pooled, &self.cfg, now),
432 needs_validation(pooled, &self.cfg, now),
433 pooled.raw.clone(),
434 )
435 };
436 if let Some(r) = reason {
437 return Some(r);
438 }
439 if need_validate && !self.is_healthy(&raw) {
440 return Some(CloseReason::Unhealthy);
441 }
442 None
443 }
444
445 fn release(
446 &self,
447 conn: T,
448 discard: bool,
449 error: Option<&(dyn Error + 'static)>,
450 ) -> Result<(), PoolError> {
451 let pooled = {
452 let mut g = self.state.lock().unwrap();
453 g.checked_out.remove(&conn)
454 };
455 let pooled = match pooled {
456 Some(p) => p,
457 None => {
458 return Err(PoolError::InvalidRelease(
459 "object is not checked out from this pool (double release?)".to_string(),
460 ));
461 }
462 };
463 let now = self.clock.monotonic();
464 let hold = pooled.hold_time(now);
465 let reason = self.check_release(&pooled, &conn, discard, error, now);
466 self.events.safe(|l| l.on_released(&pooled, hold));
467 match reason {
468 Some(r) => self.destroy_detached(pooled, r),
469 None => {
470 if let Err(p) = self.park(pooled, now) {
471 self.destroy_detached(p, CloseReason::PoolClosed);
472 }
473 }
474 }
475 Ok(())
476 }
477
478 fn check_release(
479 &self,
480 pooled: &PooledConnection<T>,
481 conn: &T,
482 discard: bool,
483 error: Option<&(dyn Error + 'static)>,
484 now: f64,
485 ) -> Option<CloseReason> {
486 if discard {
487 return Some(CloseReason::Discarded);
488 }
489 if let Some(e) = error {
490 if self.factory.is_fatal_error(e) || !self.is_healthy(conn) {
491 return Some(CloseReason::Error);
492 }
493 }
494 if let Some(r) = rotation_reason(pooled, &self.cfg, now) {
495 return Some(r);
496 }
497 if self.reset_conn(conn).is_err() {
498 eprintln!(
499 "connpool: pool {:?}: reset failed; discarding {}",
500 self.name, pooled
501 );
502 return Some(CloseReason::ResetFailed);
503 }
504 None
505 }
506
507 fn park(&self, mut pooled: PooledConnection<T>, now: f64) -> Result<(), PooledConnection<T>> {
508 let mut g = self.state.lock().unwrap();
509 if g.closed {
510 return Err(pooled);
511 }
512 pooled.mark_returned(now);
513 g.idle.push(pooled);
514 self.signal();
515 Ok(())
516 }
517
518 fn create(&self, deadline: Option<f64>) -> Result<(PooledConnection<T>, f64), PoolError> {
519 let started = self.clock.monotonic();
520 let raw = match self.create_raw(deadline) {
521 Ok(r) => r,
522 Err(e) => {
523 self.free_slot();
524 return Err(e);
525 }
526 };
527 let now = self.clock.monotonic();
528 Ok((self.new_conn(raw, now), now - started))
529 }
530
531 fn register_new(
532 &self,
533 pooled: PooledConnection<T>,
534 created_in: f64,
535 ) -> Result<T, PoolError> {
536 let key = pooled.raw.clone();
537 let ev;
538 {
539 let mut g = self.state.lock().unwrap();
540 if g.closed {
541 drop(g);
542 self.events.safe(|l| l.on_connection_created(&pooled, created_in));
543 self.destroy_detached(pooled, CloseReason::PoolClosed);
544 return Err(PoolError::Closed(format!("pool {:?} is closed", self.name)));
545 }
546 if g.checked_out.contains_key(&key) || g.idle.iter().any(|c| c.raw == key) {
547 drop(g);
548 self.free_slot();
549 return Err(PoolError::ConnectionCreate {
550 message: format!(
551 "pool {:?}: factory.create() returned an object the pool already manages; each create must return a distinct connection",
552 self.name
553 ),
554 source: None,
555 });
556 }
557 ev = pooled.clone();
558 g.checked_out.insert(key.clone(), pooled);
559 }
560 self.events.safe(|l| l.on_connection_created(&ev, created_in));
561 Ok(key)
562 }
563
564 fn create_raw(&self, deadline: Option<f64>) -> Result<T, PoolError> {
565 if let Some(b) = self.breaker.get() {
566 b.before_call()?;
567 }
568 match self.create_with_retry(deadline) {
569 Ok(raw) => {
570 if let Some(b) = self.breaker.get() {
571 b.record_success();
572 }
573 Ok(raw)
574 }
575 Err(e) => {
576 if let Some(b) = self.breaker.get() {
577 if matches!(e, PoolError::ConnectionCreate { .. }) {
578 b.record_failure();
579 } else {
580 b.record_abort();
581 }
582 }
583 Err(e)
584 }
585 }
586 }
587
588 fn create_with_retry(&self, deadline: Option<f64>) -> Result<T, PoolError> {
589 let retry = &self.cfg.retry;
590 let mut attempt = 0u32;
591 loop {
592 if self.is_closed() {
593 return Err(PoolError::Closed(format!("pool {:?} is closed", self.name)));
594 }
595 attempt += 1;
596 match self.create_one() {
597 Ok(raw) => return Ok(raw),
598 Err(e) => {
599 self.events.safe(|l| l.on_create_failed(&*e));
600 if attempt >= retry.max_attempts || !retry.should_retry(&*e) {
601 return Err(PoolError::ConnectionCreate {
602 message: format!(
603 "pool {:?}: could not create connection after {} attempt(s): {}",
604 self.name, attempt, e
605 ),
606 source: Some(e),
607 });
608 }
609 let delay = self.retry_delay(attempt);
610 let left = remaining(deadline, self.clock.monotonic());
611 if let Some(l) = left {
612 if delay >= l {
613 return Err(PoolError::Timeout(format!(
614 "pool {:?}: acquire deadline reached while retrying creation",
615 self.name
616 )));
617 }
618 }
619 self.clock.sleep(delay);
620 }
621 }
622 }
623 }
624
625 fn create_one(&self) -> Result<T, FactoryError> {
626 match catch_unwind(AssertUnwindSafe(|| self.factory.create())) {
627 Ok(r) => r,
628 Err(_) => Err("create panicked".into()),
629 }
630 }
631
632 fn is_healthy(&self, raw: &T) -> bool {
633 catch_unwind(AssertUnwindSafe(|| self.factory.validate(raw))).unwrap_or(false)
634 }
635
636 fn reset_conn(&self, conn: &T) -> Result<(), FactoryError> {
637 match catch_unwind(AssertUnwindSafe(|| self.factory.reset(conn))) {
638 Ok(r) => r,
639 Err(_) => Err("reset panicked".into()),
640 }
641 }
642
643 fn free_slot(&self) {
644 let mut g = self.state.lock().unwrap();
645 g.size -= 1;
646 self.signal();
647 }
648
649 fn destroy_by_key(&self, key: &T, reason: CloseReason) {
650 let pooled = {
651 let mut g = self.state.lock().unwrap();
652 g.checked_out.remove(key)
653 };
654 if let Some(p) = pooled {
655 self.destroy_detached(p, reason);
656 }
657 }
658
659 fn destroy_detached(&self, pooled: PooledConnection<T>, reason: CloseReason) {
660 self.close_conn(&pooled);
661 {
662 let mut g = self.state.lock().unwrap();
663 g.size -= 1;
664 self.signal();
665 }
666 self.events.safe(|l| l.on_connection_closed(&pooled, reason));
667 }
668
669 fn close_conn(&self, pooled: &PooledConnection<T>) {
670 let raw = &pooled.raw;
671 let result = catch_unwind(AssertUnwindSafe(|| self.factory.close(raw)));
672 match result {
673 Ok(Ok(())) => {}
674 Ok(Err(e)) => eprintln!("connpool: pool {:?}: error closing {}: {}", self.name, pooled, e),
675 Err(_) => eprintln!("connpool: pool {:?}: panic closing {}", self.name, pooled),
676 }
677 }
678
679 fn fill_to_min(&self) {
680 loop {
681 {
682 let mut g = self.state.lock().unwrap();
683 if g.closed || g.size >= self.cfg.min_size {
684 return;
685 }
686 g.size += 1;
687 }
688 match self.create(None) {
689 Err(e) => {
690 eprintln!("connpool: pool {:?}: warm-up failed: {}", self.name, e);
691 return;
692 }
693 Ok((pooled, created_in)) => {
694 self.events.safe(|l| l.on_connection_created(&pooled, created_in));
695 if let Err(p) = self.park(pooled, self.clock.monotonic()) {
696 self.destroy_detached(p, CloseReason::PoolClosed);
697 }
698 }
699 }
700 }
701 }
702
703 fn run_maintenance(&self) {
704 let doomed = {
705 let mut g = self.state.lock().unwrap();
706 if g.closed {
707 return;
708 }
709 let idle = std::mem::take(&mut g.idle);
710 let (keep, doomed) = sweep(idle, g.size, &self.cfg, self.clock.monotonic());
711 g.idle = keep;
712 doomed
713 };
714 doomed
715 .into_iter()
716 .for_each(|(p, reason)| self.destroy_detached(p, reason));
717 self.health_check_idle();
718 self.fill_to_min();
719 self.check_leaks();
720 }
721
722 fn run_maintenance_safe(&self) {
723 if catch_unwind(AssertUnwindSafe(|| self.run_maintenance())).is_err() {
724 eprintln!("connpool: pool {:?}: maintenance pass failed", self.name);
725 }
726 }
727
728 fn health_check_idle(&self) {
729 if !self.cfg.validate_idle {
730 return;
731 }
732 let keys: Vec<T> = {
733 let mut g = self.state.lock().unwrap();
734 let batch = std::mem::take(&mut g.idle);
735 let mut keys = Vec::with_capacity(batch.len());
736 for conn in batch {
737 let key = conn.raw.clone();
738 g.checked_out.insert(key.clone(), conn);
739 keys.push(key);
740 }
741 keys
742 };
743 for key in keys {
744 let raw = {
745 let g = self.state.lock().unwrap();
746 g.checked_out.get(&key).expect("reserved").raw.clone()
747 };
748 let healthy = self.is_healthy(&raw);
749 let doomed = {
750 let mut g = self.state.lock().unwrap();
751 let pooled = g.checked_out.remove(&key).expect("reserved");
752 if healthy && !g.closed {
753 g.idle.push(pooled);
754 self.signal();
755 None
756 } else {
757 Some(pooled)
758 }
759 };
760 if let Some(p) = doomed {
761 let reason = if healthy {
762 CloseReason::PoolClosed
763 } else {
764 CloseReason::Unhealthy
765 };
766 self.destroy_detached(p, reason);
767 }
768 }
769 }
770
771 fn check_leaks(&self) {
772 let timeout = match self.cfg.leak_detection_timeout {
773 Some(t) => t,
774 None => return,
775 };
776 let now = self.clock.monotonic();
777 let leaked: Vec<(PooledConnection<T>, f64)> = {
778 let mut g = self.state.lock().unwrap();
779 let mut out = Vec::new();
780 for pooled in g.checked_out.values_mut() {
781 if !pooled.leak_reported && pooled.hold_time(now) >= timeout {
782 pooled.leak_reported = true;
783 let held = pooled.hold_time(now);
784 out.push((pooled.clone(), held));
785 }
786 }
787 out
788 };
789 for (pooled, held) in leaked {
790 eprintln!(
791 "connpool: pool {:?}: possible connection leak: {} held for {:.1}s",
792 self.name, pooled, held
793 );
794 self.events.safe(|l| l.on_connection_leak(&pooled, held));
795 }
796 }
797
798 fn stats_snapshot(&self) -> PoolStats {
799 let (name, max_size, size, idle, in_use, waiting) = {
800 let g = self.state.lock().unwrap();
801 (
802 self.name.clone(),
803 self.cfg.max_size,
804 g.size,
805 g.idle.len(),
806 g.checked_out.len(),
807 g.waiters,
808 )
809 };
810 let breaker_state = self.breaker.get().map(|b| b.state().as_str().to_string());
811 self.stats.snapshot(StatsGauges {
812 name,
813 max_size,
814 size,
815 idle,
816 in_use,
817 waiting,
818 breaker_state,
819 })
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use super::*;
826 use crate::resilience::breaker::BreakerConfig;
827 use crate::resilience::retry::RetryPolicy;
828 use crate::test_support::*;
829 use std::sync::atomic::Ordering;
830 use std::sync::Arc;
831
832 fn plain(max_size: usize) -> PoolConfig {
834 PoolConfig {
835 max_size,
836 maintenance_interval: None,
837 breaker: None,
838 ..Default::default()
839 }
840 }
841
842 #[test]
843 fn acquire_release_reuses_same_connection() {
844 let h = Harness::new();
845 let pool = h.pool(plain(2));
846 let a = pool.acquire(Timeout::Unset).unwrap();
847 let id_a = a.id;
848 pool.release(a).unwrap();
849 let b = pool.acquire(Timeout::Unset).unwrap();
850 assert_eq!(b.id, id_a, "LIFO reuse returns same connection");
851 pool.release(b).unwrap();
852 let s = pool.stats();
853 assert_eq!(s.created, 1);
854 assert_eq!(s.acquired, 2);
855 assert_eq!(s.released, 2);
856 assert_eq!(pool.name(), "test");
857 }
858
859 #[test]
860 fn fifo_strategy_rotates() {
861 let h = Harness::new();
862 let pool = h.pool(PoolConfig {
863 idle_strategy: crate::policy::config::IdleStrategy::Fifo,
864 ..plain(3)
865 });
866 let a = pool.acquire(Timeout::Unset).unwrap();
867 let b = pool.acquire(Timeout::Unset).unwrap();
868 pool.release(a).unwrap();
869 pool.release(b).unwrap();
870 let c = pool.acquire(Timeout::Unset).unwrap();
872 assert_eq!(c.id, a.id);
873 }
874
875 #[test]
876 fn max_size_is_a_hard_cap_and_zero_timeout_fails_fast() {
877 let h = Harness::new();
878 let pool = h.pool(plain(1));
879 let a = pool.acquire(Timeout::Unset).unwrap();
880 let err = pool.acquire(Timeout::Of(0.0)).unwrap_err();
881 assert!(matches!(err, PoolError::Timeout(_)));
882 assert_eq!(pool.stats().timeouts, 1);
883 pool.release(a).unwrap();
884 }
885
886 #[test]
887 fn max_waiters_backpressure() {
888 let h = Harness::new();
889 let pool = h.pool(PoolConfig {
890 max_waiters: Some(0),
891 ..plain(1)
892 });
893 let a = pool.acquire(Timeout::Unset).unwrap();
894 assert!(matches!(
896 pool.acquire(Timeout::Infinite),
897 Err(PoolError::Exhausted(_))
898 ));
899 pool.release(a).unwrap();
900 }
901
902 #[test]
903 fn blocked_waiter_is_woken_on_release() {
904 let h = Harness::new();
905 let pool = h.pool(plain(1));
906 let a = pool.acquire(Timeout::Unset).unwrap();
907 let p2 = pool.clone();
908 let handle = std::thread::spawn(move || p2.acquire(Timeout::Infinite).map(|c| c.id));
909 std::thread::sleep(std::time::Duration::from_millis(30));
911 pool.release(a).unwrap();
912 let got = handle.join().unwrap().unwrap();
913 assert_eq!(got, a.id);
914 pool.release(Conn::new(got)).unwrap();
915 }
916
917 #[test]
918 fn timed_wait_eventually_times_out() {
919 let h = Harness::new();
920 let pool = ConnectionPool::new(
922 h.factory(),
923 plain(1),
924 Some(PoolOptions {
925 name: Some("rt".into()),
926 clock: None,
927 rng: None,
928 listeners: vec![],
929 }),
930 )
931 .unwrap();
932 let a = pool.acquire(Timeout::Unset).unwrap();
933 let err = pool.acquire(Timeout::Of(0.05)).unwrap_err();
934 assert!(matches!(err, PoolError::Timeout(_)));
935 pool.release(a).unwrap();
936 }
937
938 #[test]
939 fn invalid_release_detected() {
940 let h = Harness::new();
941 let pool = h.pool(plain(10));
942 assert!(matches!(
943 pool.release(Conn::new(9999)).unwrap_err(),
944 PoolError::InvalidRelease(_)
945 ));
946 }
947
948 #[test]
949 fn with_connection_runs_and_releases() {
950 let h = Harness::new();
951 let pool = h.pool(plain(10));
952 let out = pool.with_connection(Timeout::Unset, |c| c.id).unwrap();
953 assert!(out >= 1);
954 assert_eq!(pool.stats().released, 1);
955 let full = h.pool(plain(1));
957 let _held = full.acquire(Timeout::Unset).unwrap();
958 assert!(full.with_connection(Timeout::Of(0.0), |c| c.id).is_err());
959 }
960
961 #[test]
962 fn config_validation_surfaces_on_new() {
963 let h = Harness::new();
964 let result = ConnectionPool::new(h.factory(), PoolConfig { max_size: 0, ..Default::default() }, None);
965 assert!(matches!(result, Err(PoolError::Config(_))));
966 }
967
968 #[test]
969 fn discard_and_error_release_close_connection() {
970 let h = Harness::new();
971 let pool = h.pool(plain(5));
972 let a = pool.acquire(Timeout::Unset).unwrap();
973 pool.discard(a).unwrap();
974 assert_eq!(h.fac.closed_ids(), vec![a.id]);
975
976 let b = pool.acquire(Timeout::Unset).unwrap();
978 let err: FactoryError = "oops".into();
979 pool.release_with_error(b, &*err).unwrap();
980 assert_eq!(h.fac.closed_ids().len(), 1);
981
982 h.fac.set_validate_ok(false);
984 let c = pool.acquire(Timeout::Unset).unwrap();
985 let err2: FactoryError = "bad".into();
986 pool.release_with_error(c, &*err2).unwrap();
987 assert!(h.fac.closed_ids().contains(&c.id));
988 let s = pool.stats();
989 assert_eq!(s.closed_by_reason.get("discarded"), Some(&1));
990 assert_eq!(s.closed_by_reason.get("error"), Some(&1));
991 }
992
993 #[test]
994 fn fatal_error_closes_without_validation() {
995 let h = Harness::new();
996 h.fac.set_fatal(true);
997 let pool = h.pool(plain(5));
998 let a = pool.acquire(Timeout::Unset).unwrap();
999 let err: FactoryError = "fatal".into();
1000 pool.release_with_error(a, &*err).unwrap();
1001 assert!(h.fac.closed_ids().contains(&a.id));
1002 }
1003
1004 #[test]
1005 fn reset_failure_discards_on_release() {
1006 let h = Harness::new();
1007 h.fac.set_reset_ok(false);
1008 let pool = h.pool(plain(5));
1009 let a = pool.acquire(Timeout::Unset).unwrap();
1010 pool.release(a).unwrap();
1011 assert!(h.fac.closed_ids().contains(&a.id));
1012 assert_eq!(pool.stats().closed_by_reason.get("reset_failed"), Some(&1));
1013 }
1014
1015 #[test]
1016 fn rotation_on_release_when_expired_or_overused() {
1017 let h = Harness::new();
1018 let pool = h.pool(PoolConfig {
1020 max_uses: Some(1),
1021 max_lifetime: None,
1022 ..plain(5)
1023 });
1024 let a = pool.acquire(Timeout::Unset).unwrap();
1025 pool.release(a).unwrap();
1026 assert!(h.fac.closed_ids().contains(&a.id));
1027 assert_eq!(pool.stats().closed_by_reason.get("max_uses"), Some(&1));
1028 }
1029
1030 #[test]
1031 fn stale_idle_connection_is_replaced_on_acquire() {
1032 let h = Harness::new();
1033 let pool = h.pool(PoolConfig {
1034 max_lifetime: Some(100.0),
1035 lifetime_jitter: 0.0,
1036 max_idle_time: None,
1037 validate_after_idle: None,
1038 ..plain(5)
1039 });
1040 let a = pool.acquire(Timeout::Unset).unwrap();
1041 pool.release(a).unwrap();
1042 h.clock.advance(200.0);
1044 let b = pool.acquire(Timeout::Unset).unwrap();
1045 assert_ne!(b.id, a.id);
1046 assert!(h.fac.closed_ids().contains(&a.id));
1047 }
1048
1049 #[test]
1050 fn validate_on_checkout_replaces_unhealthy() {
1051 let h = Harness::new();
1052 let pool = h.pool(PoolConfig {
1053 validate_after_idle: Some(5.0),
1054 max_lifetime: None,
1055 max_idle_time: None,
1056 ..plain(5)
1057 });
1058 let a = pool.acquire(Timeout::Unset).unwrap();
1059 pool.release(a).unwrap();
1060 h.clock.advance(10.0); h.fac.set_validate_ok(false); let b = pool.acquire(Timeout::Unset).unwrap();
1063 assert_ne!(b.id, a.id);
1064 assert_eq!(pool.stats().closed_by_reason.get("unhealthy"), Some(&1));
1065 }
1066
1067 #[test]
1068 fn create_failure_after_retries_surfaces_connection_create_error() {
1069 let h = Harness::new();
1070 h.fac.fail_next_creates(10);
1071 let pool = h.pool(PoolConfig {
1072 retry: RetryPolicy {
1073 max_attempts: 3,
1074 base_delay: 0.0,
1075 ..Default::default()
1076 },
1077 ..plain(2)
1078 });
1079 let err = pool.acquire(Timeout::Unset).unwrap_err();
1080 assert!(matches!(err, PoolError::ConnectionCreate { .. }));
1081 assert_eq!(pool.stats().create_failures, 3);
1082 h.fac.fail_next_creates(0);
1084 assert!(pool.acquire(Timeout::Unset).is_ok());
1085 }
1086
1087 #[test]
1088 fn create_retry_then_success() {
1089 let h = Harness::new();
1090 h.fac.fail_next_creates(1);
1091 let pool = h.pool(PoolConfig {
1092 retry: RetryPolicy {
1093 max_attempts: 3,
1094 base_delay: 0.0,
1095 ..Default::default()
1096 },
1097 ..plain(2)
1098 });
1099 let a = pool.acquire(Timeout::Unset).unwrap();
1100 assert_eq!(pool.stats().create_failures, 1);
1101 pool.release(a).unwrap();
1102 }
1103
1104 #[test]
1105 fn retry_delay_exceeding_deadline_times_out() {
1106 let h = Harness::new();
1107 h.fac.fail_next_creates(10);
1108 let pool = h.pool(PoolConfig {
1109 acquire_timeout: Some(0.05),
1110 retry: RetryPolicy {
1111 max_attempts: 5,
1112 base_delay: 100.0,
1113 jitter: 0.0,
1114 ..Default::default()
1115 },
1116 ..plain(2)
1117 });
1118 let err = pool.acquire(Timeout::Unset).unwrap_err();
1120 assert!(matches!(err, PoolError::Timeout(_)));
1121 }
1122
1123 #[test]
1124 fn create_panic_is_contained() {
1125 let h = Harness::new();
1126 h.fac.set_create_panic(true);
1127 let pool = h.pool(PoolConfig {
1128 retry: RetryPolicy::no_retry(),
1129 ..plain(2)
1130 });
1131 assert!(matches!(
1132 pool.acquire(Timeout::Unset),
1133 Err(PoolError::ConnectionCreate { .. })
1134 ));
1135 }
1136
1137 #[test]
1138 fn duplicate_object_rejected() {
1139 let h = Harness::new();
1140 h.fac.force_id(Some(42));
1141 let pool = h.pool(plain(5));
1142 let _a = pool.acquire(Timeout::Unset).unwrap(); let err = pool.acquire(Timeout::Unset).unwrap_err();
1145 assert!(matches!(err, PoolError::ConnectionCreate { .. }));
1146 }
1147
1148 #[test]
1149 fn breaker_trips_then_blocks_then_recovers() {
1150 let h = Harness::new();
1151 h.fac.fail_next_creates(100);
1152 let pool = h.pool(PoolConfig {
1153 retry: RetryPolicy::no_retry(),
1154 maintenance_interval: None,
1155 breaker: Some(BreakerConfig {
1156 failure_threshold: 1,
1157 recovery_timeout: 10.0,
1158 half_open_max_calls: 1,
1159 success_threshold: 1,
1160 }),
1161 ..Default::default()
1162 });
1163 assert!(matches!(
1165 pool.acquire(Timeout::Unset),
1166 Err(PoolError::ConnectionCreate { .. })
1167 ));
1168 assert!(matches!(
1170 pool.acquire(Timeout::Unset),
1171 Err(PoolError::CircuitOpen { .. })
1172 ));
1173 assert_eq!(pool.stats().breaker_state.as_deref(), Some("open"));
1174 h.clock.advance(10.0);
1176 h.fac.fail_next_creates(0);
1177 let a = pool.acquire(Timeout::Unset).unwrap();
1178 assert_eq!(pool.stats().breaker_state.as_deref(), Some("closed"));
1179 pool.release(a).unwrap();
1180 }
1181
1182 #[test]
1183 fn breaker_abort_does_not_trip() {
1184 let h = Harness::new();
1185 h.fac.fail_next_creates(100);
1186 let pool = h.pool(PoolConfig {
1187 acquire_timeout: Some(0.05),
1188 retry: RetryPolicy {
1189 max_attempts: 5,
1190 base_delay: 100.0,
1191 jitter: 0.0,
1192 ..Default::default()
1193 },
1194 maintenance_interval: None,
1195 breaker: Some(BreakerConfig {
1196 failure_threshold: 1,
1197 ..Default::default()
1198 }),
1199 ..Default::default()
1200 });
1201 assert!(matches!(pool.acquire(Timeout::Unset), Err(PoolError::Timeout(_))));
1203 assert_eq!(pool.stats().breaker_state.as_deref(), Some("closed"));
1204 }
1205
1206 #[test]
1207 fn open_warms_up_and_close_drains() {
1208 let h = Harness::new();
1209 let rec = Recorder::new();
1210 let pool = h.pool_with(
1211 PoolConfig {
1212 min_size: 2,
1213 maintenance_interval: None,
1214 breaker: None,
1215 ..Default::default()
1216 },
1217 vec![rec.clone()],
1218 );
1219 pool.open().unwrap();
1220 assert!(pool.open().is_ok()); assert_eq!(pool.stats().idle, 2);
1222 assert_eq!(rec.created.load(Ordering::SeqCst), 2);
1223 pool.close(Some(0.0));
1224 pool.close(None); assert!(pool.is_closed());
1226 assert_eq!(rec.closed.load(Ordering::SeqCst), 2);
1227 assert!(matches!(pool.open(), Err(PoolError::Closed(_))));
1229 }
1230
1231 #[test]
1232 fn close_drains_checked_out_with_timeout() {
1233 let h = Harness::new();
1234 let pool = h.pool(plain(2));
1235 let a = pool.acquire(Timeout::Unset).unwrap();
1236 let p2 = pool.clone();
1237 let closer = std::thread::spawn(move || p2.close(Some(5.0)));
1238 std::thread::sleep(std::time::Duration::from_millis(30));
1239 pool.release(a).unwrap(); closer.join().unwrap();
1241 assert!(pool.is_closed());
1242 }
1243
1244 #[test]
1245 fn release_into_closed_pool_closes_connection() {
1246 let h = Harness::new();
1247 let pool = h.pool(plain(2));
1248 let a = pool.acquire(Timeout::Unset).unwrap();
1249 pool.close(None);
1250 pool.release(a).unwrap();
1252 assert!(h.fac.closed_ids().contains(&a.id));
1253 }
1254
1255 #[test]
1256 fn maintenance_sweeps_and_refills() {
1257 let h = Harness::new();
1258 let pool = h.pool(PoolConfig {
1259 min_size: 1,
1260 max_lifetime: Some(100.0),
1261 lifetime_jitter: 0.0,
1262 max_idle_time: Some(50.0),
1263 validate_after_idle: None,
1264 maintenance_interval: None,
1265 breaker: None,
1266 ..Default::default()
1267 });
1268 pool.open().unwrap();
1269 let first_idle = pool.stats().idle;
1270 assert_eq!(first_idle, 1);
1271 h.clock.advance(200.0);
1273 pool.run_maintenance();
1274 let s = pool.stats();
1275 assert_eq!(s.idle, 1); assert!(s.closed >= 1); pool.close(None);
1278 }
1279
1280 #[test]
1281 fn maintenance_noop_when_closed() {
1282 let h = Harness::new();
1283 let pool = h.pool(plain(2));
1284 pool.close(None);
1285 pool.run_maintenance(); }
1287
1288 #[test]
1289 fn health_check_idle_evicts_unhealthy() {
1290 let h = Harness::new();
1291 let pool = h.pool(PoolConfig {
1292 validate_idle: true,
1293 max_lifetime: None,
1294 max_idle_time: None,
1295 validate_after_idle: None,
1296 maintenance_interval: None,
1297 breaker: None,
1298 ..Default::default()
1299 });
1300 let a = pool.acquire(Timeout::Unset).unwrap();
1301 pool.release(a).unwrap();
1302 pool.run_maintenance();
1304 assert_eq!(pool.stats().idle, 1);
1305 h.fac.set_validate_ok(false);
1307 pool.run_maintenance();
1308 assert_eq!(pool.stats().idle, 0);
1309 assert_eq!(pool.stats().closed_by_reason.get("unhealthy"), Some(&1));
1310 }
1311
1312 #[test]
1313 fn leak_detection_warns_once() {
1314 let h = Harness::new();
1315 let rec = Recorder::new();
1316 let pool = h.pool_with(
1317 PoolConfig {
1318 leak_detection_timeout: Some(10.0),
1319 maintenance_interval: None,
1320 breaker: None,
1321 ..Default::default()
1322 },
1323 vec![rec.clone()],
1324 );
1325 let a = pool.acquire(Timeout::Unset).unwrap();
1326 h.clock.advance(20.0); pool.run_maintenance();
1328 pool.run_maintenance(); assert_eq!(rec.leaks.load(Ordering::SeqCst), 1);
1330 pool.release(a).unwrap();
1331 }
1332
1333 #[test]
1334 fn background_maintenance_thread_runs_and_stops() {
1335 let h = Harness::new();
1336 let pool = h.pool(PoolConfig {
1337 min_size: 0,
1338 maintenance_interval: Some(0.01),
1339 breaker: None,
1340 ..Default::default()
1341 });
1342 pool.open().unwrap();
1343 std::thread::sleep(std::time::Duration::from_millis(40)); pool.close(None); assert!(pool.is_closed());
1346 }
1347
1348 #[test]
1349 fn listener_panic_does_not_break_pool() {
1350 struct Boom;
1351 impl PoolListener<Conn> for Boom {
1352 fn on_acquired(&self, _c: &PooledConnection<Conn>, _w: f64) {
1353 panic!("listener boom");
1354 }
1355 }
1356 let h = Harness::new();
1357 let pool = h.pool_with(plain(2), vec![Arc::new(Boom)]);
1358 let a = pool.acquire(Timeout::Unset).unwrap(); pool.release(a).unwrap();
1360 }
1361
1362 #[test]
1363 fn breaker_state_change_reaches_listeners() {
1364 let h = Harness::new();
1365 let rec = Recorder::new();
1366 h.fac.fail_next_creates(100);
1367 let pool = h.pool_with(
1368 PoolConfig {
1369 retry: RetryPolicy::no_retry(),
1370 maintenance_interval: None,
1371 breaker: Some(BreakerConfig { failure_threshold: 1, ..Default::default() }),
1372 ..Default::default()
1373 },
1374 vec![rec.clone()],
1375 );
1376 let _ = pool.acquire(Timeout::Unset); assert!(!rec.breaker_changes.lock().unwrap().is_empty());
1378 }
1379
1380 #[test]
1381 fn waiter_under_max_waiters_blocks_then_succeeds() {
1382 let h = Harness::new();
1383 let pool = h.pool(PoolConfig { max_waiters: Some(5), ..plain(1) });
1384 let a = pool.acquire(Timeout::Unset).unwrap();
1385 let p2 = pool.clone();
1386 let waiter = std::thread::spawn(move || p2.acquire(Timeout::Infinite).map(|c| c.id));
1387 std::thread::sleep(std::time::Duration::from_millis(30));
1388 pool.release(a).unwrap();
1389 assert_eq!(waiter.join().unwrap().unwrap(), a.id);
1390 pool.release(Conn::new(a.id)).unwrap();
1391 }
1392
1393 #[test]
1394 fn retry_sleeps_without_deadline() {
1395 let h = Harness::new();
1396 h.fac.fail_next_creates(1);
1397 let pool = h.pool(PoolConfig {
1398 acquire_timeout: None, retry: RetryPolicy { max_attempts: 3, base_delay: 1.0, jitter: 0.0, ..Default::default() },
1400 ..plain(2)
1401 });
1402 let a = pool.acquire(Timeout::Unset).unwrap(); pool.release(a).unwrap();
1404 }
1405
1406 #[test]
1407 fn validate_panic_on_checkout_treated_unhealthy() {
1408 let h = Harness::new();
1409 let pool = h.pool(PoolConfig {
1410 validate_after_idle: Some(5.0),
1411 max_lifetime: None,
1412 max_idle_time: None,
1413 ..plain(5)
1414 });
1415 let a = pool.acquire(Timeout::Unset).unwrap();
1416 pool.release(a).unwrap();
1417 h.clock.advance(10.0);
1418 h.fac.set_validate_panic(true);
1419 let b = pool.acquire(Timeout::Unset).unwrap(); assert_ne!(b.id, a.id);
1421 pool.release(b).unwrap();
1422 }
1423
1424 #[test]
1425 fn reset_panic_discards() {
1426 let h = Harness::new();
1427 h.fac.set_reset_panic(true);
1428 let pool = h.pool(plain(5));
1429 let a = pool.acquire(Timeout::Unset).unwrap();
1430 pool.release(a).unwrap();
1431 assert_eq!(pool.stats().closed_by_reason.get("reset_failed"), Some(&1));
1432 }
1433
1434 #[test]
1435 fn close_errors_and_panics_are_logged() {
1436 let h = Harness::new();
1437 h.fac.set_close_err(true);
1438 let pool = h.pool(plain(5));
1439 let a = pool.acquire(Timeout::Unset).unwrap();
1440 pool.discard(a).unwrap(); assert_eq!(pool.stats().closed, 1);
1442
1443 h.fac.set_close_err(false);
1444 h.fac.set_close_panic(true);
1445 let b = pool.acquire(Timeout::Unset).unwrap();
1446 pool.discard(b).unwrap(); assert_eq!(pool.stats().closed, 2);
1448 }
1449
1450 #[test]
1451 fn warmup_failure_is_logged_and_stops() {
1452 let h = Harness::new();
1453 h.fac.fail_next_creates(100);
1454 let pool = h.pool(PoolConfig {
1455 min_size: 2,
1456 retry: RetryPolicy::no_retry(),
1457 maintenance_interval: None,
1458 breaker: None,
1459 ..Default::default()
1460 });
1461 pool.open().unwrap(); assert_eq!(pool.stats().idle, 0);
1463 }
1464
1465 #[test]
1466 fn pool_closed_during_create_registers_as_closed() {
1467 let slot: Arc<Mutex<Option<ConnectionPool<Conn>>>> = Arc::new(Mutex::new(None));
1469 let s2 = slot.clone();
1470 let factory = crate::factories::sync_factory::FnFactory::new(move || {
1471 if let Some(p) = s2.lock().unwrap().as_ref() {
1472 p.close(None);
1473 }
1474 Ok::<_, FactoryError>(Conn::new(7))
1475 });
1476 let pool = ConnectionPool::new(factory, plain(2), None).unwrap();
1477 *slot.lock().unwrap() = Some(pool.clone());
1478 assert!(matches!(pool.acquire(Timeout::Unset), Err(PoolError::Closed(_))));
1480 }
1481
1482 #[test]
1483 fn pool_closed_mid_retry_loop() {
1484 let slot: Arc<Mutex<Option<ConnectionPool<Conn>>>> = Arc::new(Mutex::new(None));
1486 let s2 = slot.clone();
1487 let factory = crate::factories::sync_factory::FnFactory::new(move || {
1488 if let Some(p) = s2.lock().unwrap().as_ref() {
1489 p.close(None);
1490 }
1491 Err::<Conn, FactoryError>("boom".into())
1492 });
1493 let pool = ConnectionPool::new(
1494 factory,
1495 PoolConfig {
1496 retry: RetryPolicy { max_attempts: 3, base_delay: 0.0, ..Default::default() },
1497 ..plain(2)
1498 },
1499 None,
1500 )
1501 .unwrap();
1502 *slot.lock().unwrap() = Some(pool.clone());
1503 assert!(matches!(pool.acquire(Timeout::Unset), Err(PoolError::Closed(_))));
1504 }
1505
1506 #[test]
1507 fn maintenance_panic_is_contained() {
1508 let h = Harness::new();
1509 let pool = h.pool(plain(2));
1510 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1512 let _g = pool.0.state.lock().unwrap();
1513 panic!("poison");
1514 }));
1515 pool.0.run_maintenance_safe(); }
1517
1518 #[test]
1519 fn close_with_timeout_gives_up_when_deadline_passed() {
1520 let h = Harness::new();
1521 let pool = h.pool(plain(2));
1522 let a = pool.acquire(Timeout::Unset).unwrap();
1523 pool.close(Some(0.0));
1525 assert!(pool.is_closed());
1526 pool.release(a).unwrap(); }
1528
1529 #[test]
1530 fn blocked_waiter_wakes_as_closed_when_pool_closes() {
1531 let h = Harness::new();
1532 let pool = h.pool(plain(1));
1533 let a = pool.acquire(Timeout::Unset).unwrap();
1534 let p2 = pool.clone();
1535 let waiter = std::thread::spawn(move || p2.acquire(Timeout::Infinite));
1536 std::thread::sleep(std::time::Duration::from_millis(30));
1537 pool.close(None); let res = waiter.join().unwrap();
1539 assert!(matches!(res, Err(PoolError::Closed(_))));
1540 pool.release(a).unwrap();
1541 }
1542
1543 #[test]
1544 fn fill_to_min_park_fails_when_closed_mid_create() {
1545 let slot: Arc<Mutex<Option<ConnectionPool<Conn>>>> = Arc::new(Mutex::new(None));
1548 let s2 = slot.clone();
1549 let factory = crate::factories::sync_factory::FnFactory::new(move || {
1550 if let Some(p) = s2.lock().unwrap().as_ref() {
1551 p.close(None);
1552 }
1553 Ok::<_, FactoryError>(Conn::new(7))
1554 });
1555 let pool = ConnectionPool::new(
1556 factory,
1557 PoolConfig {
1558 min_size: 1,
1559 retry: RetryPolicy::no_retry(),
1560 maintenance_interval: None,
1561 breaker: None,
1562 ..Default::default()
1563 },
1564 None,
1565 )
1566 .unwrap();
1567 *slot.lock().unwrap() = Some(pool.clone());
1568 pool.open().unwrap(); assert!(pool.is_closed());
1570 }
1571
1572 #[test]
1573 fn health_check_closed_mid_validation_uses_pool_closed_reason() {
1574 let slot: Arc<Mutex<Option<ConnectionPool<Conn>>>> = Arc::new(Mutex::new(None));
1577 let close_on_validate = Arc::new(std::sync::atomic::AtomicBool::new(false));
1578 let s2 = slot.clone();
1579 let flag = close_on_validate.clone();
1580 let next = Arc::new(std::sync::atomic::AtomicU64::new(0));
1581 let factory = crate::factories::sync_factory::FnFactory::new(move || {
1582 Ok::<_, FactoryError>(Conn::new(next.fetch_add(1, Ordering::SeqCst) + 1))
1583 })
1584 .with_validate(move |_c| {
1585 if flag.load(Ordering::SeqCst) {
1586 s2.lock().unwrap().as_ref().unwrap().close(None);
1587 }
1588 true
1589 });
1590 let pool = ConnectionPool::new(
1591 factory,
1592 PoolConfig {
1593 validate_idle: true,
1594 validate_after_idle: None,
1595 max_lifetime: None,
1596 max_idle_time: None,
1597 maintenance_interval: None,
1598 breaker: None,
1599 ..Default::default()
1600 },
1601 None,
1602 )
1603 .unwrap();
1604 *slot.lock().unwrap() = Some(pool.clone());
1605 let a = pool.acquire(Timeout::Unset).unwrap();
1606 pool.release(a).unwrap(); close_on_validate.store(true, Ordering::SeqCst);
1608 pool.run_maintenance(); assert!(pool.is_closed());
1610 }
1611
1612 #[test]
1613 fn default_options_when_none() {
1614 let h = Harness::new();
1616 let pool = ConnectionPool::new(h.factory(), plain(2), None).unwrap();
1617 assert_eq!(pool.name(), "pool");
1618 let a = pool.acquire(Timeout::Unset).unwrap();
1619 pool.release(a).unwrap();
1620 }
1621
1622 #[test]
1623 fn dropping_pool_without_close_stops_sweeper() {
1624 let h = Harness::new();
1625 let pool = h.pool(PoolConfig {
1626 maintenance_interval: Some(0.01),
1627 breaker: None,
1628 ..Default::default()
1629 });
1630 pool.open().unwrap();
1631 drop(pool); std::thread::sleep(std::time::Duration::from_millis(30));
1633 }
1634}