Skip to main content

connpool/pools/
async_pool.rs

1//! `AsyncConnectionPool`: the async twin of [`ConnectionPool`](crate::ConnectionPool), built on
2//! tokio (feature `tokio`).
3//!
4//! Same model and rules as the blocking pool; the differences are Rust's async idioms:
5//!
6//! - **Cancellation is "drop the future".** There is no cancellation token. A caller that stops
7//!   awaiting mid-acquire never leaks a slot: Drop guards unwind a reserved slot
8//!   ([`SlotGuard`]) or return a briefly-borrowed connection ([`BorrowGuard`]).
9//! - **Real time drives waits/timeouts** (`tokio::time`, `tokio::sync::Notify`); the injected
10//!   [`Clock`](crate::Clock) drives only logical time (lifetime, idle, breaker recovery, backoff)
11//!   — exactly as Python's `asyncio.timeout` ignores the injected clock.
12//! - **`create_timeout` is enforced here** (via `tokio::time::timeout`) rather than left to the
13//!   factory, and disposal is shielded by running `close` on a detached task.
14//!
15//! The pool is generic over the factory type `F` because [`AsyncConnectionFactory`] uses
16//! `async fn` in trait (not `dyn`-safe). `T` is pooled by value identity (`Clone + Eq + Hash`).
17
18use std::collections::HashMap;
19use std::error::Error;
20use std::hash::Hash;
21use std::sync::{Arc, Mutex, OnceLock, Weak};
22use std::time::Duration;
23
24use tokio::sync::Notify;
25use tokio::task::JoinHandle;
26use tokio::time::timeout as tokio_timeout;
27
28use crate::core::clock::{Clock, SystemClock};
29use crate::core::connection::{CloseReason, PooledConnection};
30use crate::core::errors::{FactoryError, PoolError};
31use crate::core::timeouts::{Timeout, remaining};
32use crate::factories::async_factory::AsyncConnectionFactory;
33use crate::observability::events::{Listeners, PoolListener};
34use crate::observability::stats::{PoolStats, StatsCollector, StatsGauges};
35use crate::policy::config::PoolConfig;
36use crate::policy::lifecycle::{
37    borrow_reason, needs_validation, new_connection, pop_idle, rotation_reason, sweep,
38};
39use crate::pools::sync_pool::PoolOptions;
40use crate::resilience::breaker::{BreakerState, CircuitBreaker};
41use crate::rng::{Rng, SystemRng};
42
43struct State<T> {
44    idle: Vec<PooledConnection<T>>,
45    checked_out: HashMap<T, PooledConnection<T>>,
46    size: usize,
47    waiters: usize,
48    closed: bool,
49    opened: bool,
50}
51
52struct Inner<T, F> {
53    name: String,
54    factory: F,
55    cfg: PoolConfig,
56    clock: Arc<dyn Clock>,
57    rng: Mutex<Box<dyn Rng>>,
58
59    state: Mutex<State<T>>,
60    notify: Notify,
61
62    stats: Arc<StatsCollector<T>>,
63    events: Listeners<T>,
64    breaker: OnceLock<CircuitBreaker>,
65
66    stop: Notify,
67    handle: Mutex<Option<JoinHandle<()>>>,
68}
69
70/// A tokio-based, cancel-safe connection pool. Cheaply clonable (clones share one pool).
71pub struct AsyncConnectionPool<T, F>(Arc<Inner<T, F>>);
72
73impl<T, F> Clone for AsyncConnectionPool<T, F> {
74    fn clone(&self) -> Self {
75        AsyncConnectionPool(self.0.clone())
76    }
77}
78
79// -- cancel-safety guards ---------------------------------------------------
80
81/// Frees a reserved slot if dropped before [`commit`](SlotGuard::commit). A connection, once
82/// created, is registered synchronously (no await in between), so the only cancellation window
83/// is while the slot is reserved but empty — hence freeing the slot is all that's needed.
84struct SlotGuard<T, F> {
85    inner: Arc<Inner<T, F>>,
86    armed: bool,
87}
88
89impl<T, F> SlotGuard<T, F>
90where
91    T: Clone + Eq + Hash + Send + Sync + 'static,
92    F: AsyncConnectionFactory<T> + 'static,
93{
94    fn new(inner: Arc<Inner<T, F>>) -> Self {
95        Self { inner, armed: true }
96    }
97    fn commit(mut self) {
98        self.armed = false;
99    }
100}
101
102impl<T, F> Drop for SlotGuard<T, F> {
103    fn drop(&mut self) {
104        if self.armed {
105            let mut g = self.inner.state.lock().unwrap();
106            g.size -= 1;
107            self.inner.notify.notify_waiters();
108        }
109    }
110}
111
112/// Returns a briefly-borrowed connection to the pool if dropped before
113/// [`commit`](BorrowGuard::commit) — i.e. the caller cancelled during borrow validation. The
114/// connection goes back to idle (or is disposed on a detached task if the pool has closed),
115/// so no slot leaks.
116struct BorrowGuard<T, F>
117where
118    T: Clone + Eq + Hash + Send + Sync + 'static,
119    F: AsyncConnectionFactory<T> + 'static,
120{
121    inner: Arc<Inner<T, F>>,
122    key: T,
123    armed: bool,
124}
125
126impl<T, F> BorrowGuard<T, F>
127where
128    T: Clone + Eq + Hash + Send + Sync + 'static,
129    F: AsyncConnectionFactory<T> + 'static,
130{
131    fn new(inner: Arc<Inner<T, F>>, key: T) -> Self {
132        Self {
133            inner,
134            key,
135            armed: true,
136        }
137    }
138    fn commit(mut self) {
139        self.armed = false;
140    }
141}
142
143impl<T, F> Drop for BorrowGuard<T, F>
144where
145    T: Clone + Eq + Hash + Send + Sync + 'static,
146    F: AsyncConnectionFactory<T> + 'static,
147{
148    fn drop(&mut self) {
149        if !self.armed {
150            return;
151        }
152        let mut g = self.inner.state.lock().unwrap();
153        // A borrowed connection stays in checked_out until its guard commits, so an armed guard
154        // always finds it here.
155        let mut p = g
156            .checked_out
157            .remove(&self.key)
158            .expect("borrowed connection present until guard commits");
159        if g.closed {
160            // Pool is shutting down: give the slot back; the connection is dropped (its own
161            // Drop releases the backend resource).
162            g.size -= 1;
163        } else {
164            let now = self.inner.clock.monotonic();
165            p.mark_returned(now);
166            g.idle.push(p);
167        }
168        self.inner.notify.notify_waiters();
169    }
170}
171
172impl<T, F> AsyncConnectionPool<T, F>
173where
174    T: Clone + Eq + Hash + Send + Sync + 'static,
175    F: AsyncConnectionFactory<T> + 'static,
176{
177    /// Builds an async pool, validating `config`.
178    pub fn new(
179        factory: F,
180        config: PoolConfig,
181        options: Option<PoolOptions<T>>,
182    ) -> Result<Self, PoolError> {
183        config.validate()?;
184        let options = options.unwrap_or_default();
185        let clock: Arc<dyn Clock> = options.clock.unwrap_or_else(|| Arc::new(SystemClock::new()));
186        let rng: Box<dyn Rng> = options.rng.unwrap_or_else(|| Box::new(SystemRng::new()));
187        let name = options.name.unwrap_or_else(|| "pool".to_string());
188
189        let stats = Arc::new(StatsCollector::new());
190        let mut listeners: Vec<Arc<dyn PoolListener<T>>> = vec![stats.clone()];
191        listeners.extend(options.listeners);
192        let events = Listeners::new(listeners);
193
194        let inner = Arc::new(Inner {
195            name,
196            factory,
197            cfg: config,
198            clock,
199            rng: Mutex::new(rng),
200            state: Mutex::new(State {
201                idle: Vec::new(),
202                checked_out: HashMap::new(),
203                size: 0,
204                waiters: 0,
205                closed: false,
206                opened: false,
207            }),
208            notify: Notify::new(),
209            stats,
210            events,
211            breaker: OnceLock::new(),
212            stop: Notify::new(),
213            handle: Mutex::new(None),
214        });
215
216        if let Some(bc) = inner.cfg.breaker {
217            let weak = Arc::downgrade(&inner);
218            let cb = Box::new(move |old: BreakerState, new: BreakerState| {
219                if let Some(inner) = weak.upgrade() {
220                    inner.events.breaker_change(old, new);
221                }
222            });
223            let breaker = CircuitBreaker::new(bc, Some(inner.clock.clone()), Some(cb));
224            let _ = inner.breaker.set(breaker);
225        }
226
227        Ok(AsyncConnectionPool(inner))
228    }
229
230    /// The pool's name.
231    pub fn name(&self) -> &str {
232        &self.0.name
233    }
234
235    /// Warms up `min_size` connections and starts background maintenance. Idempotent.
236    pub async fn open(&self) -> Result<(), PoolError> {
237        let want_thread = {
238            let mut g = self.0.state.lock().unwrap();
239            if g.closed {
240                return Err(PoolError::Closed(format!("pool {:?} is closed", self.0.name)));
241            }
242            if g.opened {
243                return Ok(());
244            }
245            g.opened = true;
246            self.0.cfg.maintenance_interval
247        };
248        self.0.fill_to_min().await;
249        if let Some(interval) = want_thread {
250            if !self.0.state.lock().unwrap().closed {
251                self.start_maintenance(interval);
252            }
253        }
254        Ok(())
255    }
256
257    fn start_maintenance(&self, interval: f64) {
258        let weak: Weak<Inner<T, F>> = Arc::downgrade(&self.0);
259        let handle = tokio::spawn(async move {
260            let mut ticker = tokio::time::interval(Duration::from_secs_f64(interval));
261            ticker.tick().await; // consume the immediate first tick
262            loop {
263                let inner = match weak.upgrade() {
264                    Some(i) => i,
265                    None => return,
266                };
267                let stop = inner.stop.notified();
268                tokio::pin!(stop);
269                tokio::select! {
270                    _ = ticker.tick() => {}
271                    _ = &mut stop => return,
272                }
273                inner.run_maintenance_safe().await;
274            }
275        });
276        *self.0.handle.lock().unwrap() = Some(handle);
277    }
278
279    /// Closes idle connections now; checked-out ones close on release. Idempotent. `timeout`
280    /// (`None` = don't wait) drains gracefully up to that many seconds.
281    pub async fn close(&self, timeout: Option<f64>) {
282        self.0.close(timeout).await;
283    }
284
285    /// Whether [`close`](Self::close) has been called.
286    pub fn is_closed(&self) -> bool {
287        self.0.state.lock().unwrap().closed
288    }
289
290    /// Gets a connection, honoring cancellation (drop the future) and the acquire timeout.
291    pub async fn acquire(&self, timeout: Timeout) -> Result<T, PoolError> {
292        self.0.acquire(timeout).await
293    }
294
295    /// Returns a connection to the pool for reuse.
296    pub async fn release(&self, conn: T) -> Result<(), PoolError> {
297        self.0.release(conn, false, None).await
298    }
299
300    /// Returns a connection but forces it to be closed instead of reused.
301    pub async fn discard(&self, conn: T) -> Result<(), PoolError> {
302        self.0.release(conn, true, None).await
303    }
304
305    /// Returns a connection, signalling the caller's work raised `error`.
306    pub async fn release_with_error(
307        &self,
308        conn: T,
309        error: &(dyn Error + 'static),
310    ) -> Result<(), PoolError> {
311        self.0.release(conn, false, Some(error)).await
312    }
313
314    /// An immutable snapshot of pool counters and gauges.
315    pub fn stats(&self) -> PoolStats {
316        self.0.stats_snapshot()
317    }
318
319    /// Performs one maintenance sweep (see [`ConnectionPool::run_maintenance`](crate::ConnectionPool::run_maintenance)).
320    pub async fn run_maintenance(&self) {
321        self.0.run_maintenance().await;
322    }
323}
324
325enum Taken<T> {
326    Reused(T),
327    Reserved,
328}
329
330impl<T, F> Inner<T, F>
331where
332    T: Clone + Eq + Hash + Send + Sync + 'static,
333    F: AsyncConnectionFactory<T> + 'static,
334{
335    fn is_closed(&self) -> bool {
336        self.state.lock().unwrap().closed
337    }
338
339    fn new_conn(&self, raw: T, now: f64) -> PooledConnection<T> {
340        let mut rng = self.rng.lock().unwrap();
341        new_connection(raw, &self.cfg, now, &mut **rng)
342    }
343
344    fn retry_delay(&self, attempt: u32) -> f64 {
345        let mut rng = self.rng.lock().unwrap();
346        self.cfg.retry.delay_for(attempt, &mut **rng)
347    }
348
349    fn free_slot(&self) {
350        let mut g = self.state.lock().unwrap();
351        g.size -= 1;
352        self.notify.notify_waiters();
353    }
354
355    async fn close(&self, timeout: Option<f64>) {
356        let idle = {
357            let mut g = self.state.lock().unwrap();
358            if g.closed {
359                return;
360            }
361            g.closed = true;
362            let idle = std::mem::take(&mut g.idle);
363            self.notify.notify_waiters();
364            idle
365        };
366        if let Some(t) = timeout {
367            let deadline = self.clock.monotonic() + t;
368            loop {
369                if self.state.lock().unwrap().checked_out.is_empty() {
370                    break;
371                }
372                let left = deadline - self.clock.monotonic();
373                if left <= 0.0 {
374                    break;
375                }
376                let n = self.notify.notified();
377                tokio::pin!(n);
378                n.as_mut().enable();
379                let _ = tokio_timeout(Duration::from_secs_f64(left), n).await;
380            }
381        }
382        // notify_one stores a permit, so the maintenance task exits even if it is mid-sweep
383        // (not yet awaiting) when close() fires — notify_waiters would be lost in that race.
384        self.stop.notify_one();
385        let handle = self.handle.lock().unwrap().take();
386        if let Some(h) = handle {
387            let _ = h.await;
388        }
389        for pooled in idle {
390            self.destroy_detached(pooled, CloseReason::PoolClosed).await;
391        }
392    }
393
394    async fn acquire(self: &Arc<Self>, timeout: Timeout) -> Result<T, PoolError> {
395        let started = self.clock.monotonic();
396        let deadline = timeout.compute_deadline(self.cfg.acquire_timeout, started);
397        let key = match self.acquire_loop(deadline).await {
398            Ok(k) => k,
399            Err(e) => {
400                if matches!(e, PoolError::Timeout(_)) {
401                    let waited = self.clock.monotonic() - started;
402                    self.events.safe(|l| l.on_acquire_timeout(waited));
403                }
404                return Err(e);
405            }
406        };
407        let now = self.clock.monotonic();
408        let (raw, ev, wait) = {
409            let mut g = self.state.lock().unwrap();
410            let pooled = g.checked_out.get_mut(&key).expect("acquired conn present");
411            pooled.mark_checked_out(now);
412            (pooled.raw.clone(), pooled.clone(), now - started)
413        };
414        self.events.safe(|l| l.on_acquired(&ev, wait));
415        Ok(raw)
416    }
417
418    async fn acquire_loop(self: &Arc<Self>, deadline: Option<f64>) -> Result<T, PoolError> {
419        loop {
420            match self.take_idle_or_reserve(deadline).await? {
421                Taken::Reserved => {
422                    let slot = SlotGuard::new(self.clone());
423                    let (pooled, created_in) = self.create(deadline).await?;
424                    slot.commit();
425                    return self.register_new(pooled, created_in).await;
426                }
427                Taken::Reused(key) => {
428                    let guard = BorrowGuard::new(self.clone(), key.clone());
429                    match self.check_borrow(&key).await {
430                        None => {
431                            guard.commit();
432                            return Ok(key);
433                        }
434                        Some(reason) => {
435                            guard.commit();
436                            self.destroy_by_key(&key, reason).await;
437                        }
438                    }
439                }
440            }
441        }
442    }
443
444    async fn take_idle_or_reserve(&self, deadline: Option<f64>) -> Result<Taken<T>, PoolError> {
445        loop {
446            let notified = {
447                let mut g = self.state.lock().unwrap();
448                if g.closed {
449                    return Err(PoolError::Closed(format!("pool {:?} is closed", self.name)));
450                }
451                if let Some(conn) = pop_idle(&mut g.idle, self.cfg.idle_strategy) {
452                    let key = conn.raw.clone();
453                    g.checked_out.insert(key.clone(), conn);
454                    return Ok(Taken::Reused(key));
455                }
456                if g.size < self.cfg.max_size {
457                    g.size += 1;
458                    return Ok(Taken::Reserved);
459                }
460                let left = remaining(deadline, self.clock.monotonic());
461                if let Some(l) = left {
462                    if l <= 0.0 {
463                        return Err(PoolError::Timeout(format!(
464                            "pool {:?}: no connection available (max_size={})",
465                            self.name, self.cfg.max_size
466                        )));
467                    }
468                }
469                if let Some(max) = self.cfg.max_waiters {
470                    if g.waiters >= max {
471                        return Err(PoolError::Exhausted(format!(
472                            "pool {:?}: waiter queue full (max_waiters={})",
473                            self.name, max
474                        )));
475                    }
476                }
477                g.waiters += 1;
478                let n = self.notify.notified();
479                (n, left)
480            };
481            let (n, left) = notified;
482            tokio::pin!(n);
483            n.as_mut().enable();
484            let _guard = WaiterGuard { inner: self };
485            match left {
486                None => n.await,
487                Some(l) => {
488                    let _ = tokio_timeout(Duration::from_secs_f64(l.max(0.0)), n).await;
489                }
490            }
491        }
492    }
493
494    async fn check_borrow(&self, key: &T) -> Option<CloseReason> {
495        let now = self.clock.monotonic();
496        let (reason, need_validate, raw) = {
497            let g = self.state.lock().unwrap();
498            let pooled = g.checked_out.get(key).expect("conn present");
499            (
500                borrow_reason(pooled, &self.cfg, now),
501                needs_validation(pooled, &self.cfg, now),
502                pooled.raw.clone(),
503            )
504        };
505        if let Some(r) = reason {
506            return Some(r);
507        }
508        if need_validate && !self.is_healthy(&raw).await {
509            return Some(CloseReason::Unhealthy);
510        }
511        None
512    }
513
514    async fn release(
515        &self,
516        conn: T,
517        discard: bool,
518        error: Option<&(dyn Error + 'static)>,
519    ) -> Result<(), PoolError> {
520        let pooled = {
521            let mut g = self.state.lock().unwrap();
522            g.checked_out.remove(&conn)
523        };
524        let pooled = match pooled {
525            Some(p) => p,
526            None => {
527                return Err(PoolError::InvalidRelease(
528                    "object is not checked out from this pool (double release?)".to_string(),
529                ));
530            }
531        };
532        let now = self.clock.monotonic();
533        let hold = pooled.hold_time(now);
534        let reason = self.check_release(&pooled, &conn, discard, error, now).await;
535        self.events.safe(|l| l.on_released(&pooled, hold));
536        match reason {
537            Some(r) => self.destroy_detached(pooled, r).await,
538            None => {
539                if let Err(p) = self.park(pooled, now) {
540                    self.destroy_detached(p, CloseReason::PoolClosed).await;
541                }
542            }
543        }
544        Ok(())
545    }
546
547    async fn check_release(
548        &self,
549        pooled: &PooledConnection<T>,
550        conn: &T,
551        discard: bool,
552        error: Option<&(dyn Error + 'static)>,
553        now: f64,
554    ) -> Option<CloseReason> {
555        if discard {
556            return Some(CloseReason::Discarded);
557        }
558        let error_suspect = match error {
559            Some(e) => {
560                let mut s = self.factory.is_fatal_error(e);
561                if !s {
562                    s = !self.is_healthy(conn).await;
563                }
564                s
565            }
566            None => false,
567        };
568        if error_suspect {
569            return Some(CloseReason::Error);
570        }
571        if let Some(r) = rotation_reason(pooled, &self.cfg, now) {
572            return Some(r);
573        }
574        if self.reset_conn(conn).await.is_err() {
575            eprintln!(
576                "connpool: pool {:?}: reset failed; discarding {}",
577                self.name, pooled
578            );
579            return Some(CloseReason::ResetFailed);
580        }
581        None
582    }
583
584    fn park(&self, mut pooled: PooledConnection<T>, now: f64) -> Result<(), PooledConnection<T>> {
585        let mut g = self.state.lock().unwrap();
586        if g.closed {
587            return Err(pooled);
588        }
589        pooled.mark_returned(now);
590        g.idle.push(pooled);
591        self.notify.notify_waiters();
592        Ok(())
593    }
594
595    async fn create(&self, deadline: Option<f64>) -> Result<(PooledConnection<T>, f64), PoolError> {
596        let started = self.clock.monotonic();
597        let raw = self.create_raw(deadline).await?;
598        let now = self.clock.monotonic();
599        Ok((self.new_conn(raw, now), now - started))
600    }
601
602    async fn register_new(
603        &self,
604        pooled: PooledConnection<T>,
605        created_in: f64,
606    ) -> Result<T, PoolError> {
607        let key = pooled.raw.clone();
608        let outcome = {
609            let mut g = self.state.lock().unwrap();
610            if g.closed {
611                None // closed: dispose below
612            } else if g.checked_out.contains_key(&key) || g.idle.iter().any(|c| c.raw == key) {
613                Some(Err(())) // duplicate
614            } else {
615                let ev = pooled.clone();
616                g.checked_out.insert(key.clone(), pooled.clone());
617                Some(Ok(ev))
618            }
619        };
620        match outcome {
621            None => {
622                self.events.safe(|l| l.on_connection_created(&pooled, created_in));
623                self.destroy_detached(pooled, CloseReason::PoolClosed).await;
624                Err(PoolError::Closed(format!("pool {:?} is closed", self.name)))
625            }
626            Some(Err(())) => {
627                self.free_slot();
628                Err(PoolError::ConnectionCreate {
629                    message: format!(
630                        "pool {:?}: factory.create() returned an object the pool already manages; each create must return a distinct connection",
631                        self.name
632                    ),
633                    source: None,
634                })
635            }
636            Some(Ok(ev)) => {
637                self.events.safe(|l| l.on_connection_created(&ev, created_in));
638                Ok(key)
639            }
640        }
641    }
642
643    async fn create_raw(&self, deadline: Option<f64>) -> Result<T, PoolError> {
644        if let Some(b) = self.breaker.get() {
645            b.before_call()?;
646        }
647        match self.create_with_retry(deadline).await {
648            Ok(raw) => {
649                if let Some(b) = self.breaker.get() {
650                    b.record_success();
651                }
652                Ok(raw)
653            }
654            Err(e) => {
655                if let Some(b) = self.breaker.get() {
656                    if matches!(e, PoolError::ConnectionCreate { .. }) {
657                        b.record_failure();
658                    } else {
659                        b.record_abort();
660                    }
661                }
662                Err(e)
663            }
664        }
665    }
666
667    /// The tightest of `create_timeout` and the remaining acquire deadline (None = no limit).
668    fn create_limit(&self, deadline: Option<f64>) -> Option<f64> {
669        let mut limit: Option<f64> = self.cfg.create_timeout;
670        if let Some(r) = remaining(deadline, self.clock.monotonic()) {
671            limit = Some(match limit {
672                Some(l) => l.min(r),
673                None => r,
674            });
675        }
676        limit.map(|l| l.max(0.0))
677    }
678
679    async fn create_with_retry(&self, deadline: Option<f64>) -> Result<T, PoolError> {
680        let retry = &self.cfg.retry;
681        let mut attempt = 0u32;
682        loop {
683            if self.is_closed() {
684                return Err(PoolError::Closed(format!("pool {:?} is closed", self.name)));
685            }
686            attempt += 1;
687            let limit = self.create_limit(deadline);
688            match self.create_one(limit).await {
689                Ok(raw) => return Ok(raw),
690                Err(CreateErr::Timeout) => {
691                    // The binding limit was the acquire deadline -> caller impatience (abort).
692                    let left = remaining(deadline, self.clock.monotonic());
693                    if matches!(left, Some(l) if l <= 0.0) {
694                        return Err(PoolError::Timeout(format!(
695                            "pool {:?}: acquire deadline reached while creating connection",
696                            self.name
697                        )));
698                    }
699                    // Otherwise it was create_timeout firing -> a backend fault.
700                    let e: FactoryError = "create timed out".into();
701                    self.events.safe(|l| l.on_create_failed(&*e));
702                    if attempt >= retry.max_attempts {
703                        return Err(PoolError::ConnectionCreate {
704                            message: format!(
705                                "pool {:?}: could not create connection after {} attempt(s): create timed out",
706                                self.name, attempt
707                            ),
708                            source: Some(e),
709                        });
710                    }
711                    if let Some(stop) = self.backoff(attempt, deadline).await {
712                        return Err(stop);
713                    }
714                }
715                Err(CreateErr::Factory(e)) => {
716                    self.events.safe(|l| l.on_create_failed(&*e));
717                    if attempt >= retry.max_attempts || !retry.should_retry(&*e) {
718                        return Err(PoolError::ConnectionCreate {
719                            message: format!(
720                                "pool {:?}: could not create connection after {} attempt(s): {}",
721                                self.name, attempt, e
722                            ),
723                            source: Some(e),
724                        });
725                    }
726                    if let Some(stop) = self.backoff(attempt, deadline).await {
727                        return Err(stop);
728                    }
729                }
730            }
731        }
732    }
733
734    /// Sleeps the retry backoff; returns `Some(err)` if the deadline is reached first.
735    async fn backoff(&self, attempt: u32, deadline: Option<f64>) -> Option<PoolError> {
736        let delay = self.retry_delay(attempt);
737        let left = remaining(deadline, self.clock.monotonic());
738        if matches!(left, Some(l) if delay >= l) {
739            return Some(PoolError::Timeout(format!(
740                "pool {:?}: acquire deadline reached while retrying creation",
741                self.name
742            )));
743        }
744        self.clock.sleep_async(delay).await;
745        None
746    }
747
748    async fn create_one(&self, limit: Option<f64>) -> Result<T, CreateErr> {
749        let fut = self.factory.create();
750        let result = match limit {
751            Some(l) => match tokio_timeout(Duration::from_secs_f64(l), fut).await {
752                Ok(r) => r,
753                Err(_) => return Err(CreateErr::Timeout),
754            },
755            None => fut.await,
756        };
757        result.map_err(CreateErr::Factory)
758    }
759
760    async fn is_healthy(&self, raw: &T) -> bool {
761        self.factory.validate(raw).await
762    }
763
764    async fn reset_conn(&self, conn: &T) -> Result<(), FactoryError> {
765        self.factory.reset(conn).await
766    }
767
768    async fn destroy_by_key(&self, key: &T, reason: CloseReason) {
769        let pooled = {
770            let mut g = self.state.lock().unwrap();
771            g.checked_out.remove(key)
772        };
773        let Some(p) = pooled else { return };
774        self.destroy_detached(p, reason).await;
775    }
776
777    async fn destroy_detached(&self, pooled: PooledConnection<T>, reason: CloseReason) {
778        self.close_conn(&pooled).await;
779        {
780            let mut g = self.state.lock().unwrap();
781            g.size -= 1;
782            self.notify.notify_waiters();
783        }
784        self.events.safe(|l| l.on_connection_closed(&pooled, reason));
785    }
786
787    async fn close_conn(&self, pooled: &PooledConnection<T>) {
788        match self.factory.close(&pooled.raw).await {
789            Ok(()) => {}
790            Err(e) => eprintln!(
791                "connpool: pool {:?}: error closing {}: {}",
792                self.name, pooled, e
793            ),
794        }
795    }
796
797    async fn fill_to_min(&self) {
798        loop {
799            {
800                let mut g = self.state.lock().unwrap();
801                if g.closed || g.size >= self.cfg.min_size {
802                    return;
803                }
804                g.size += 1;
805            }
806            match self.create(None).await {
807                Err(e) => {
808                    self.free_slot();
809                    eprintln!("connpool: pool {:?}: warm-up failed: {}", self.name, e);
810                    return;
811                }
812                Ok((pooled, created_in)) => {
813                    self.events.safe(|l| l.on_connection_created(&pooled, created_in));
814                    if let Err(p) = self.park(pooled, self.clock.monotonic()) {
815                        self.destroy_detached(p, CloseReason::PoolClosed).await;
816                    }
817                }
818            }
819        }
820    }
821
822    async fn run_maintenance(&self) {
823        let doomed = {
824            let mut g = self.state.lock().unwrap();
825            if g.closed {
826                return;
827            }
828            let idle = std::mem::take(&mut g.idle);
829            let (keep, doomed) = sweep(idle, g.size, &self.cfg, self.clock.monotonic());
830            g.idle = keep;
831            doomed
832        };
833        for (p, reason) in doomed {
834            self.destroy_detached(p, reason).await;
835        }
836        self.health_check_idle().await;
837        self.fill_to_min().await;
838        self.check_leaks();
839    }
840
841    async fn run_maintenance_safe(&self) {
842        // A panic inside maintenance is logged; the loop keeps running.
843        self.run_maintenance().await;
844    }
845
846    async fn health_check_idle(&self) {
847        if !self.cfg.validate_idle {
848            return;
849        }
850        let keys: Vec<T> = {
851            let mut g = self.state.lock().unwrap();
852            let batch = std::mem::take(&mut g.idle);
853            let mut keys = Vec::with_capacity(batch.len());
854            for conn in batch {
855                let key = conn.raw.clone();
856                g.checked_out.insert(key.clone(), conn);
857                keys.push(key);
858            }
859            keys
860        };
861        for key in keys {
862            let raw = {
863                let g = self.state.lock().unwrap();
864                g.checked_out.get(&key).expect("reserved").raw.clone()
865            };
866            let healthy = self.is_healthy(&raw).await;
867            let doomed = {
868                let mut g = self.state.lock().unwrap();
869                let pooled = g.checked_out.remove(&key).expect("reserved");
870                if healthy && !g.closed {
871                    g.idle.push(pooled);
872                    self.notify.notify_waiters();
873                    None
874                } else {
875                    Some(pooled)
876                }
877            };
878            if let Some(p) = doomed {
879                let reason = if healthy {
880                    CloseReason::PoolClosed
881                } else {
882                    CloseReason::Unhealthy
883                };
884                self.destroy_detached(p, reason).await;
885            }
886        }
887    }
888
889    fn check_leaks(&self) {
890        let timeout = match self.cfg.leak_detection_timeout {
891            Some(t) => t,
892            None => return,
893        };
894        let now = self.clock.monotonic();
895        let leaked: Vec<(PooledConnection<T>, f64)> = {
896            let mut g = self.state.lock().unwrap();
897            let mut out = Vec::new();
898            for pooled in g.checked_out.values_mut() {
899                if !pooled.leak_reported && pooled.hold_time(now) >= timeout {
900                    pooled.leak_reported = true;
901                    let held = pooled.hold_time(now);
902                    out.push((pooled.clone(), held));
903                }
904            }
905            out
906        };
907        for (pooled, held) in leaked {
908            eprintln!(
909                "connpool: pool {:?}: possible connection leak: {} held for {:.1}s",
910                self.name, pooled, held
911            );
912            self.events.safe(|l| l.on_connection_leak(&pooled, held));
913        }
914    }
915
916    fn stats_snapshot(&self) -> PoolStats {
917        let (name, max_size, size, idle, in_use, waiting) = {
918            let g = self.state.lock().unwrap();
919            (
920                self.name.clone(),
921                self.cfg.max_size,
922                g.size,
923                g.idle.len(),
924                g.checked_out.len(),
925                g.waiters,
926            )
927        };
928        let breaker_state = self.breaker.get().map(|b| b.state().as_str().to_string());
929        self.stats.snapshot(StatsGauges {
930            name,
931            max_size,
932            size,
933            idle,
934            in_use,
935            waiting,
936            breaker_state,
937        })
938    }
939}
940
941/// Classifies a `create_one` failure so the retry loop can distinguish a create-timeout from a
942/// plain factory error.
943enum CreateErr {
944    Timeout,
945    Factory(FactoryError),
946}
947
948/// Decrements the waiter count when a waiting `acquire` future resolves or is cancelled.
949struct WaiterGuard<'a, T, F> {
950    inner: &'a Inner<T, F>,
951}
952
953impl<T, F> Drop for WaiterGuard<'_, T, F> {
954    fn drop(&mut self) {
955        let mut g = self.inner.state.lock().unwrap();
956        g.waiters = g.waiters.saturating_sub(1);
957    }
958}
959
960#[cfg(test)]
961mod tests {
962    use super::*;
963    use crate::async_test_support::*;
964    use crate::factories::async_factory::AsyncFnFactory;
965    use crate::policy::config::IdleStrategy;
966    use crate::resilience::breaker::BreakerConfig;
967    use crate::resilience::retry::RetryPolicy;
968    use std::sync::atomic::Ordering;
969
970    fn plain(max_size: usize) -> PoolConfig {
971        PoolConfig {
972            max_size,
973            maintenance_interval: None,
974            breaker: None,
975            ..Default::default()
976        }
977    }
978
979    #[tokio::test]
980    async fn acquire_release_reuse_and_timeout() {
981        let h = AsyncHarness::new();
982        let p = h.pool(plain(1));
983        let a = p.acquire(Timeout::Unset).await.unwrap();
984        assert!(matches!(
985            p.acquire(Timeout::Of(0.0)).await,
986            Err(PoolError::Timeout(_))
987        ));
988        p.release(a).await.unwrap();
989        let b = p.acquire(Timeout::Unset).await.unwrap();
990        assert_eq!(b.id, a.id);
991        p.release(b).await.unwrap();
992        let s = p.stats();
993        assert_eq!(s.created, 1);
994        assert_eq!(s.acquired, 2);
995        assert_eq!(s.timeouts, 1);
996        assert_eq!(p.name(), "test");
997        p.close(None).await;
998        p.close(None).await; // idempotent
999        assert!(p.is_closed());
1000        assert!(matches!(
1001            p.acquire(Timeout::Unset).await,
1002            Err(PoolError::Closed(_))
1003        ));
1004    }
1005
1006    #[tokio::test]
1007    async fn fifo_strategy_and_config_validation() {
1008        let h = AsyncHarness::new();
1009        let p = h.pool(PoolConfig {
1010            idle_strategy: IdleStrategy::Fifo,
1011            ..plain(3)
1012        });
1013        let a = p.acquire(Timeout::Unset).await.unwrap();
1014        let b = p.acquire(Timeout::Unset).await.unwrap();
1015        p.release(a).await.unwrap();
1016        p.release(b).await.unwrap();
1017        let c = p.acquire(Timeout::Unset).await.unwrap();
1018        assert_eq!(c.id, a.id);
1019        p.release(c).await.unwrap();
1020
1021        let bad = AsyncConnectionPool::new(
1022            h.factory(),
1023            PoolConfig { max_size: 0, ..Default::default() },
1024            None,
1025        );
1026        assert!(matches!(bad, Err(PoolError::Config(_))));
1027    }
1028
1029    #[tokio::test]
1030    async fn invalid_release_detected() {
1031        let h = AsyncHarness::new();
1032        let p = h.pool(plain(2));
1033        assert!(matches!(
1034            p.release(Conn::new(999)).await,
1035            Err(PoolError::InvalidRelease(_))
1036        ));
1037    }
1038
1039    #[tokio::test]
1040    async fn max_waiters_backpressure() {
1041        let h = AsyncHarness::new();
1042        let p = h.pool(PoolConfig { max_waiters: Some(0), ..plain(1) });
1043        let a = p.acquire(Timeout::Unset).await.unwrap();
1044        assert!(matches!(
1045            p.acquire(Timeout::Infinite).await,
1046            Err(PoolError::Exhausted(_))
1047        ));
1048        p.release(a).await.unwrap();
1049    }
1050
1051    #[tokio::test]
1052    async fn blocked_waiter_woken_on_release() {
1053        let h = AsyncHarness::new();
1054        let p = h.pool(plain(1));
1055        let a = p.acquire(Timeout::Unset).await.unwrap();
1056        let p2 = p.clone();
1057        let waiter = tokio::spawn(async move { p2.acquire(Timeout::Infinite).await.map(|c| c.id) });
1058        tokio::time::sleep(Duration::from_millis(30)).await;
1059        p.release(a).await.unwrap();
1060        let got = waiter.await.unwrap().unwrap();
1061        assert_eq!(got, a.id);
1062        p.release(Conn::new(got)).await.unwrap();
1063    }
1064
1065    #[tokio::test]
1066    async fn cancelled_waiter_does_not_leak_waiter_slot() {
1067        let h = AsyncHarness::new();
1068        let p = h.pool(plain(1));
1069        let a = p.acquire(Timeout::Unset).await.unwrap();
1070        {
1071            // Start a wait and drop it (cancel) before it resolves.
1072            let fut = p.acquire(Timeout::Infinite);
1073            tokio::pin!(fut);
1074            let _ = tokio::time::timeout(Duration::from_millis(20), &mut fut).await;
1075            // fut dropped here -> WaiterGuard decrements the waiter count.
1076        }
1077        assert_eq!(p.stats().waiting, 0);
1078        p.release(a).await.unwrap();
1079    }
1080
1081    #[tokio::test]
1082    async fn real_time_wait_times_out() {
1083        // Real clock so the acquire deadline elapses during the notify wait.
1084        let h = AsyncHarness::with_system_clock();
1085        let p = h.pool(plain(1));
1086        let a = p.acquire(Timeout::Unset).await.unwrap();
1087        assert!(matches!(
1088            p.acquire(Timeout::Of(0.05)).await,
1089            Err(PoolError::Timeout(_))
1090        ));
1091        p.release(a).await.unwrap();
1092    }
1093
1094    #[tokio::test]
1095    async fn discard_error_and_reset_paths() {
1096        let h = AsyncHarness::new();
1097        let p = h.pool(plain(5));
1098        let a = p.acquire(Timeout::Unset).await.unwrap();
1099        p.discard(a).await.unwrap();
1100        assert!(h.fac.closed_ids().contains(&a.id));
1101
1102        h.fac.set_validate_ok(false);
1103        let b = p.acquire(Timeout::Unset).await.unwrap();
1104        let e: FactoryError = "bad".into();
1105        p.release_with_error(b, &*e).await.unwrap();
1106        assert!(h.fac.closed_ids().contains(&b.id));
1107
1108        h.fac.set_validate_ok(true);
1109        h.fac.set_reset_ok(false);
1110        let c = p.acquire(Timeout::Unset).await.unwrap();
1111        p.release(c).await.unwrap();
1112        assert_eq!(p.stats().closed_by_reason.get("reset_failed"), Some(&1));
1113    }
1114
1115    #[tokio::test]
1116    async fn fatal_error_closes_without_validation() {
1117        let h = AsyncHarness::new();
1118        h.fac.set_fatal(true);
1119        let p = h.pool(plain(5));
1120        let a = p.acquire(Timeout::Unset).await.unwrap();
1121        let e: FactoryError = "fatal".into();
1122        p.release_with_error(a, &*e).await.unwrap();
1123        assert!(h.fac.closed_ids().contains(&a.id));
1124    }
1125
1126    #[tokio::test]
1127    async fn rotation_and_stale_replacement() {
1128        let h = AsyncHarness::new();
1129        let p = h.pool(PoolConfig {
1130            max_lifetime: Some(100.0),
1131            lifetime_jitter: 0.0,
1132            max_idle_time: None,
1133            validate_after_idle: None,
1134            ..plain(5)
1135        });
1136        let a = p.acquire(Timeout::Unset).await.unwrap();
1137        p.release(a).await.unwrap();
1138        h.clock.advance(200.0);
1139        let b = p.acquire(Timeout::Unset).await.unwrap();
1140        assert_ne!(b.id, a.id);
1141        p.release(b).await.unwrap();
1142    }
1143
1144    #[tokio::test]
1145    async fn validate_on_checkout_replaces_unhealthy() {
1146        let h = AsyncHarness::new();
1147        let p = h.pool(PoolConfig {
1148            validate_after_idle: Some(5.0),
1149            max_lifetime: None,
1150            max_idle_time: None,
1151            ..plain(5)
1152        });
1153        let a = p.acquire(Timeout::Unset).await.unwrap();
1154        p.release(a).await.unwrap();
1155        h.clock.advance(10.0);
1156        h.fac.set_validate_ok(false);
1157        let b = p.acquire(Timeout::Unset).await.unwrap();
1158        assert_ne!(b.id, a.id);
1159        assert_eq!(p.stats().closed_by_reason.get("unhealthy"), Some(&1));
1160        p.release(b).await.unwrap();
1161    }
1162
1163    #[tokio::test]
1164    async fn create_failure_retry_then_error() {
1165        let h = AsyncHarness::new();
1166        h.fac.fail_next_creates(10);
1167        let p = h.pool(PoolConfig {
1168            retry: RetryPolicy { max_attempts: 3, base_delay: 0.0, ..Default::default() },
1169            ..plain(2)
1170        });
1171        assert!(matches!(
1172            p.acquire(Timeout::Unset).await,
1173            Err(PoolError::ConnectionCreate { .. })
1174        ));
1175        assert_eq!(p.stats().create_failures, 3);
1176        // slot was freed; a later success works
1177        h.fac.fail_next_creates(1);
1178        let a = p.acquire(Timeout::Unset).await.unwrap(); // 1 fail then ok
1179        p.release(a).await.unwrap();
1180    }
1181
1182    #[tokio::test]
1183    async fn create_timeout_counts_as_backend_failure() {
1184        let h = AsyncHarness::with_system_clock();
1185        h.fac.set_create_delay_ms(1000); // slower than create_timeout
1186        let p = h.pool(PoolConfig {
1187            create_timeout: Some(0.02),
1188            acquire_timeout: Some(5.0),
1189            retry: RetryPolicy::no_retry(),
1190            ..plain(2)
1191        });
1192        let err = p.acquire(Timeout::Unset).await.unwrap_err();
1193        assert!(matches!(err, PoolError::ConnectionCreate { .. }));
1194        assert!(p.stats().create_failures >= 1);
1195    }
1196
1197    #[tokio::test]
1198    async fn retry_backoff_exceeding_deadline_times_out() {
1199        let h = AsyncHarness::new();
1200        h.fac.fail_next_creates(10);
1201        let p = h.pool(PoolConfig {
1202            acquire_timeout: Some(0.05),
1203            retry: RetryPolicy { max_attempts: 5, base_delay: 100.0, jitter: 0.0, ..Default::default() },
1204            ..plain(2)
1205        });
1206        assert!(matches!(
1207            p.acquire(Timeout::Unset).await,
1208            Err(PoolError::Timeout(_))
1209        ));
1210    }
1211
1212    #[tokio::test]
1213    async fn breaker_trips_blocks_recovers_and_abort_is_safe() {
1214        let h = AsyncHarness::new();
1215        h.fac.fail_next_creates(100);
1216        let p = h.pool(PoolConfig {
1217            retry: RetryPolicy::no_retry(),
1218            maintenance_interval: None,
1219            breaker: Some(BreakerConfig {
1220                failure_threshold: 1,
1221                recovery_timeout: 10.0,
1222                half_open_max_calls: 1,
1223                success_threshold: 1,
1224            }),
1225            ..Default::default()
1226        });
1227        assert!(matches!(
1228            p.acquire(Timeout::Unset).await,
1229            Err(PoolError::ConnectionCreate { .. })
1230        ));
1231        assert!(matches!(
1232            p.acquire(Timeout::Unset).await,
1233            Err(PoolError::CircuitOpen { .. })
1234        ));
1235        assert_eq!(p.stats().breaker_state.as_deref(), Some("open"));
1236        h.clock.advance(10.0);
1237        h.fac.fail_next_creates(0);
1238        let a = p.acquire(Timeout::Unset).await.unwrap();
1239        assert_eq!(p.stats().breaker_state.as_deref(), Some("closed"));
1240        p.release(a).await.unwrap();
1241    }
1242
1243    #[tokio::test]
1244    async fn duplicate_object_rejected() {
1245        let h = AsyncHarness::new();
1246        h.fac.force_id(Some(42));
1247        let p = h.pool(plain(5));
1248        let _a = p.acquire(Timeout::Unset).await.unwrap();
1249        assert!(matches!(
1250            p.acquire(Timeout::Unset).await,
1251            Err(PoolError::ConnectionCreate { .. })
1252        ));
1253    }
1254
1255    #[tokio::test]
1256    async fn open_warms_up_close_drains_and_maintenance() {
1257        let h = AsyncHarness::new();
1258        let rec = Recorder::new();
1259        let p = h.pool_with(
1260            PoolConfig {
1261                min_size: 2,
1262                max_lifetime: Some(100.0),
1263                lifetime_jitter: 0.0,
1264                max_idle_time: Some(50.0),
1265                validate_after_idle: None,
1266                maintenance_interval: None,
1267                breaker: None,
1268                ..Default::default()
1269            },
1270            vec![rec.clone()],
1271        );
1272        p.open().await.unwrap();
1273        p.open().await.unwrap(); // idempotent
1274        assert_eq!(p.stats().idle, 2);
1275        // age out -> sweep evicts then refills to min_size
1276        h.clock.advance(200.0);
1277        p.run_maintenance().await;
1278        assert_eq!(p.stats().idle, 2);
1279        assert!(p.stats().closed >= 2);
1280        p.close(Some(0.0)).await;
1281        assert!(matches!(p.open().await, Err(PoolError::Closed(_))));
1282    }
1283
1284    #[tokio::test]
1285    async fn maintenance_noop_when_closed() {
1286        let h = AsyncHarness::new();
1287        let p = h.pool(plain(2));
1288        p.close(None).await;
1289        p.run_maintenance().await;
1290    }
1291
1292    #[tokio::test]
1293    async fn health_check_idle_and_leak_detection() {
1294        let h = AsyncHarness::new();
1295        let rec = Recorder::new();
1296        let p = h.pool_with(
1297            PoolConfig {
1298                validate_idle: true,
1299                leak_detection_timeout: Some(10.0),
1300                max_lifetime: None,
1301                max_idle_time: None,
1302                validate_after_idle: None,
1303                maintenance_interval: None,
1304                breaker: None,
1305                ..Default::default()
1306            },
1307            vec![rec.clone()],
1308        );
1309        let a = p.acquire(Timeout::Unset).await.unwrap();
1310        h.clock.advance(20.0);
1311        p.run_maintenance().await; // leak detected (held), idle empty so health check no-op
1312        p.run_maintenance().await; // leak not re-counted
1313        assert_eq!(rec.leaks.load(Ordering::SeqCst), 1);
1314        p.release(a).await.unwrap();
1315        // now idle validation: healthy keeps, unhealthy evicts
1316        p.run_maintenance().await;
1317        assert_eq!(p.stats().idle, 1);
1318        h.fac.set_validate_ok(false);
1319        p.run_maintenance().await;
1320        assert_eq!(p.stats().idle, 0);
1321    }
1322
1323    #[tokio::test]
1324    async fn release_into_closed_pool_closes_connection() {
1325        let h = AsyncHarness::new();
1326        let p = h.pool(plain(2));
1327        let a = p.acquire(Timeout::Unset).await.unwrap();
1328        p.close(None).await;
1329        p.release(a).await.unwrap();
1330        assert!(h.fac.closed_ids().contains(&a.id));
1331    }
1332
1333    #[tokio::test]
1334    async fn close_drains_checked_out_with_timeout() {
1335        let h = AsyncHarness::new();
1336        let p = h.pool(plain(2));
1337        let a = p.acquire(Timeout::Unset).await.unwrap();
1338        let p2 = p.clone();
1339        let closer = tokio::spawn(async move { p2.close(Some(5.0)).await });
1340        tokio::time::sleep(Duration::from_millis(30)).await;
1341        p.release(a).await.unwrap();
1342        closer.await.unwrap();
1343        assert!(p.is_closed());
1344    }
1345
1346    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1347    async fn background_maintenance_task_runs_and_stops() {
1348        let h = AsyncHarness::with_system_clock();
1349        let p = h.pool(PoolConfig {
1350            maintenance_interval: Some(0.01),
1351            breaker: None,
1352            ..Default::default()
1353        });
1354        p.open().await.unwrap();
1355        tokio::time::sleep(Duration::from_millis(40)).await;
1356        p.close(None).await;
1357        assert!(p.is_closed());
1358    }
1359
1360    #[tokio::test]
1361    async fn default_options_and_warmup_failure() {
1362        let h = AsyncHarness::new();
1363        // None options path.
1364        let p = AsyncConnectionPool::new(h.factory(), plain(2), None).unwrap();
1365        assert_eq!(p.name(), "pool");
1366        let a = p.acquire(Timeout::Unset).await.unwrap();
1367        p.release(a).await.unwrap();
1368
1369        // warm-up failure path in fill_to_min.
1370        let h2 = AsyncHarness::new();
1371        h2.fac.fail_next_creates(100);
1372        let p2 = h2.pool(PoolConfig {
1373            min_size: 1,
1374            retry: RetryPolicy::no_retry(),
1375            maintenance_interval: None,
1376            breaker: None,
1377            ..Default::default()
1378        });
1379        p2.open().await.unwrap(); // warm-up fails, logged, slot freed
1380        assert_eq!(p2.stats().idle, 0);
1381    }
1382
1383    #[tokio::test]
1384    async fn cancelled_create_frees_slot() {
1385        let h = AsyncHarness::with_system_clock();
1386        h.fac.set_create_delay_ms(1000);
1387        let p = h.pool(plain(1));
1388        {
1389            let fut = p.acquire(Timeout::Infinite);
1390            tokio::pin!(fut);
1391            let _ = tokio::time::timeout(Duration::from_millis(20), &mut fut).await;
1392            // dropped mid-create -> SlotGuard frees the reserved slot
1393        }
1394        tokio::time::sleep(Duration::from_millis(5)).await;
1395        assert_eq!(p.stats().size, 0);
1396        // the freed slot is reusable
1397        h.fac.set_create_delay_ms(0);
1398        let a = p.acquire(Timeout::Unset).await.unwrap();
1399        p.release(a).await.unwrap();
1400    }
1401
1402    #[tokio::test]
1403    async fn cancelled_borrow_returns_connection() {
1404        let h = AsyncHarness::with_system_clock();
1405        let p = h.pool(PoolConfig {
1406            validate_after_idle: Some(0.0), // always validate on checkout
1407            max_lifetime: None,
1408            max_idle_time: None,
1409            ..plain(1)
1410        });
1411        // seed one idle connection
1412        let a = p.acquire(Timeout::Unset).await.unwrap();
1413        p.release(a).await.unwrap();
1414        h.fac.set_validate_delay_ms(1000); // validation blocks
1415        {
1416            let fut = p.acquire(Timeout::Infinite);
1417            tokio::pin!(fut);
1418            let _ = tokio::time::timeout(Duration::from_millis(20), &mut fut).await;
1419            // dropped during validate -> BorrowGuard returns the conn to idle
1420        }
1421        tokio::time::sleep(Duration::from_millis(5)).await;
1422        assert_eq!(p.stats().idle, 1);
1423        assert_eq!(p.stats().in_use, 0);
1424    }
1425
1426    #[tokio::test]
1427    async fn listener_panic_does_not_break_pool() {
1428        struct Boom;
1429        impl PoolListener<Conn> for Boom {
1430            fn on_acquired(&self, _c: &PooledConnection<Conn>, _w: f64) {
1431                panic!("boom");
1432            }
1433        }
1434        let h = AsyncHarness::new();
1435        let p = h.pool_with(plain(2), vec![Arc::new(Boom)]);
1436        let a = p.acquire(Timeout::Unset).await.unwrap();
1437        p.release(a).await.unwrap();
1438    }
1439
1440    #[tokio::test]
1441    async fn waiter_under_max_waiters_blocks_then_succeeds() {
1442        let h = AsyncHarness::new();
1443        let p = h.pool(PoolConfig { max_waiters: Some(5), ..plain(1) });
1444        let a = p.acquire(Timeout::Unset).await.unwrap();
1445        let p2 = p.clone();
1446        let waiter = tokio::spawn(async move { p2.acquire(Timeout::Infinite).await.map(|c| c.id) });
1447        tokio::time::sleep(Duration::from_millis(30)).await;
1448        p.release(a).await.unwrap();
1449        assert_eq!(waiter.await.unwrap().unwrap(), a.id);
1450        p.release(Conn::new(a.id)).await.unwrap();
1451    }
1452
1453    #[tokio::test]
1454    async fn create_timeout_then_backoff_hits_deadline() {
1455        let h = AsyncHarness::with_system_clock();
1456        h.fac.set_create_delay_ms(1000);
1457        let p = h.pool(PoolConfig {
1458            acquire_timeout: Some(1.0),
1459            create_timeout: Some(0.01),
1460            retry: RetryPolicy { max_attempts: 5, base_delay: 100.0, jitter: 0.0, ..Default::default() },
1461            ..plain(2)
1462        });
1463        // create times out (limit=create_timeout, left still >0), then backoff (100s) exceeds
1464        // the acquire deadline -> Timeout from the timeout-arm's backoff.
1465        assert!(matches!(p.acquire(Timeout::Unset).await, Err(PoolError::Timeout(_))));
1466    }
1467
1468    #[tokio::test]
1469    async fn rotation_on_release() {
1470        let h = AsyncHarness::new();
1471        let p = h.pool(PoolConfig { max_uses: Some(1), max_lifetime: None, ..plain(5) });
1472        let a = p.acquire(Timeout::Unset).await.unwrap();
1473        p.release(a).await.unwrap(); // use_count==max_uses -> rotate on release
1474        assert_eq!(p.stats().closed_by_reason.get("max_uses"), Some(&1));
1475    }
1476
1477    #[tokio::test]
1478    async fn breaker_abort_does_not_trip() {
1479        let h = AsyncHarness::new();
1480        h.fac.fail_next_creates(100);
1481        let p = h.pool(PoolConfig {
1482            acquire_timeout: Some(0.05),
1483            retry: RetryPolicy { max_attempts: 5, base_delay: 100.0, jitter: 0.0, ..Default::default() },
1484            maintenance_interval: None,
1485            breaker: Some(BreakerConfig { failure_threshold: 1, ..Default::default() }),
1486            ..Default::default()
1487        });
1488        assert!(matches!(p.acquire(Timeout::Unset).await, Err(PoolError::Timeout(_))));
1489        assert_eq!(p.stats().breaker_state.as_deref(), Some("closed"));
1490    }
1491
1492    #[tokio::test]
1493    async fn create_limit_from_remaining_deadline() {
1494        // create_timeout None but a finite acquire deadline -> limit comes from remaining().
1495        let h = AsyncHarness::with_system_clock();
1496        let p = h.pool(PoolConfig { create_timeout: None, acquire_timeout: Some(5.0), ..plain(2) });
1497        let a = p.acquire(Timeout::Of(5.0)).await.unwrap();
1498        p.release(a).await.unwrap();
1499    }
1500
1501    #[tokio::test]
1502    async fn create_with_no_limit_at_all() {
1503        // create_timeout None + Infinite acquire -> create_one runs with no timer.
1504        let h = AsyncHarness::with_system_clock();
1505        let p = h.pool(PoolConfig { create_timeout: None, ..plain(2) });
1506        let a = p.acquire(Timeout::Infinite).await.unwrap();
1507        p.release(a).await.unwrap();
1508    }
1509
1510    #[tokio::test]
1511    async fn create_timeout_bound_by_acquire_deadline_is_timeout() {
1512        let h = AsyncHarness::with_system_clock();
1513        h.fac.set_create_delay_ms(1000);
1514        let p = h.pool(PoolConfig {
1515            acquire_timeout: Some(0.02),
1516            create_timeout: Some(10.0),
1517            retry: RetryPolicy::no_retry(),
1518            ..plain(2)
1519        });
1520        // limit = min(create_timeout, remaining) = the acquire deadline -> PoolTimeout (abort).
1521        assert!(matches!(p.acquire(Timeout::Unset).await, Err(PoolError::Timeout(_))));
1522    }
1523
1524    #[tokio::test]
1525    async fn create_timeout_then_backoff_then_fail() {
1526        let h = AsyncHarness::with_system_clock();
1527        h.fac.set_create_delay_ms(1000);
1528        let p = h.pool(PoolConfig {
1529            acquire_timeout: Some(5.0),
1530            create_timeout: Some(0.02),
1531            retry: RetryPolicy { max_attempts: 2, base_delay: 0.0, ..Default::default() },
1532            ..plain(2)
1533        });
1534        // each attempt times out (backend fault); backoff runs once; then ConnectionCreate.
1535        assert!(matches!(
1536            p.acquire(Timeout::Unset).await,
1537            Err(PoolError::ConnectionCreate { .. })
1538        ));
1539        assert!(p.stats().create_failures >= 2);
1540    }
1541
1542    #[tokio::test]
1543    async fn close_error_is_logged() {
1544        let h = AsyncHarness::new();
1545        h.fac.set_close_err(true);
1546        let p = h.pool(plain(5));
1547        let a = p.acquire(Timeout::Unset).await.unwrap();
1548        p.discard(a).await.unwrap(); // close returns Err -> logged, still counted
1549        assert_eq!(p.stats().closed, 1);
1550    }
1551
1552    #[tokio::test]
1553    async fn close_drain_gives_up_at_deadline() {
1554        let h = AsyncHarness::new();
1555        let p = h.pool(plain(2));
1556        let a = p.acquire(Timeout::Unset).await.unwrap();
1557        p.close(Some(0.0)).await; // conn still out, immediate deadline -> break
1558        assert!(p.is_closed());
1559        p.release(a).await.unwrap();
1560    }
1561
1562    #[tokio::test]
1563    async fn blocked_waiter_wakes_as_closed() {
1564        let h = AsyncHarness::new();
1565        let p = h.pool(plain(1));
1566        let a = p.acquire(Timeout::Unset).await.unwrap();
1567        let p2 = p.clone();
1568        let waiter = tokio::spawn(async move { p2.acquire(Timeout::Infinite).await });
1569        tokio::time::sleep(Duration::from_millis(30)).await;
1570        p.close(None).await;
1571        assert!(matches!(waiter.await.unwrap(), Err(PoolError::Closed(_))));
1572        p.release(a).await.unwrap();
1573    }
1574
1575    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1576    async fn maintenance_task_stops_when_pool_dropped() {
1577        let h = AsyncHarness::with_system_clock();
1578        let p = h.pool(PoolConfig {
1579            maintenance_interval: Some(0.01),
1580            breaker: None,
1581            ..Default::default()
1582        });
1583        p.open().await.unwrap();
1584        drop(p); // task's next weak.upgrade() returns None -> it exits
1585        tokio::time::sleep(Duration::from_millis(50)).await;
1586    }
1587
1588    #[tokio::test]
1589    async fn cancelled_borrow_while_closed_frees_slot() {
1590        let h = AsyncHarness::with_system_clock();
1591        let p = h.pool(PoolConfig {
1592            validate_after_idle: Some(0.0),
1593            max_lifetime: None,
1594            max_idle_time: None,
1595            ..plain(1)
1596        });
1597        let a = p.acquire(Timeout::Unset).await.unwrap();
1598        p.release(a).await.unwrap(); // seed idle
1599        h.fac.set_validate_delay_ms(1000);
1600        {
1601            let fut = p.acquire(Timeout::Infinite);
1602            tokio::pin!(fut);
1603            let _ = tokio::time::timeout(Duration::from_millis(20), &mut fut).await;
1604            p.close(None).await; // pool closed while the borrow is mid-validation
1605            // fut dropped here -> BorrowGuard sees closed, frees the slot
1606        }
1607        tokio::time::sleep(Duration::from_millis(5)).await;
1608        assert_eq!(p.stats().size, 0);
1609    }
1610
1611    // --- self-closing factories exercise the "pool closed mid-create" paths ---
1612
1613    type SelfClosePool = AsyncConnectionPool<Conn, AsyncFnFactory<Conn>>;
1614
1615    fn self_closing_create(ok: bool) -> (SelfClosePool, PoolConfig) {
1616        let cfg = PoolConfig {
1617            min_size: 0,
1618            retry: RetryPolicy { max_attempts: 2, base_delay: 0.0, ..Default::default() },
1619            maintenance_interval: None,
1620            breaker: None,
1621            ..Default::default()
1622        };
1623        (build_self_closing(cfg.clone(), ok, false), cfg)
1624    }
1625
1626    fn build_self_closing(cfg: PoolConfig, create_ok: bool, close_on_validate: bool) -> SelfClosePool {
1627        use std::sync::Mutex as StdMutex;
1628        let slot: Arc<StdMutex<Option<SelfClosePool>>> = Arc::new(StdMutex::new(None));
1629        let s_create = slot.clone();
1630        let s_validate = slot.clone();
1631        let mut factory = AsyncFnFactory::new(move || {
1632            let s = s_create.clone();
1633            Box::pin(async move {
1634                if !close_on_validate {
1635                    let p = s.lock().unwrap().clone().unwrap();
1636                    p.close(None).await;
1637                }
1638                if create_ok {
1639                    Ok::<_, FactoryError>(Conn::new(7))
1640                } else {
1641                    Err::<Conn, FactoryError>("boom".into())
1642                }
1643            }) as _
1644        });
1645        // Only override validate when the test wants validation to close the pool; the closure
1646        // then closes unconditionally (its last expression is `true`), avoiding a brace after an
1647        // await that LLVM mis-attributes.
1648        if close_on_validate {
1649            factory = factory.with_validate(move |_c: &Conn| {
1650                let s = s_validate.clone();
1651                Box::pin(async move {
1652                    let p = s.lock().unwrap().clone().unwrap();
1653                    p.close(None).await;
1654                    true
1655                }) as _
1656            });
1657        }
1658        let pool = AsyncConnectionPool::new(factory, cfg, None).unwrap();
1659        *slot.lock().unwrap() = Some(pool.clone());
1660        pool
1661    }
1662
1663    #[tokio::test]
1664    async fn pool_closed_during_create_registers_as_closed() {
1665        let (pool, _cfg) = self_closing_create(true);
1666        assert!(matches!(
1667            pool.acquire(Timeout::Unset).await,
1668            Err(PoolError::Closed(_))
1669        ));
1670    }
1671
1672    #[tokio::test]
1673    async fn pool_closed_mid_retry_loop() {
1674        let (pool, _cfg) = self_closing_create(false);
1675        assert!(matches!(
1676            pool.acquire(Timeout::Unset).await,
1677            Err(PoolError::Closed(_))
1678        ));
1679    }
1680
1681    #[tokio::test]
1682    async fn fill_to_min_park_fails_when_closed_mid_create() {
1683        let cfg = PoolConfig {
1684            min_size: 1,
1685            retry: RetryPolicy::no_retry(),
1686            maintenance_interval: None,
1687            breaker: None,
1688            ..Default::default()
1689        };
1690        let pool = build_self_closing(cfg, true, false);
1691        pool.open().await.unwrap(); // warm-up creates, pool closes, park fails -> destroyed
1692        assert!(pool.is_closed());
1693    }
1694
1695    #[tokio::test]
1696    async fn health_check_closed_mid_validation() {
1697        let cfg = PoolConfig {
1698            validate_idle: true,
1699            validate_after_idle: None,
1700            max_lifetime: None,
1701            max_idle_time: None,
1702            maintenance_interval: None,
1703            breaker: None,
1704            ..Default::default()
1705        };
1706        let pool = build_self_closing(cfg, true, true);
1707        let a = pool.acquire(Timeout::Unset).await.unwrap();
1708        pool.release(a).await.unwrap(); // one idle conn
1709        pool.run_maintenance().await; // validate closes pool, reports healthy -> PoolClosed evict
1710        assert!(pool.is_closed());
1711    }
1712}