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}