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