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