1use crate::dialect::Dialect;
12use crate::driver::{Driver, DriverConnection};
13use rustlavel_core::{Error, Result};
14use std::collections::VecDeque;
15use std::sync::Arc;
16use tokio::sync::{Mutex, Semaphore};
17
18struct Inner {
19 driver: Arc<dyn Driver>,
20 idle: Mutex<VecDeque<(u64, Box<dyn DriverConnection>)>>,
24 permits: Arc<Semaphore>,
26}
27
28#[derive(Clone)]
29pub struct Pool {
30 inner: Arc<Inner>,
31}
32
33impl Pool {
34 pub fn new(driver: Arc<dyn Driver>) -> Self {
37 let permits = Arc::new(Semaphore::new(driver.max_connections().max(1)));
38 Pool { inner: Arc::new(Inner { driver, idle: Mutex::new(VecDeque::new()), permits }) }
39 }
40
41 pub async fn verify(&self) -> Result<()> {
44 let mut connection = self.acquire().await?;
45 connection.simple_query("select 1").await?;
46 Ok(())
47 }
48
49 pub fn driver(&self) -> &Arc<dyn Driver> {
50 &self.inner.driver
51 }
52
53 pub fn dialect(&self) -> Arc<dyn Dialect> {
54 self.inner.driver.dialect()
55 }
56
57 pub async fn acquire(&self) -> Result<PooledConnection> {
59 let permit = Arc::clone(&self.inner.permits)
60 .acquire_owned()
61 .await
62 .map_err(|_| Error::msg("the database pool has been closed"))?;
63
64 let generation = self.inner.driver.generation();
65
66 loop {
72 let Some((opened_under, connection)) = self.inner.idle.lock().await.pop_front() else {
73 break;
74 };
75
76 if opened_under == generation {
77 return Ok(PooledConnection {
78 connection: Some(connection),
79 generation,
80 pool: Arc::clone(&self.inner),
81 _permit: permit,
82 });
83 }
84
85 connection.close().await;
86 }
87
88 let connection = self.inner.driver.connect().await?;
89 Ok(PooledConnection {
90 connection: Some(connection),
91 generation,
92 pool: Arc::clone(&self.inner),
93 _permit: permit,
94 })
95 }
96
97 pub async fn idle_count(&self) -> usize {
99 self.inner.idle.lock().await.len()
100 }
101
102 pub async fn close(&self) {
104 let mut idle = self.inner.idle.lock().await;
105 while let Some((_, connection)) = idle.pop_front() {
106 connection.close().await;
107 }
108 }
109
110 pub async fn retire_superseded(&self) -> usize {
121 let generation = self.inner.driver.generation();
122 let mut idle = self.inner.idle.lock().await;
123
124 let mut keeping = VecDeque::with_capacity(idle.len());
125 let mut closed = 0;
126
127 while let Some((opened_under, connection)) = idle.pop_front() {
128 if opened_under == generation {
129 keeping.push_back((opened_under, connection));
130 } else {
131 connection.close().await;
132 closed += 1;
133 }
134 }
135
136 *idle = keeping;
137 closed
138 }
139}
140
141pub struct PooledConnection {
143 connection: Option<Box<dyn DriverConnection>>,
144 generation: u64,
146 pool: Arc<Inner>,
147 _permit: tokio::sync::OwnedSemaphorePermit,
149}
150
151impl std::ops::Deref for PooledConnection {
152 type Target = dyn DriverConnection;
153
154 fn deref(&self) -> &(dyn DriverConnection + 'static) {
155 self.connection.as_deref().expect("connection is present until drop")
156 }
157}
158
159impl std::ops::DerefMut for PooledConnection {
160 fn deref_mut(&mut self) -> &mut (dyn DriverConnection + 'static) {
161 self.connection.as_deref_mut().expect("connection is present until drop")
162 }
163}
164
165impl Drop for PooledConnection {
166 fn drop(&mut self) {
167 let Some(connection) = self.connection.take() else { return };
168
169 if connection.is_broken() || connection.in_transaction() {
172 tokio::spawn(async move { connection.close().await });
173 return;
174 }
175
176 let pool = Arc::clone(&self.pool);
180 let generation = self.generation;
181 tokio::spawn(async move {
182 if generation != pool.driver.generation() {
183 connection.close().await;
184 return;
185 }
186 pool.idle.lock().await.push_back((generation, connection));
187 });
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::dialect::Postgres;
195 use crate::driver::BoxFuture;
196
197 struct Counting {
200 opened: Arc<std::sync::atomic::AtomicUsize>,
201 closed: Arc<std::sync::atomic::AtomicUsize>,
202 generation: Arc<std::sync::atomic::AtomicU64>,
203 }
204
205 struct Nothing(Arc<std::sync::atomic::AtomicUsize>);
206
207 impl DriverConnection for Nothing {
208 fn query<'a>(
209 &'a mut self,
210 _sql: &'a str,
211 _params: &'a [crate::value::Value],
212 ) -> BoxFuture<'a, Result<crate::driver::QueryResult>> {
213 Box::pin(async { Err(Error::msg("not a real connection")) })
214 }
215
216 fn simple_query<'a>(
217 &'a mut self,
218 _sql: &'a str,
219 ) -> BoxFuture<'a, Result<crate::driver::QueryResult>> {
220 Box::pin(async { Err(Error::msg("not a real connection")) })
221 }
222
223 fn is_broken(&self) -> bool {
224 false
225 }
226
227 fn in_transaction(&self) -> bool {
228 false
229 }
230
231 fn close(self: Box<Self>) -> BoxFuture<'static, ()> {
232 self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
233 Box::pin(async {})
234 }
235 }
236
237 impl Driver for Counting {
238 fn dialect(&self) -> Arc<dyn Dialect> {
239 Arc::new(Postgres)
240 }
241
242 fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
243 self.opened.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
244 let closed = Arc::clone(&self.closed);
245 Box::pin(async move { Ok(Box::new(Nothing(closed)) as Box<dyn DriverConnection>) })
246 }
247
248 fn describe(&self) -> String {
249 "test://counting".into()
250 }
251
252 fn generation(&self) -> u64 {
253 self.generation.load(std::sync::atomic::Ordering::Acquire)
254 }
255 }
256
257 fn counting() -> (Pool, Arc<std::sync::atomic::AtomicUsize>, Arc<std::sync::atomic::AtomicUsize>, Arc<std::sync::atomic::AtomicU64>) {
258 let opened = Arc::new(std::sync::atomic::AtomicUsize::new(0));
259 let closed = Arc::new(std::sync::atomic::AtomicUsize::new(0));
260 let generation = Arc::new(std::sync::atomic::AtomicU64::new(1));
261 let driver = Counting {
262 opened: Arc::clone(&opened),
263 closed: Arc::clone(&closed),
264 generation: Arc::clone(&generation),
265 };
266 (Pool::new(Arc::new(driver)), opened, closed, generation)
267 }
268
269 async fn settle() {
272 for _ in 0..8 {
273 tokio::task::yield_now().await;
274 }
275 }
276
277 #[tokio::test]
278 async fn a_connection_comes_back_and_is_reused() {
279 let (pool, opened, _, _) = counting();
280
281 drop(pool.acquire().await.unwrap());
282 settle().await;
283 drop(pool.acquire().await.unwrap());
284 settle().await;
285
286 assert_eq!(opened.load(std::sync::atomic::Ordering::SeqCst), 1, "the second borrow reused it");
287 }
288
289 #[tokio::test]
290 async fn an_idle_connection_from_a_rotated_credential_is_never_handed_out() {
291 let (pool, opened, closed, generation) = counting();
295
296 drop(pool.acquire().await.unwrap());
297 settle().await;
298 assert_eq!(pool.idle_count().await, 1);
299
300 generation.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
301
302 drop(pool.acquire().await.unwrap());
303 settle().await;
304
305 assert_eq!(opened.load(std::sync::atomic::Ordering::SeqCst), 2, "a fresh connection");
306 assert_eq!(closed.load(std::sync::atomic::Ordering::SeqCst), 1, "the stale one was closed");
307 }
308
309 #[tokio::test]
310 async fn a_borrowed_connection_is_retired_when_it_comes_back() {
311 let (pool, _, closed, generation) = counting();
315
316 let borrowed = pool.acquire().await.unwrap();
317 generation.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
318 drop(borrowed);
319 settle().await;
320
321 assert_eq!(pool.idle_count().await, 0);
322 assert_eq!(closed.load(std::sync::atomic::Ordering::SeqCst), 1);
323 }
324
325 #[tokio::test]
326 async fn retiring_early_closes_the_stale_and_keeps_the_current() {
327 let (pool, _, closed, generation) = counting();
328
329 let first = pool.acquire().await.unwrap();
331 let second = pool.acquire().await.unwrap();
332 drop(first);
333 drop(second);
334 settle().await;
335 assert_eq!(pool.idle_count().await, 2);
336
337 generation.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
338
339 assert_eq!(pool.retire_superseded().await, 2);
340 assert_eq!(pool.idle_count().await, 0);
341 assert_eq!(closed.load(std::sync::atomic::Ordering::SeqCst), 2);
342
343 drop(pool.acquire().await.unwrap());
345 settle().await;
346 assert_eq!(pool.retire_superseded().await, 0);
347 assert_eq!(pool.idle_count().await, 1);
348 }
349
350 #[tokio::test]
351 async fn a_pool_with_static_credentials_never_retires_anything() {
352 let (pool, opened, closed, _) = counting();
355
356 for _ in 0..5 {
357 drop(pool.acquire().await.unwrap());
358 settle().await;
359 }
360
361 assert_eq!(opened.load(std::sync::atomic::Ordering::SeqCst), 1);
362 assert_eq!(closed.load(std::sync::atomic::Ordering::SeqCst), 0);
363 }
364
365 struct Unreachable;
368
369 impl Driver for Unreachable {
370 fn dialect(&self) -> Arc<dyn Dialect> {
371 Arc::new(Postgres)
372 }
373
374 fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
375 Box::pin(async { Err(Error::msg("nothing is listening")) })
376 }
377
378 fn describe(&self) -> String {
379 "test://unreachable".into()
380 }
381
382 fn max_connections(&self) -> usize {
383 3
384 }
385 }
386
387 #[tokio::test]
388 async fn a_pool_opens_nothing_until_it_is_used() {
389 let pool = Pool::new(Arc::new(Unreachable));
390 assert_eq!(pool.idle_count().await, 0);
391 }
392
393 #[tokio::test]
394 async fn acquiring_reports_the_drivers_failure() {
395 let pool = Pool::new(Arc::new(Unreachable));
396
397 let error = match pool.acquire().await {
398 Err(error) => error.to_string(),
399 Ok(_) => panic!("this driver cannot connect"),
400 };
401 assert!(error.contains("nothing is listening"), "{error}");
402 }
403
404 #[tokio::test]
405 async fn the_pool_carries_its_drivers_dialect() {
406 let pool = Pool::new(Arc::new(Unreachable));
407 assert_eq!(pool.dialect().name(), "postgres");
408 }
409}