1use std::{
4 any::Any,
5 cell::RefCell,
6 collections::HashMap,
7 rc::{Rc, Weak},
8 sync::{
9 Arc,
10 atomic::{AtomicBool, AtomicU64, Ordering::Relaxed},
11 },
12 time::Instant,
13};
14
15use crate::{
16 config::Config,
17 error::Error,
18 exchange::{Exchange, NoExchange, Parked, Unparked},
19 guard::Pooled,
20 manage::Manage,
21 metrics::{Counters, Metrics},
22 shard::Shard,
23 slot::Slot,
24};
25
26static NEXT_POOL_ID: AtomicU64 = AtomicU64::new(0);
27
28struct Reserved<'a, M: Manage> {
38 shard: &'a Rc<Shard<M>>,
39 counters: &'a Counters,
40 holds_conn: bool,
42 armed: bool,
43}
44
45impl<'a, M: Manage> Reserved<'a, M> {
46 fn empty(shard: &'a Rc<Shard<M>>, counters: &'a Counters) -> Self {
48 Self {
49 shard,
50 counters,
51 holds_conn: false,
52 armed: true,
53 }
54 }
55
56 fn holding(shard: &'a Rc<Shard<M>>, counters: &'a Counters) -> Self {
58 Self {
59 shard,
60 counters,
61 holds_conn: true,
62 armed: true,
63 }
64 }
65
66 fn disarm(mut self) {
68 self.armed = false;
69 }
70}
71
72impl<M: Manage> Drop for Reserved<'_, M> {
73 fn drop(&mut self) {
74 if !self.armed {
75 return;
76 }
77 if self.holds_conn {
78 Counters::dec(&self.counters.live);
81 Counters::inc(&self.counters.closed);
82 }
83 self.shard.release();
84 }
85}
86
87thread_local! {
88 static SHARDS: RefCell<HashMap<u64, Box<dyn Any>>> = RefCell::new(HashMap::new());
96}
97
98struct Inner<M: Manage, X: Exchange<M>> {
99 id: u64,
100 manager: Arc<M>,
101 config: Config,
102 exchange: X,
103 counters: Arc<Counters>,
104 generation: AtomicU64,
105 closed: AtomicBool,
106}
107
108pub struct Pool<M: Manage, X: Exchange<M> = NoExchange> {
119 inner: Arc<Inner<M, X>>,
120}
121
122impl<M: Manage, X: Exchange<M>> Clone for Pool<M, X> {
123 fn clone(&self) -> Self {
124 Self {
125 inner: self.inner.clone(),
126 }
127 }
128}
129
130impl<M: Manage> Pool<M> {
131 pub fn new(manager: M, config: Config) -> Self {
133 Self::with_exchange(manager, config, NoExchange)
134 }
135
136 pub fn builder(manager: M) -> Builder<M, NoExchange> {
138 Builder {
139 manager,
140 config: Config::default(),
141 exchange: NoExchange,
142 }
143 }
144}
145
146impl<M: Manage, X: Exchange<M>> Pool<M, X> {
147 pub fn with_exchange(manager: M, config: Config, exchange: X) -> Self {
149 Self {
150 inner: Arc::new(Inner {
151 id: NEXT_POOL_ID.fetch_add(1, Relaxed),
152 manager: Arc::new(manager),
153 config,
154 exchange,
155 counters: Arc::new(Counters::default()),
156 generation: AtomicU64::new(0),
157 closed: AtomicBool::new(false),
158 }),
159 }
160 }
161
162 pub fn manager(&self) -> &M {
164 self.inner.manager.as_ref()
165 }
166
167 pub fn config(&self) -> &Config {
169 &self.inner.config
170 }
171
172 pub fn local_size(&self) -> usize {
174 self.shard().size()
175 }
176
177 pub fn local_idle(&self) -> usize {
179 self.shard().idle_len()
180 }
181
182 pub fn metrics(&self) -> Metrics {
184 self.inner.counters.snapshot(self.inner.exchange.parked())
185 }
186
187 pub fn invalidate(&self) {
193 self.inner.generation.fetch_add(1, Relaxed);
194 self.inner.exchange.clear();
195 }
196
197 pub fn close(&self) {
204 self.inner.closed.store(true, Relaxed);
205 self.inner.exchange.clear();
206 let shard = self.shard();
207 for slot in shard.drain_all() {
208 self.destroy(&shard, slot);
209 }
210 }
211
212 pub fn is_closed(&self) -> bool {
214 self.inner.closed.load(Relaxed)
215 }
216
217 pub async fn warm(&self) -> Result<(), Error<M::Error>> {
222 let shard = self.shard();
223 let target = self.inner.config.effective_min_idle();
224 while shard.idle_len() < target {
225 if !shard.try_reserve(self.inner.config.max_size) {
226 break;
227 }
228 match self.inner.manager.connect().await {
229 Ok(conn) => {
230 Counters::inc(&self.inner.counters.created);
231 Counters::inc(&self.inner.counters.live);
232 let generation = self.inner.generation.load(Relaxed);
233 shard.push_idle(Slot::new(conn, generation));
234 }
235 Err(e) => {
236 shard.release();
237 return Err(Error::Backend(e));
238 }
239 }
240 }
241 Ok(())
242 }
243
244 pub async fn acquire(&self) -> Result<Pooled<M, X>, Error<M::Error>> {
249 match self.inner.config.acquire_timeout {
250 Some(limit) => match compio::time::timeout(limit, self.acquire_inner()).await {
251 Ok(result) => result,
252 Err(_elapsed) => {
253 Counters::inc(&self.inner.counters.timeouts);
254 Err(Error::Timeout)
255 }
256 },
257 None => self.acquire_inner().await,
258 }
259 }
260
261 async fn acquire_inner(&self) -> Result<Pooled<M, X>, Error<M::Error>> {
262 let shard = self.shard();
263 loop {
264 if self.inner.closed.load(Relaxed) {
265 return Err(Error::Closed);
266 }
267 let generation = self.inner.generation.load(Relaxed);
268
269 while let Some(slot) = shard.pop_idle() {
271 if slot.is_expired(&self.inner.config, generation) {
272 self.destroy(&shard, slot);
273 continue;
274 }
275 let Slot { mut conn, meta } = slot;
276 let reserved = Reserved::holding(&shard, &self.inner.counters);
279 let recycled = self.inner.manager.recycle(&mut conn, &meta).await;
280 reserved.disarm();
281 match recycled {
282 Ok(()) => {
283 Counters::inc(&self.inner.counters.acquires);
284 return Ok(Pooled::new(Slot { conn, meta }, shard, self.clone()));
285 }
286 Err(_) => {
287 Counters::inc(&self.inner.counters.recycle_failures);
288 self.destroy_conn(&shard, conn);
289 }
290 }
291 }
292
293 if shard.try_reserve(self.inner.config.max_size) {
296 let reserved = Reserved::empty(&shard, &self.inner.counters);
299 let unparked = self.inner.exchange.unpark().await;
300 reserved.disarm();
301
302 match unparked {
303 Unparked::Claimed(conn, meta) => {
306 Counters::inc(&self.inner.counters.unparked);
307 Counters::inc(&self.inner.counters.live);
308 Counters::inc(&self.inner.counters.acquires);
309 return Ok(Pooled::new(Slot { conn, meta }, shard, self.clone()));
310 }
311 Unparked::Lost => Counters::inc(&self.inner.counters.closed),
315 Unparked::Empty => {}
316 }
317 let reserved = Reserved::empty(&shard, &self.inner.counters);
318 let connected = self.inner.manager.connect().await;
319 reserved.disarm();
320 return match connected {
321 Ok(conn) => {
322 Counters::inc(&self.inner.counters.created);
323 Counters::inc(&self.inner.counters.live);
324 Counters::inc(&self.inner.counters.acquires);
325 Ok(Pooled::new(
326 Slot::new(conn, generation),
327 shard,
328 self.clone(),
329 ))
330 }
331 Err(e) => {
332 shard.release();
333 Err(Error::Backend(e))
334 }
335 };
336 }
337
338 Counters::inc(&self.inner.counters.waits);
341 shard.wait().await;
342 }
343 }
344
345 pub(crate) fn release(
347 &self,
348 shard: &Rc<Shard<M>>,
349 mut slot: Slot<M::Connection>,
350 poisoned: bool,
351 ) {
352 slot.meta.uses += 1;
353 slot.meta.last_used = Instant::now();
354
355 if poisoned {
356 Counters::inc(&self.inner.counters.poisoned);
357 return self.destroy(shard, slot);
358 }
359 if self.inner.closed.load(Relaxed) {
360 return self.destroy(shard, slot);
361 }
362 let generation = self.inner.generation.load(Relaxed);
363 if slot.is_expired(&self.inner.config, generation) {
364 return self.destroy(shard, slot);
365 }
366
367 if !shard.has_waiters() && shard.idle_len() >= self.inner.config.effective_min_idle() {
371 let Slot { conn, meta } = slot;
372 match self.inner.exchange.park(conn, meta) {
373 Parked::Accepted => {
374 Counters::dec(&self.inner.counters.live);
376 shard.release();
377 return;
378 }
379 Parked::Destroyed => {
382 Counters::dec(&self.inner.counters.live);
383 Counters::inc(&self.inner.counters.closed);
384 shard.release();
385 return;
386 }
387 Parked::Refused(conn, meta) => slot = Slot { conn, meta },
388 }
389 }
390
391 shard.push_idle(slot);
392 }
393
394 pub(crate) fn forget(&self, shard: &Rc<Shard<M>>) {
397 Counters::dec(&self.inner.counters.live);
398 shard.release();
399 }
400
401 fn destroy(&self, shard: &Rc<Shard<M>>, slot: Slot<M::Connection>) {
402 self.destroy_conn(shard, slot.conn);
403 }
404
405 fn destroy_conn(&self, shard: &Rc<Shard<M>>, conn: M::Connection) {
406 self.inner.manager.disconnect(conn);
407 Counters::dec(&self.inner.counters.live);
408 Counters::inc(&self.inner.counters.closed);
409 shard.release();
410 }
411
412 fn shard(&self) -> Rc<Shard<M>> {
414 let id = self.inner.id;
415
416 let (shard, created) = SHARDS.with(|shards| {
417 let mut shards = shards.borrow_mut();
418 match shards.get(&id) {
419 Some(any) => (
420 any.downcast_ref::<Rc<Shard<M>>>()
421 .expect("pool id uniquely determines shard type")
422 .clone(),
423 false,
424 ),
425 None => {
426 let shard = Rc::new(Shard::<M>::new(
431 self.inner.counters.clone(),
432 self.inner.manager.clone(),
433 ));
434 shards.insert(id, Box::new(shard.clone()));
435 (shard, true)
436 }
437 }
438 });
439
440 if created && self.inner.config.needs_reaper() {
443 self.spawn_reaper(&shard);
444 }
445 shard
446 }
447
448 fn spawn_reaper(&self, shard: &Rc<Shard<M>>) {
449 if compio::runtime::Runtime::try_with_current(|_| ()).is_err() {
452 return;
453 }
454 let pool = self.clone();
455 let shard = Rc::downgrade(shard);
456 compio::runtime::spawn(async move { pool.reap_loop(shard).await }).detach();
457 }
458
459 async fn reap_loop(self, shard: Weak<Shard<M>>) {
464 let interval = self.inner.config.reap_interval;
465 loop {
466 compio::time::sleep(interval).await;
467
468 let Some(shard) = shard.upgrade() else { return };
469 if self.inner.closed.load(Relaxed) {
470 for slot in shard.drain_all() {
471 self.destroy(&shard, slot);
472 }
473 return;
474 }
475
476 let generation = self.inner.generation.load(Relaxed);
477 for slot in shard.drain_expired(&self.inner.config, generation) {
478 self.destroy(&shard, slot);
479 }
480
481 let target = self.inner.config.effective_min_idle();
482 while shard.idle_len() < target && !shard.has_waiters() {
483 if !shard.try_reserve(self.inner.config.max_size) {
484 break;
485 }
486 match self.inner.manager.connect().await {
487 Ok(conn) => {
488 Counters::inc(&self.inner.counters.created);
489 Counters::inc(&self.inner.counters.live);
490 shard.push_idle(Slot::new(conn, generation));
491 }
492 Err(_) => {
493 shard.release();
494 break;
495 }
496 }
497 }
498 }
499 }
500}
501
502impl<M: Manage, X: Exchange<M>> std::fmt::Debug for Pool<M, X> {
503 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
504 f.debug_struct("Pool")
505 .field("id", &self.inner.id)
506 .field("closed", &self.inner.closed.load(Relaxed))
507 .field("metrics", &self.metrics())
508 .finish_non_exhaustive()
509 }
510}
511
512pub struct Builder<M: Manage, X: Exchange<M>> {
514 manager: M,
515 config: Config,
516 exchange: X,
517}
518
519impl<M: Manage, X: Exchange<M>> Builder<M, X> {
520 pub fn config(mut self, config: Config) -> Self {
522 self.config = config;
523 self
524 }
525
526 pub fn max_size(mut self, n: usize) -> Self {
528 self.config = self.config.clone().max_size(n);
529 self
530 }
531
532 pub fn min_idle(mut self, n: usize) -> Self {
534 self.config = self.config.clone().min_idle(n);
535 self
536 }
537
538 pub fn acquire_timeout(mut self, t: impl Into<Option<std::time::Duration>>) -> Self {
540 self.config = self.config.clone().acquire_timeout(t);
541 self
542 }
543
544 pub fn max_lifetime(mut self, t: impl Into<Option<std::time::Duration>>) -> Self {
546 self.config = self.config.clone().max_lifetime(t);
547 self
548 }
549
550 pub fn idle_timeout(mut self, t: impl Into<Option<std::time::Duration>>) -> Self {
552 self.config = self.config.clone().idle_timeout(t);
553 self
554 }
555
556 pub fn max_uses(mut self, n: impl Into<Option<u64>>) -> Self {
558 self.config = self.config.clone().max_uses(n);
559 self
560 }
561
562 pub fn exchange<Y: Exchange<M>>(self, exchange: Y) -> Builder<M, Y> {
564 Builder {
565 manager: self.manager,
566 config: self.config,
567 exchange,
568 }
569 }
570
571 pub fn build(self) -> Pool<M, X> {
573 Pool::with_exchange(self.manager, self.config, self.exchange)
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580 use crate::test_support::TestManager;
581
582 fn untimed() -> Config {
583 Config::new().max_lifetime(None).idle_timeout(None)
584 }
585
586 fn pool() -> Pool<TestManager> {
587 Pool::new(TestManager::new(), untimed())
588 }
589
590 #[test]
598 fn a_disarmed_empty_reservation_returns_nothing() {
599 let p = pool();
600 let shard = p.shard();
601 assert!(shard.try_reserve(4));
602
603 Reserved::empty(&shard, &p.inner.counters).disarm();
604
605 assert_eq!(shard.size(), 1, "the budget stayed claimed");
606 assert_eq!(p.metrics().closed, 0);
607 }
608
609 #[test]
612 fn a_dropped_empty_reservation_returns_the_budget() {
613 let p = pool();
614 let shard = p.shard();
615 assert!(shard.try_reserve(4));
616
617 drop(Reserved::empty(&shard, &p.inner.counters));
618
619 assert_eq!(shard.size(), 0);
620 let m = p.metrics();
621 assert_eq!(m.closed, 0, "there was no connection to close");
622 assert_eq!(m.live, 0);
623 }
624
625 #[test]
626 fn a_disarmed_holding_reservation_leaves_the_connection_counted() {
627 let p = pool();
628 let shard = p.shard();
629 assert!(shard.try_reserve(4));
630 Counters::inc(&p.inner.counters.live);
631
632 Reserved::holding(&shard, &p.inner.counters).disarm();
633
634 let m = p.metrics();
635 assert_eq!(m.live, 1, "the connection is still ours");
636 assert_eq!(m.closed, 0);
637 assert_eq!(shard.size(), 1);
638 }
639
640 #[test]
644 fn a_dropped_holding_reservation_counts_the_connection_closed() {
645 let p = pool();
646 let shard = p.shard();
647 assert!(shard.try_reserve(4));
648 Counters::inc(&p.inner.counters.live);
649
650 drop(Reserved::holding(&shard, &p.inner.counters));
651
652 let m = p.metrics();
653 assert_eq!(m.live, 0);
654 assert_eq!(m.closed, 1);
655 assert_eq!(shard.size(), 0, "and the budget comes back too");
656 }
657
658 #[test]
661 fn a_shard_is_created_once_per_thread_and_then_reused() {
662 let p = pool();
663 let first = p.shard();
664 let second = p.shard();
665 assert!(
666 Rc::ptr_eq(&first, &second),
667 "the hit path must not rebuild the shard"
668 );
669 assert!(Rc::ptr_eq(&first, &p.clone().shard()));
671 }
672
673 #[test]
674 fn two_pools_of_the_same_type_do_not_share_a_shard() {
675 let a = pool();
676 let b = pool();
677 assert!(
678 !Rc::ptr_eq(&a.shard(), &b.shard()),
679 "shards are keyed by pool id, not by connection type"
680 );
681 }
682
683 #[test]
686 fn a_pool_with_a_reaper_config_works_without_a_runtime() {
687 let p = Pool::new(TestManager::new(), Config::new().min_idle(2));
688 assert!(p.config().needs_reaper());
689 assert_eq!(p.local_size(), 0);
690 assert_eq!(p.local_idle(), 0);
691 }
692
693 #[test]
694 fn a_pool_without_a_reaper_config_skips_the_spawn_entirely() {
695 let p = pool();
696 assert!(!p.config().needs_reaper());
697 assert_eq!(p.local_size(), 0);
698 }
699
700 #[test]
703 fn debug_reports_identity_state_and_metrics() {
704 let p = pool();
705 let s = format!("{p:?}");
706 assert!(s.contains("closed: false"), "got {s}");
707 assert!(s.contains("metrics"), "got {s}");
708
709 p.close();
710 assert!(format!("{p:?}").contains("closed: true"));
711 }
712
713 #[test]
714 fn pool_ids_are_unique() {
715 let a = pool();
716 let b = pool();
717 assert_ne!(a.inner.id, b.inner.id);
718 }
719
720 #[test]
721 fn destroy_closes_through_the_manager_and_frees_budget() {
722 let manager = TestManager::new();
723 let counts = manager.counts();
724 let p = Pool::new(manager, untimed());
725 let shard = p.shard();
726 assert!(shard.try_reserve(4));
727 Counters::inc(&p.inner.counters.live);
728
729 p.destroy(&shard, Slot::new(crate::test_support::TestConn::new(0), 0));
730
731 assert_eq!(counts.disconnected(), 1);
732 assert_eq!(shard.size(), 0);
733 let m = p.metrics();
734 assert_eq!((m.live, m.closed), (0, 1));
735 }
736
737 #[test]
738 fn forget_frees_budget_without_closing_anything() {
739 let manager = TestManager::new();
740 let counts = manager.counts();
741 let p = Pool::new(manager, untimed());
742 let shard = p.shard();
743 assert!(shard.try_reserve(4));
744 Counters::inc(&p.inner.counters.live);
745
746 p.forget(&shard);
747
748 assert_eq!(counts.disconnected(), 0, "the caller owns it now");
749 assert_eq!(shard.size(), 0, "but the shard may open a replacement");
750 let m = p.metrics();
751 assert_eq!((m.live, m.closed), (0, 0));
752 }
753}