warmup/warmup.rs
1//! A guided tour of the pool's **lifecycle knobs** — [`min_idle`], [`warm`],
2//! [`state`] — on a single compio runtime, with no sockets and no threads at all.
3//!
4//! There is no server here and no [`std::thread::spawn`]: just one [`Runtime`]
5//! driving one [`LocalPool`], printing a [`State`] snapshot after each step so you
6//! can watch the open/idle counts move. The pooled "connection" is an abstract
7//! resource with an id — the pool is just as useful for any bounded, reused thing
8//! as it is for sockets — and a counter records how many were ever opened, which
9//! is what [`warm`] and reuse are there to keep down.
10//!
11//! What it demonstrates, in order:
12//!
13//! * [`warm`] opens [`min_idle`] connections up front and parks them idle;
14//! * [`get`] on a warm pool reuses an idle connection — no new open;
15//! * dropping a lease returns the connection to the idle list;
16//! * a burst reuses the idle ones first, then opens more only up to
17//! [`max_size`], which is the ceiling.
18//!
19//! ```text
20//! cargo run --release --example warmup
21//! ```
22//!
23//! [`min_idle`]: compio_pool::Builder::min_idle
24//! [`max_size`]: compio_pool::Builder::max_size
25//! [`warm`]: compio_pool::LocalPool::warm
26//! [`get`]: compio_pool::LocalPool::get
27//! [`state`]: compio_pool::LocalPool::state
28//! [`State`]: compio_pool::State
29
30use std::{
31 io,
32 sync::atomic::{AtomicU64, Ordering},
33};
34
35use compio::runtime::Runtime;
36use compio_pool::{ManageConnection, Pool};
37
38/// How many resources the manager has ever opened — the number `warm` and reuse
39/// exist to hold down.
40static OPENS: AtomicU64 = AtomicU64::new(0);
41
42/// One pooled resource. A real manager would open a socket or a client here; this
43/// one just stamps an id so `connect` does something observable.
44struct Resource {
45 #[allow(dead_code)]
46 id: u64,
47}
48
49/// Opens abstract resources and counts every open.
50struct Manager;
51
52impl ManageConnection for Manager {
53 type Connection = Resource;
54 type Error = io::Error;
55
56 async fn connect(&self) -> io::Result<Resource> {
57 let id = OPENS.fetch_add(1, Ordering::Relaxed);
58 Ok(Resource { id })
59 }
60
61 async fn is_valid(&self, _conn: &mut Resource) -> io::Result<()> {
62 Ok(())
63 }
64
65 fn has_broken(&self, _conn: &mut Resource) -> bool {
66 false
67 }
68}
69
70fn opened() -> u64 {
71 OPENS.load(Ordering::Relaxed)
72}
73
74fn main() -> io::Result<()> {
75 // Keep at most 8 per thread; warm 4 of them ahead of demand.
76 let pool = Pool::builder().max_size(8).min_idle(4).build(Manager);
77
78 // One runtime, one thread — the pool's natural unit.
79 let runtime = Runtime::builder().build().expect("build compio runtime");
80 runtime.block_on(async move {
81 let local = pool.local();
82 println!("fresh pool: {:?} opened={}", local.state(), opened());
83
84 // Warm opens min_idle connections up front and parks them on the idle list.
85 let made = local.warm().await.expect("warm");
86 println!(
87 "after warm: {:?} opened={} (warm made {made})",
88 local.state(),
89 opened()
90 );
91
92 // Checking three out pulls them off the warm idle list — no new opens.
93 let a = local.get().await.expect("get a");
94 let b = local.get().await.expect("get b");
95 let c = local.get().await.expect("get c");
96 println!("3 checked out: {:?} opened={}", local.state(), opened());
97
98 // Dropping the leases returns the resources to the idle list.
99 drop((a, b, c));
100 println!("3 returned: {:?} opened={}", local.state(), opened());
101
102 // A burst reuses the 4 idle resources first, then opens more — but only up
103 // to max_size=8, which caps the pool. A 9th get here would wait for a free
104 // slot rather than open a ninth.
105 let mut held = Vec::new();
106 for _ in 0..8 {
107 held.push(local.get().await.expect("burst"));
108 }
109 println!("8 checked out: {:?} opened={}", local.state(), opened());
110
111 drop(held);
112 println!("8 returned: {:?} opened={}", local.state(), opened());
113 });
114
115 Ok(())
116}