Skip to main content

compio_pool/
pool.rs

1//! The pool: a `Send` blueprint you build once and clone onto every thread, and
2//! the strictly thread-local pool it stamps out on each.
3//!
4//! [`Pool`] holds nothing hot — only the manager, the configuration, a shutdown
5//! flag and a registry of per-thread metrics blocks, behind one `Arc`. Cloning it
6//! is an `Arc` bump. The connections, the idle list and the slot count all live in
7//! the [`LocalPool`] that [`Pool::local`] creates on a thread: `Rc` and `Cell`, no
8//! lock and no atom on the hot path, never `Send`. The type system then stops a
9//! connection — bound to one ring — from ever leaving its thread.
10//!
11//! # Metrics across threads
12//!
13//! Each [`LocalPool`] owns one [`Metrics`] block and registers a clone of it with
14//! the [`Pool`]. Only the owning thread ever writes its block, so the counters are
15//! plain relaxed atomics with no cross-thread contention. [`Pool::state`] and
16//! [`Pool::statistics`] lock the registry — a cold path — and sum every block to
17//! give a process-wide view; [`LocalPool::state`] and [`LocalPool::statistics`]
18//! read only this thread's block.
19
20use std::{
21    cell::{Cell, RefCell},
22    fmt,
23    future::Future,
24    ops::{Deref, DerefMut},
25    pin::Pin,
26    rc::Rc,
27    sync::{
28        Arc, Mutex, PoisonError,
29        atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering::Relaxed},
30    },
31    task::{Context, Poll, Waker},
32    time::{Duration, Instant},
33};
34
35use crate::{
36    builder::{Builder, PoolConfig},
37    manage::{ManageConnection, RunError},
38};
39
40/// A blueprint for per-thread pools: build it once, clone it onto each compio
41/// thread you spawn, and call [`local`](Self::local) there.
42///
43/// `Clone + Send + Sync`. Cloning shares nothing that is touched on the hot path
44/// — only the manager, the [`Builder`] settings, the shutdown flag and the
45/// metrics registry. Every connection, idle list and slot counter belongs to one
46/// thread's [`LocalPool`].
47pub struct Pool<M: ManageConnection> {
48    shared: Arc<Shared<M>>,
49}
50
51pub(crate) struct Shared<M: ManageConnection> {
52    manager: M,
53    config: PoolConfig,
54    /// Set by [`Pool::close`]; read on every `get` and connection return.
55    closed: AtomicBool,
56    /// One metrics block per [`LocalPool`] ever created, for process-wide
57    /// aggregation. Locked only when a pool is created or stats are read.
58    metrics: Mutex<Vec<Arc<Metrics>>>,
59}
60
61impl<M: ManageConnection> Clone for Pool<M> {
62    fn clone(&self) -> Self {
63        Self {
64            shared: self.shared.clone(),
65        }
66    }
67}
68
69impl<M: ManageConnection> Pool<M> {
70    pub(crate) fn new(manager: M, config: PoolConfig) -> Self {
71        Self {
72            shared: Arc::new(Shared {
73                manager,
74                config,
75                closed: AtomicBool::new(false),
76                metrics: Mutex::new(Vec::new()),
77            }),
78        }
79    }
80
81    /// Start configuring a pool.
82    pub fn builder() -> Builder<M> {
83        Builder::new()
84    }
85
86    /// The per-thread ceiling on open connections.
87    pub fn max_size(&self) -> u32 {
88        self.shared.config.max_size
89    }
90
91    /// Make this thread's pool.
92    ///
93    /// Each call creates an **independent** [`LocalPool`] bound to the calling
94    /// thread. Call it once per compio thread and keep the handle; it is `Clone`
95    /// (a cheap `Rc` bump) if several tasks on that thread need it. Because the
96    /// returned pool is `!Send`, the compiler guarantees it — and the ring-bound
97    /// connections it holds — never move to another thread.
98    pub fn local(&self) -> LocalPool<M> {
99        LocalPool::new(self.shared.clone())
100    }
101
102    /// Shut the pool down for every thread.
103    ///
104    /// After this, each thread's [`LocalPool::get`] fails immediately with
105    /// [`RunError::Closed`], a returned [`PooledConnection`] is dropped instead of
106    /// recycled, and [`warm`](LocalPool::warm) / [`maintain`](LocalPool::maintain)
107    /// become no-ops. Connections already checked out keep working until their
108    /// holders drop them.
109    ///
110    /// The flag is shared, but the wakers that unpark a task waiting in `get` are
111    /// not — they live on each worker's own thread. A task already parked waiting
112    /// for a slot therefore unblocks on its next **local** wake (another
113    /// connection returning, or [`LocalPool::clear`]) or when its
114    /// [`connection_timeout`](Builder::connection_timeout) elapses, not the instant
115    /// `close` is called from another thread. For a prompt drain, stop each
116    /// worker's accept loop and call [`LocalPool::clear`] on its thread.
117    pub fn close(&self) {
118        self.shared.closed.store(true, Relaxed);
119    }
120
121    /// Whether [`close`](Self::close) has been called.
122    pub fn is_closed(&self) -> bool {
123        self.shared.closed.load(Relaxed)
124    }
125
126    /// A process-wide snapshot: the live connection, idle and waiter counts summed
127    /// across every thread's [`LocalPool`]. Takes the registry lock, so call it off
128    /// the hot path (a metrics scrape, a health endpoint). For one thread's numbers
129    /// use [`LocalPool::state`].
130    pub fn state(&self) -> State {
131        let registry = self
132            .shared
133            .metrics
134            .lock()
135            .unwrap_or_else(PoisonError::into_inner);
136        let mut state = State::default();
137        for m in registry.iter() {
138            state.connections += m.connections.load(Relaxed);
139            state.idle_connections += m.idle.load(Relaxed);
140            state.pending_waiters += m.waiters.load(Relaxed);
141        }
142        state
143    }
144
145    /// Process-wide cumulative counters summed across every thread's
146    /// [`LocalPool`]. Like [`state`](Self::state) it takes the registry lock and is
147    /// meant for a scrape, not the hot path. For one thread's totals use
148    /// [`LocalPool::statistics`].
149    pub fn statistics(&self) -> Statistics {
150        let registry = self
151            .shared
152            .metrics
153            .lock()
154            .unwrap_or_else(PoisonError::into_inner);
155        let mut stats = Statistics::default();
156        let mut micros = 0u64;
157        for m in registry.iter() {
158            stats.get_direct += m.get_direct.load(Relaxed);
159            stats.get_waited += m.get_waited.load(Relaxed);
160            stats.get_timed_out += m.get_timed_out.load(Relaxed);
161            stats.connections_created += m.connections_created.load(Relaxed);
162            stats.connections_closed_broken += m.connections_closed_broken.load(Relaxed);
163            stats.connections_closed_idle_timeout +=
164                m.connections_closed_idle_timeout.load(Relaxed);
165            stats.connections_closed_max_lifetime +=
166                m.connections_closed_max_lifetime.load(Relaxed);
167            micros += m.wait_micros.load(Relaxed);
168        }
169        stats.total_wait_time = Duration::from_micros(micros);
170        stats
171    }
172}
173
174impl<M: ManageConnection> fmt::Debug for Pool<M> {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        f.debug_struct("Pool")
177            .field("max_size", &self.shared.config.max_size)
178            .field("closed", &self.is_closed())
179            .finish_non_exhaustive()
180    }
181}
182
183/// A snapshot of live connection counts. From [`LocalPool::state`] it is one
184/// thread's; from [`Pool::state`] it is the sum across every thread.
185#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
186pub struct State {
187    /// Open connections right now: idle plus checked out.
188    pub connections: u32,
189    /// How many of those are idle on the free list.
190    pub idle_connections: u32,
191    /// Tasks currently parked in [`get`](LocalPool::get) waiting for a connection.
192    pub pending_waiters: u32,
193}
194
195/// Cumulative counters over a pool's life. From [`LocalPool::statistics`] it is
196/// one thread's; from [`Pool::statistics`] it is the sum across every thread.
197///
198/// Counters only ever grow (connections opened and closed are counted
199/// separately), so two snapshots subtracted give the activity in between — the
200/// shape a metrics exporter wants.
201#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
202pub struct Statistics {
203    /// [`get`](LocalPool::get) calls served without waiting — an idle connection
204    /// was ready, or there was room to open one straight away.
205    pub get_direct: u64,
206    /// [`get`](LocalPool::get) calls that had to park for a slot before being
207    /// served.
208    pub get_waited: u64,
209    /// [`get`](LocalPool::get) calls that gave up with
210    /// [`RunError::TimedOut`](crate::RunError::TimedOut).
211    pub get_timed_out: u64,
212    /// Connections opened by the manager.
213    pub connections_created: u64,
214    /// Connections dropped because [`has_broken`](ManageConnection::has_broken)
215    /// or [`is_valid`](ManageConnection::is_valid) rejected them.
216    pub connections_closed_broken: u64,
217    /// Connections dropped for passing [`idle_timeout`](Builder::idle_timeout).
218    pub connections_closed_idle_timeout: u64,
219    /// Connections dropped for passing [`max_lifetime`](Builder::max_lifetime).
220    pub connections_closed_max_lifetime: u64,
221    /// Total time [`get`](LocalPool::get) callers spent parked waiting, across the
222    /// `get_waited` calls.
223    pub total_wait_time: Duration,
224}
225
226impl Statistics {
227    /// Mean time a waiting caller parked, or zero if none ever waited.
228    pub fn average_wait_time(&self) -> Duration {
229        self.total_wait_time
230            .checked_div(self.get_waited as u32)
231            .unwrap_or_default()
232    }
233}
234
235/// Per-thread counters, written only by the owning thread (so plain relaxed
236/// atomics suffice) and summed across threads by [`Pool::state`] /
237/// [`Pool::statistics`]. The live gauges are zeroed when the owning thread's
238/// [`Inner`] drops; the cumulative counters outlive it.
239#[derive(Default)]
240struct Metrics {
241    // Live gauges.
242    connections: AtomicU32,
243    idle: AtomicU32,
244    waiters: AtomicU32,
245    // Cumulative counters.
246    get_direct: AtomicU64,
247    get_waited: AtomicU64,
248    get_timed_out: AtomicU64,
249    connections_created: AtomicU64,
250    connections_closed_broken: AtomicU64,
251    connections_closed_idle_timeout: AtomicU64,
252    connections_closed_max_lifetime: AtomicU64,
253    wait_micros: AtomicU64,
254}
255
256impl Metrics {
257    fn inc_conn(&self) {
258        self.connections.fetch_add(1, Relaxed);
259    }
260
261    fn dec_conn(&self, n: u32) {
262        let c = self.connections.load(Relaxed);
263        self.connections.store(c.saturating_sub(n), Relaxed);
264    }
265
266    fn inc_idle(&self) {
267        self.idle.fetch_add(1, Relaxed);
268    }
269
270    fn dec_idle(&self, n: u32) {
271        let c = self.idle.load(Relaxed);
272        self.idle.store(c.saturating_sub(n), Relaxed);
273    }
274
275    /// Count a connection retired for the given expiry reason.
276    fn note_expired(&self, reason: Expiry) {
277        match reason {
278            Expiry::IdleTimeout => self.connections_closed_idle_timeout.fetch_add(1, Relaxed),
279            Expiry::MaxLifetime => self.connections_closed_max_lifetime.fetch_add(1, Relaxed),
280        };
281    }
282
283    fn snapshot_state(&self) -> State {
284        State {
285            connections: self.connections.load(Relaxed),
286            idle_connections: self.idle.load(Relaxed),
287            pending_waiters: self.waiters.load(Relaxed),
288        }
289    }
290
291    fn snapshot_statistics(&self) -> Statistics {
292        Statistics {
293            get_direct: self.get_direct.load(Relaxed),
294            get_waited: self.get_waited.load(Relaxed),
295            get_timed_out: self.get_timed_out.load(Relaxed),
296            connections_created: self.connections_created.load(Relaxed),
297            connections_closed_broken: self.connections_closed_broken.load(Relaxed),
298            connections_closed_idle_timeout: self.connections_closed_idle_timeout.load(Relaxed),
299            connections_closed_max_lifetime: self.connections_closed_max_lifetime.load(Relaxed),
300            total_wait_time: Duration::from_micros(self.wait_micros.load(Relaxed)),
301        }
302    }
303}
304
305/// One connection plus the timestamps that decide when it is retired.
306struct Conn<C> {
307    raw: C,
308    created_at: Instant,
309    last_used: Instant,
310}
311
312/// Why a connection is being retired, so the close can be attributed to the right
313/// counter.
314#[derive(Clone, Copy)]
315enum Expiry {
316    IdleTimeout,
317    MaxLifetime,
318}
319
320/// `max_lifetime` is checked first, so a connection that is both too old and too
321/// idle is attributed to age.
322fn expiry<C>(conn: &Conn<C>, now: Instant, cfg: &PoolConfig) -> Option<Expiry> {
323    if let Some(max) = cfg.max_lifetime
324        && now.duration_since(conn.created_at) >= max
325    {
326        return Some(Expiry::MaxLifetime);
327    }
328    if let Some(idle) = cfg.idle_timeout
329        && now.duration_since(conn.last_used) >= idle
330    {
331        return Some(Expiry::IdleTimeout);
332    }
333    None
334}
335
336struct Inner<M: ManageConnection> {
337    shared: Arc<Shared<M>>,
338    idle: RefCell<Vec<Conn<M::Connection>>>,
339    /// This thread's counters, also held (cloned `Arc`) in the pool's registry.
340    metrics: Arc<Metrics>,
341    /// Woken when a slot frees or a connection returns to the idle list.
342    waiters: Waiters,
343}
344
345impl<M: ManageConnection> Inner<M> {
346    fn connections(&self) -> u32 {
347        self.metrics.connections.load(Relaxed)
348    }
349
350    fn closed(&self) -> bool {
351        self.shared.closed.load(Relaxed)
352    }
353
354    /// Give back one slot and wake anyone waiting for capacity.
355    fn release_slot(&self) {
356        self.metrics.dec_conn(1);
357        self.waiters.wake_all();
358    }
359
360    fn push_idle(&self, conn: Conn<M::Connection>) {
361        self.idle.borrow_mut().push(conn);
362        self.metrics.inc_idle();
363    }
364
365    fn pop_idle(&self) -> Option<Conn<M::Connection>> {
366        let conn = self.idle.borrow_mut().pop();
367        if conn.is_some() {
368            self.metrics.dec_idle(1);
369        }
370        conn
371    }
372}
373
374impl<M: ManageConnection> Drop for Inner<M> {
375    fn drop(&mut self) {
376        // The connections this thread held drop with `idle`; zero the live gauges
377        // so a finished worker no longer shows up in `Pool::state`. The cumulative
378        // counters stay, so lifetime totals survive the thread.
379        self.metrics.connections.store(0, Relaxed);
380        self.metrics.idle.store(0, Relaxed);
381        self.metrics.waiters.store(0, Relaxed);
382    }
383}
384
385/// One thread's pool, created by [`Pool::local`].
386///
387/// Holds the idle connections and the slot count for this thread alone. `Clone`
388/// shares the same per-thread pool between tasks on the thread; it is never
389/// `Send`.
390pub struct LocalPool<M: ManageConnection> {
391    inner: Rc<Inner<M>>,
392}
393
394impl<M: ManageConnection> Clone for LocalPool<M> {
395    fn clone(&self) -> Self {
396        Self {
397            inner: self.inner.clone(),
398        }
399    }
400}
401
402impl<M: ManageConnection> LocalPool<M> {
403    pub(crate) fn new(shared: Arc<Shared<M>>) -> Self {
404        let cap = shared.config.max_size.min(1024) as usize;
405        let metrics = Arc::new(Metrics::default());
406        shared
407            .metrics
408            .lock()
409            .unwrap_or_else(PoisonError::into_inner)
410            .push(metrics.clone());
411        Self {
412            inner: Rc::new(Inner {
413                shared,
414                idle: RefCell::new(Vec::with_capacity(cap)),
415                waiters: Waiters::new(metrics.clone()),
416                metrics,
417            }),
418        }
419    }
420
421    /// The per-thread ceiling on open connections.
422    pub fn max_size(&self) -> u32 {
423        self.inner.shared.config.max_size
424    }
425
426    /// A snapshot of **this thread's** open, idle and waiting counts. Lock-free.
427    /// For the process-wide sum use [`Pool::state`].
428    pub fn state(&self) -> State {
429        self.inner.metrics.snapshot_state()
430    }
431
432    /// **This thread's** cumulative counters. Lock-free. For the process-wide sum
433    /// use [`Pool::statistics`].
434    pub fn statistics(&self) -> Statistics {
435        self.inner.metrics.snapshot_statistics()
436    }
437
438    /// Open connections up to [`min_idle`](Builder::min_idle) and park them on the
439    /// idle list. Returns how many were created. Tops up to the floor each time it
440    /// is called, so it doubles as the warm-up and the min-idle maintenance step;
441    /// [`maintain`](Self::maintain) pairs it with a reap. A no-op (returns `Ok(0)`)
442    /// once the pool is [`closed`](Pool::close). Stops early and returns
443    /// [`RunError::User`] if the manager fails to connect.
444    pub async fn warm(&self) -> Result<u32, RunError<M::Error>> {
445        if self.inner.closed() {
446            return Ok(0);
447        }
448        let target = self.inner.shared.config.min_idle;
449        let mut made = 0;
450        while self.inner.connections() < target {
451            let Some(mut held) = self.try_reserve() else {
452                break;
453            };
454            match self.inner.shared.manager.connect().await {
455                Ok(raw) => {
456                    let now = Instant::now();
457                    held.conn = Some(Conn {
458                        raw,
459                        created_at: now,
460                        last_used: now,
461                    });
462                    self.inner.metrics.connections_created.fetch_add(1, Relaxed);
463                    held.into_idle();
464                    made += 1;
465                }
466                // `held` drops here, releasing the slot it reserved.
467                Err(e) => return Err(RunError::User(e)),
468            }
469        }
470        Ok(made)
471    }
472
473    /// Drop idle connections that have passed [`idle_timeout`](Builder::idle_timeout)
474    /// or [`max_lifetime`](Builder::max_lifetime), freeing their slots. Returns how
475    /// many were reaped.
476    ///
477    /// Expiry is otherwise lazy — a connection is only checked as it is handed out
478    /// or returned — so a pool that goes quiet would hold dead backend connections
479    /// open indefinitely. Call this on a timer (see [`maintain`](Self::maintain))
480    /// to retire them promptly. Only idle connections are considered; one that is
481    /// checked out is left to its holder.
482    pub fn reap(&self) -> u32 {
483        let now = Instant::now();
484        let cfg = &self.inner.shared.config;
485        let metrics = &self.inner.metrics;
486        let mut reaped = 0u32;
487        {
488            let mut idle = self.inner.idle.borrow_mut();
489            idle.retain(|conn| match expiry(conn, now, cfg) {
490                Some(reason) => {
491                    metrics.note_expired(reason);
492                    reaped += 1;
493                    false
494                }
495                None => true,
496            });
497        }
498        if reaped > 0 {
499            metrics.dec_idle(reaped);
500            metrics.dec_conn(reaped);
501            self.inner.waiters.wake_all();
502        }
503        reaped
504    }
505
506    /// The periodic maintenance step: [`reap`](Self::reap) expired idle
507    /// connections, then [`warm`](Self::warm) back up to
508    /// [`min_idle`](Builder::min_idle). Returns how many fresh connections were
509    /// opened. Call it from a timer on each worker thread — thread-per-core has no
510    /// shared reaper thread, so maintenance is yours to drive:
511    ///
512    /// ```no_run
513    /// # async fn run<M: compio_pool::ManageConnection>(
514    /// #     local: compio_pool::LocalPool<M>,
515    /// # ) {
516    /// use std::time::Duration;
517    /// compio::runtime::spawn(async move {
518    ///     loop {
519    ///         compio::time::sleep(Duration::from_secs(30)).await;
520    ///         let _ = local.maintain().await;
521    ///     }
522    /// })
523    /// .detach();
524    /// # }
525    /// ```
526    pub async fn maintain(&self) -> Result<u32, RunError<M::Error>> {
527        self.reap();
528        self.warm().await
529    }
530
531    /// Drop every idle connection now, freeing their slots, and return how many
532    /// were dropped. Connections currently checked out are untouched and return as
533    /// usual. Use it to drain after a backend failover, or as part of a shutdown
534    /// once [`Pool::close`] has been called. Unlike [`reap`](Self::reap) it ignores
535    /// the timeouts and clears unconditionally.
536    pub fn clear(&self) -> u32 {
537        let n = {
538            let mut idle = self.inner.idle.borrow_mut();
539            let n = idle.len() as u32;
540            idle.clear();
541            n
542        };
543        if n > 0 {
544            self.inner.metrics.dec_idle(n);
545            self.inner.metrics.dec_conn(n);
546            self.inner.waiters.wake_all();
547        }
548        n
549    }
550
551    /// Check a connection out of this thread's pool, waiting up to
552    /// [`connection_timeout`](Builder::connection_timeout) for one.
553    ///
554    /// Reuses an idle connection when there is one (validated first if
555    /// [`test_on_check_out`](Builder::test_on_check_out) is set), otherwise opens
556    /// one while under `max_size`, otherwise waits for a slot. The returned
557    /// [`PooledConnection`] derefs to the connection and returns it to this pool
558    /// when dropped. Fails with [`RunError::Closed`] if the pool has been
559    /// [`closed`](Pool::close).
560    pub async fn get(&self) -> Result<PooledConnection<M>, RunError<M::Error>> {
561        if self.inner.closed() {
562            return Err(RunError::Closed);
563        }
564        let timeout = self.inner.shared.config.connection_timeout;
565        match compio::time::timeout(timeout, self.get_inner()).await {
566            Ok(result) => result,
567            Err(_elapsed) => {
568                self.inner.metrics.get_timed_out.fetch_add(1, Relaxed);
569                Err(RunError::TimedOut)
570            }
571        }
572    }
573
574    async fn get_inner(&self) -> Result<PooledConnection<M>, RunError<M::Error>> {
575        let start = Instant::now();
576        let mut waited = false;
577        loop {
578            if self.inner.closed() {
579                return Err(RunError::Closed);
580            }
581            // Wait until there is an idle connection or room to open one. The
582            // check-and-take below is synchronous, so what we observed still
583            // holds when we act on it. `wait_ready` reports whether it parked.
584            waited |= self.wait_ready().await;
585            if self.inner.closed() {
586                return Err(RunError::Closed);
587            }
588
589            // Prefer a live idle connection.
590            if let Some(mut held) = self.pop_fresh_idle() {
591                if self.inner.shared.config.test_on_check_out {
592                    // Scope the borrow of `held` so it ends before the `drop`.
593                    let valid = {
594                        let conn = held.conn.as_mut().expect("idle held carries a connection");
595                        self.inner
596                            .shared
597                            .manager
598                            .is_valid(&mut conn.raw)
599                            .await
600                            .is_ok()
601                    };
602                    if !valid {
603                        // Stale: count it closed, drop it (which frees the slot),
604                        // and try again rather than failing.
605                        self.inner
606                            .metrics
607                            .connections_closed_broken
608                            .fetch_add(1, Relaxed);
609                        drop(held);
610                        continue;
611                    }
612                }
613                self.record_acquire(waited, start);
614                return Ok(held.into_pooled());
615            }
616
617            // Nothing idle; open one if there is room.
618            if let Some(mut held) = self.try_reserve() {
619                match self.inner.shared.manager.connect().await {
620                    Ok(raw) => {
621                        let now = Instant::now();
622                        held.conn = Some(Conn {
623                            raw,
624                            created_at: now,
625                            last_used: now,
626                        });
627                        self.inner.metrics.connections_created.fetch_add(1, Relaxed);
628                        self.record_acquire(waited, start);
629                        return Ok(held.into_pooled());
630                    }
631                    // `held` drops, releasing the reserved slot.
632                    Err(e) => return Err(RunError::User(e)),
633                }
634            }
635            // Another task took the slot between the wait and here: loop.
636        }
637    }
638
639    /// Record a successful checkout: direct, or waited-with-elapsed.
640    fn record_acquire(&self, waited: bool, start: Instant) {
641        if waited {
642            self.inner.metrics.get_waited.fetch_add(1, Relaxed);
643            self.inner
644                .metrics
645                .wait_micros
646                .fetch_add(start.elapsed().as_micros() as u64, Relaxed);
647        } else {
648            self.inner.metrics.get_direct.fetch_add(1, Relaxed);
649        }
650    }
651
652    /// Reserve a slot for a new connection, if under `max_size`.
653    fn try_reserve(&self) -> Option<Held<M>> {
654        let taken = self.inner.connections();
655        if taken < self.inner.shared.config.max_size {
656            self.inner.metrics.inc_conn();
657            Some(Held {
658                inner: self.inner.clone(),
659                conn: None,
660                released: false,
661            })
662        } else {
663            None
664        }
665    }
666
667    /// Pop the newest idle connection, discarding any that have expired (each
668    /// discard frees its slot). `None` when the idle list is empty.
669    fn pop_fresh_idle(&self) -> Option<Held<M>> {
670        let now = Instant::now();
671        loop {
672            let conn = self.inner.pop_idle()?;
673            if let Some(reason) = expiry(&conn, now, &self.inner.shared.config) {
674                drop(conn);
675                self.inner.metrics.note_expired(reason);
676                self.inner.release_slot();
677                continue;
678            }
679            return Some(Held {
680                inner: self.inner.clone(),
681                conn: Some(conn),
682                released: false,
683            });
684        }
685    }
686
687    fn ready_now(&self) -> bool {
688        self.inner.closed()
689            || self.inner.metrics.idle.load(Relaxed) > 0
690            || self.inner.connections() < self.inner.shared.config.max_size
691    }
692
693    fn wait_ready(&self) -> WaitReady<'_, M> {
694        WaitReady {
695            pool: self,
696            id: None,
697            parked: false,
698        }
699    }
700}
701
702impl<M: ManageConnection> fmt::Debug for LocalPool<M> {
703    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704        let state = self.state();
705        f.debug_struct("LocalPool")
706            .field("max_size", &self.inner.shared.config.max_size)
707            .field("connections", &state.connections)
708            .field("idle", &state.idle_connections)
709            .field("pending_waiters", &state.pending_waiters)
710            .finish()
711    }
712}
713
714/// A connection, or a reserved-but-not-yet-filled slot, that is counted in
715/// `connections` but not yet owned by a [`PooledConnection`]. Dropping it before
716/// it is committed releases the slot (and drops the connection, if any), which
717/// makes every early return and cancellation in `get`/`warm` leak-free.
718struct Held<M: ManageConnection> {
719    inner: Rc<Inner<M>>,
720    conn: Option<Conn<M::Connection>>,
721    released: bool,
722}
723
724impl<M: ManageConnection> Held<M> {
725    /// Hand the connection to a [`PooledConnection`]; the slot stays taken.
726    fn into_pooled(mut self) -> PooledConnection<M> {
727        let conn = self.conn.take();
728        self.released = true;
729        PooledConnection {
730            inner: self.inner.clone(),
731            conn,
732        }
733    }
734
735    /// Park the connection on the idle list; the slot stays taken.
736    fn into_idle(mut self) {
737        if let Some(mut conn) = self.conn.take() {
738            conn.last_used = Instant::now();
739            self.inner.push_idle(conn);
740            self.inner.waiters.wake_all();
741        }
742        self.released = true;
743    }
744}
745
746impl<M: ManageConnection> Drop for Held<M> {
747    fn drop(&mut self) {
748        if !self.released {
749            self.conn.take();
750            self.inner.release_slot();
751        }
752    }
753}
754
755/// A connection checked out of its [`LocalPool`]. Derefs to the connection;
756/// dropping it runs [`has_broken`](ManageConnection::has_broken) and either
757/// returns the connection to this thread's idle list or drops it and frees the
758/// slot. If the pool has been [`closed`](Pool::close), the connection is always
759/// dropped.
760pub struct PooledConnection<M: ManageConnection> {
761    inner: Rc<Inner<M>>,
762    conn: Option<Conn<M::Connection>>,
763}
764
765impl<M: ManageConnection> Deref for PooledConnection<M> {
766    type Target = M::Connection;
767
768    fn deref(&self) -> &M::Connection {
769        &self
770            .conn
771            .as_ref()
772            .expect("connection present until drop")
773            .raw
774    }
775}
776
777impl<M: ManageConnection> DerefMut for PooledConnection<M> {
778    fn deref_mut(&mut self) -> &mut M::Connection {
779        &mut self
780            .conn
781            .as_mut()
782            .expect("connection present until drop")
783            .raw
784    }
785}
786
787impl<M: ManageConnection> Drop for PooledConnection<M> {
788    fn drop(&mut self) {
789        if let Some(mut conn) = self.conn.take() {
790            // A closing pool keeps nothing; drop and free the slot.
791            if self.inner.closed() {
792                drop(conn);
793                self.inner.release_slot();
794                return;
795            }
796            if self.inner.shared.manager.has_broken(&mut conn.raw) {
797                drop(conn);
798                self.inner
799                    .metrics
800                    .connections_closed_broken
801                    .fetch_add(1, Relaxed);
802                self.inner.release_slot();
803            } else if let Some(reason) = expiry(&conn, Instant::now(), &self.inner.shared.config) {
804                drop(conn);
805                self.inner.metrics.note_expired(reason);
806                self.inner.release_slot();
807            } else {
808                conn.last_used = Instant::now();
809                self.inner.push_idle(conn);
810                // A connection is available again; wake a waiter.
811                self.inner.waiters.wake_all();
812            }
813        }
814    }
815}
816
817impl<M: ManageConnection> fmt::Debug for PooledConnection<M> {
818    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
819        f.debug_struct("PooledConnection")
820            .field("held", &self.conn.is_some())
821            .finish()
822    }
823}
824
825/// Wakers of futures waiting for the pool to be ready to hand out a connection.
826/// Each waiter owns an id so it can withdraw its waker if it is dropped first;
827/// nothing stale is ever woken. The registered count is mirrored into
828/// [`Metrics::waiters`] so [`Pool::state`] can see it from another thread.
829struct Waiters {
830    metrics: Arc<Metrics>,
831    next_id: Cell<u64>,
832    entries: RefCell<Vec<(u64, Waker)>>,
833}
834
835impl Waiters {
836    fn new(metrics: Arc<Metrics>) -> Self {
837        Self {
838            metrics,
839            next_id: Cell::new(0),
840            entries: RefCell::new(Vec::new()),
841        }
842    }
843
844    fn register(&self, id: &mut Option<u64>, waker: &Waker) {
845        let mut entries = self.entries.borrow_mut();
846        if let Some(existing) = *id
847            && let Some(entry) = entries.iter_mut().find(|(i, _)| *i == existing)
848        {
849            if !entry.1.will_wake(waker) {
850                entry.1 = waker.clone();
851            }
852            return;
853        }
854        let new_id = self.next_id.get();
855        self.next_id.set(new_id.wrapping_add(1));
856        entries.push((new_id, waker.clone()));
857        self.metrics.waiters.store(entries.len() as u32, Relaxed);
858        *id = Some(new_id);
859    }
860
861    fn unregister(&self, id: Option<u64>) {
862        if let Some(id) = id {
863            let mut entries = self.entries.borrow_mut();
864            entries.retain(|(i, _)| *i != id);
865            self.metrics.waiters.store(entries.len() as u32, Relaxed);
866        }
867    }
868
869    /// Wake everyone. The borrow is dropped before any waker runs, so a waker
870    /// that polls inline can register again without a double borrow.
871    fn wake_all(&self) {
872        let wakers: Vec<Waker> = self
873            .entries
874            .borrow_mut()
875            .drain(..)
876            .map(|(_, w)| w)
877            .collect();
878        self.metrics.waiters.store(0, Relaxed);
879        for waker in wakers {
880            waker.wake();
881        }
882    }
883}
884
885/// Resolves when the pool can hand out a connection — an idle one exists, there
886/// is room to open one, or the pool has closed — keeping exactly one waker
887/// registered until then. Its output is whether it ever parked, so `get` can tell
888/// a direct checkout from one that waited.
889struct WaitReady<'a, M: ManageConnection> {
890    pool: &'a LocalPool<M>,
891    id: Option<u64>,
892    parked: bool,
893}
894
895impl<M: ManageConnection> Future for WaitReady<'_, M> {
896    type Output = bool;
897
898    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<bool> {
899        let this = self.get_mut();
900        if this.pool.ready_now() {
901            this.pool.inner.waiters.unregister(this.id.take());
902            Poll::Ready(this.parked)
903        } else {
904            this.parked = true;
905            this.pool.inner.waiters.register(&mut this.id, cx.waker());
906            Poll::Pending
907        }
908    }
909}
910
911impl<M: ManageConnection> Drop for WaitReady<'_, M> {
912    fn drop(&mut self) {
913        self.pool.inner.waiters.unregister(self.id.take());
914    }
915}