1use crate::dialect::Dialect;
12use crate::driver::{Driver, DriverConnection};
13use rustlavel_core::{Error, Result};
14use std::collections::VecDeque;
15use std::sync::Arc;
16use std::sync::{Mutex, MutexGuard};
17use tokio::sync::Semaphore;
18
19struct Inner {
20 driver: Arc<dyn Driver>,
21 idle: Mutex<VecDeque<(u64, Box<dyn DriverConnection>)>>,
25 permits: Arc<Semaphore>,
27}
28
29#[derive(Clone)]
30pub struct Pool {
31 inner: Arc<Inner>,
32}
33
34impl Inner {
35 fn idle(&self) -> MutexGuard<'_, VecDeque<(u64, Box<dyn DriverConnection>)>> {
53 self.idle.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
54 }
55}
56
57impl Pool {
58 pub fn new(driver: Arc<dyn Driver>) -> Self {
61 let permits = Arc::new(Semaphore::new(driver.max_connections().max(1)));
62 Pool { inner: Arc::new(Inner { driver, idle: Mutex::new(VecDeque::new()), permits }) }
63 }
64
65 pub async fn verify(&self) -> Result<()> {
68 let mut connection = self.acquire().await?;
69 connection.simple_query("select 1").await?;
70 Ok(())
71 }
72
73 pub fn driver(&self) -> &Arc<dyn Driver> {
74 &self.inner.driver
75 }
76
77 pub fn dialect(&self) -> Arc<dyn Dialect> {
78 self.inner.driver.dialect()
79 }
80
81 pub async fn acquire(&self) -> Result<PooledConnection> {
83 let permit = Arc::clone(&self.inner.permits)
84 .acquire_owned()
85 .await
86 .map_err(|_| Error::msg("the database pool has been closed"))?;
87
88 let generation = self.inner.driver.generation();
89
90 loop {
96 let Some((opened_under, connection)) = self.inner.idle().pop_front() else {
97 break;
98 };
99
100 if opened_under == generation {
101 return Ok(PooledConnection {
102 connection: Some(connection),
103 generation,
104 pool: Arc::clone(&self.inner),
105 _permit: permit,
106 });
107 }
108
109 connection.close().await;
110 }
111
112 let connection = self.inner.driver.connect().await?;
113 Ok(PooledConnection {
114 connection: Some(connection),
115 generation,
116 pool: Arc::clone(&self.inner),
117 _permit: permit,
118 })
119 }
120
121 pub async fn idle_count(&self) -> usize {
123 self.inner.idle().len()
124 }
125
126 pub async fn open_count(&self) -> usize {
137 let borrowed = self
138 .inner
139 .driver
140 .max_connections()
141 .max(1)
142 .saturating_sub(self.inner.permits.available_permits());
143 self.inner.idle().len() + borrowed
144 }
145
146 pub async fn close_idle(&self, limit: usize) -> usize {
153 let mut closed = 0;
154 while closed < limit {
155 let Some((_, connection)) = self.inner.idle().pop_front() else { break };
156 connection.close().await;
157 closed += 1;
158 }
159 closed
160 }
161
162 pub async fn close(&self) {
164 let drained: Vec<_> = self.inner.idle().drain(..).collect();
167 for (_, connection) in drained {
168 connection.close().await;
169 }
170 }
171
172 pub async fn retire_superseded(&self) -> usize {
183 let generation = self.inner.driver.generation();
184
185 let superseded: Vec<_> = {
187 let mut idle = self.inner.idle();
188 let mut superseded = Vec::new();
189 let mut keeping = VecDeque::with_capacity(idle.len());
190 for entry in idle.drain(..) {
191 if entry.0 == generation {
192 keeping.push_back(entry);
193 } else {
194 superseded.push(entry);
195 }
196 }
197 *idle = keeping;
198 superseded
199 };
200
201 let closed = superseded.len();
202 for (_, connection) in superseded {
203 connection.close().await;
204 }
205 closed
206 }
207}
208
209pub struct PooledConnection {
211 connection: Option<Box<dyn DriverConnection>>,
212 generation: u64,
214 pool: Arc<Inner>,
215 _permit: tokio::sync::OwnedSemaphorePermit,
217}
218
219impl std::ops::Deref for PooledConnection {
220 type Target = dyn DriverConnection;
221
222 fn deref(&self) -> &(dyn DriverConnection + 'static) {
223 self.connection.as_deref().expect("connection is present until drop")
224 }
225}
226
227impl std::ops::DerefMut for PooledConnection {
228 fn deref_mut(&mut self) -> &mut (dyn DriverConnection + 'static) {
229 self.connection.as_deref_mut().expect("connection is present until drop")
230 }
231}
232
233impl Drop for PooledConnection {
234 fn drop(&mut self) {
235 let Some(connection) = self.connection.take() else { return };
236
237 if connection.is_broken() || connection.in_transaction() {
240 tokio::spawn(async move { connection.close().await });
241 return;
242 }
243
244 if self.generation != self.pool.driver.generation() {
249 tokio::spawn(async move { connection.close().await });
250 return;
251 }
252
253 self.pool.idle().push_back((self.generation, connection));
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::dialect::Postgres;
265 use crate::driver::BoxFuture;
266
267 struct Counting {
270 opened: Arc<std::sync::atomic::AtomicUsize>,
271 closed: Arc<std::sync::atomic::AtomicUsize>,
272 generation: Arc<std::sync::atomic::AtomicU64>,
273 }
274
275 struct Nothing(Arc<std::sync::atomic::AtomicUsize>);
276
277 impl DriverConnection for Nothing {
278 fn query<'a>(
279 &'a mut self,
280 _sql: &'a str,
281 _params: &'a [crate::value::Value],
282 ) -> BoxFuture<'a, Result<crate::driver::QueryResult>> {
283 Box::pin(async { Err(Error::msg("not a real connection")) })
284 }
285
286 fn simple_query<'a>(
287 &'a mut self,
288 _sql: &'a str,
289 ) -> BoxFuture<'a, Result<crate::driver::QueryResult>> {
290 Box::pin(async { Err(Error::msg("not a real connection")) })
291 }
292
293 fn is_broken(&self) -> bool {
294 false
295 }
296
297 fn in_transaction(&self) -> bool {
298 false
299 }
300
301 fn close(self: Box<Self>) -> BoxFuture<'static, ()> {
302 self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
303 Box::pin(async {})
304 }
305 }
306
307 impl Driver for Counting {
308 fn dialect(&self) -> Arc<dyn Dialect> {
309 Arc::new(Postgres)
310 }
311
312 fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
313 self.opened.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
314 let closed = Arc::clone(&self.closed);
315 Box::pin(async move { Ok(Box::new(Nothing(closed)) as Box<dyn DriverConnection>) })
316 }
317
318 fn describe(&self) -> String {
319 "test://counting".into()
320 }
321
322 fn generation(&self) -> u64 {
323 self.generation.load(std::sync::atomic::Ordering::Acquire)
324 }
325 }
326
327 struct Capped {
330 opened: Arc<std::sync::atomic::AtomicUsize>,
331 closed: Arc<std::sync::atomic::AtomicUsize>,
332 max: usize,
333 }
334
335 impl Driver for Capped {
336 fn dialect(&self) -> Arc<dyn Dialect> {
337 Arc::new(Postgres)
338 }
339
340 fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
341 self.opened.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
342 let closed = Arc::clone(&self.closed);
343 Box::pin(async move { Ok(Box::new(Nothing(closed)) as Box<dyn DriverConnection>) })
344 }
345
346 fn describe(&self) -> String {
347 "test://capped".into()
348 }
349
350 fn max_connections(&self) -> usize {
351 self.max
352 }
353 }
354
355 #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
374 async fn a_pool_never_opens_more_connections_than_it_is_allowed() {
375 const CEILING: usize = 2;
376 let opened = Arc::new(std::sync::atomic::AtomicUsize::new(0));
377 let closed = Arc::new(std::sync::atomic::AtomicUsize::new(0));
378 let pool = Pool::new(Arc::new(Capped {
379 opened: Arc::clone(&opened),
380 closed: Arc::clone(&closed),
381 max: CEILING,
382 }));
383
384 let mut workers = Vec::new();
385 for _ in 0..16 {
386 let pool = pool.clone();
387 workers.push(tokio::spawn(async move {
388 for _ in 0..200 {
389 let connection = pool.acquire().await.expect("a connection");
390 tokio::task::yield_now().await;
393 drop(connection);
394 }
395 }));
396 }
397 for worker in workers {
398 worker.await.unwrap();
399 }
400
401 let opened = opened.load(std::sync::atomic::Ordering::SeqCst);
402 let closed = closed.load(std::sync::atomic::Ordering::SeqCst);
403 assert!(
404 opened <= CEILING,
405 "a pool allowed {CEILING} connections opened {opened} ({closed} closed) \
406 across 3,200 borrows"
407 );
408 assert!(
409 pool.open_count().await <= CEILING,
410 "{} connections are open in a pool allowed {CEILING}",
411 pool.open_count().await
412 );
413 }
414
415 fn counting() -> (Pool, Arc<std::sync::atomic::AtomicUsize>, Arc<std::sync::atomic::AtomicUsize>, Arc<std::sync::atomic::AtomicU64>) {
416 let opened = Arc::new(std::sync::atomic::AtomicUsize::new(0));
417 let closed = Arc::new(std::sync::atomic::AtomicUsize::new(0));
418 let generation = Arc::new(std::sync::atomic::AtomicU64::new(1));
419 let driver = Counting {
420 opened: Arc::clone(&opened),
421 closed: Arc::clone(&closed),
422 generation: Arc::clone(&generation),
423 };
424 (Pool::new(Arc::new(driver)), opened, closed, generation)
425 }
426
427 async fn settle() {
430 for _ in 0..8 {
431 tokio::task::yield_now().await;
432 }
433 }
434
435 #[tokio::test]
436 async fn a_connection_comes_back_and_is_reused() {
437 let (pool, opened, _, _) = counting();
438
439 drop(pool.acquire().await.unwrap());
440 settle().await;
441 drop(pool.acquire().await.unwrap());
442 settle().await;
443
444 assert_eq!(opened.load(std::sync::atomic::Ordering::SeqCst), 1, "the second borrow reused it");
445 }
446
447 #[tokio::test]
448 async fn an_idle_connection_from_a_rotated_credential_is_never_handed_out() {
449 let (pool, opened, closed, generation) = counting();
453
454 drop(pool.acquire().await.unwrap());
455 settle().await;
456 assert_eq!(pool.idle_count().await, 1);
457
458 generation.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
459
460 drop(pool.acquire().await.unwrap());
461 settle().await;
462
463 assert_eq!(opened.load(std::sync::atomic::Ordering::SeqCst), 2, "a fresh connection");
464 assert_eq!(closed.load(std::sync::atomic::Ordering::SeqCst), 1, "the stale one was closed");
465 }
466
467 #[tokio::test]
468 async fn a_borrowed_connection_is_retired_when_it_comes_back() {
469 let (pool, _, closed, generation) = counting();
473
474 let borrowed = pool.acquire().await.unwrap();
475 generation.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
476 drop(borrowed);
477 settle().await;
478
479 assert_eq!(pool.idle_count().await, 0);
480 assert_eq!(closed.load(std::sync::atomic::Ordering::SeqCst), 1);
481 }
482
483 #[tokio::test]
484 async fn retiring_early_closes_the_stale_and_keeps_the_current() {
485 let (pool, _, closed, generation) = counting();
486
487 let first = pool.acquire().await.unwrap();
489 let second = pool.acquire().await.unwrap();
490 drop(first);
491 drop(second);
492 settle().await;
493 assert_eq!(pool.idle_count().await, 2);
494
495 generation.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
496
497 assert_eq!(pool.retire_superseded().await, 2);
498 assert_eq!(pool.idle_count().await, 0);
499 assert_eq!(closed.load(std::sync::atomic::Ordering::SeqCst), 2);
500
501 drop(pool.acquire().await.unwrap());
503 settle().await;
504 assert_eq!(pool.retire_superseded().await, 0);
505 assert_eq!(pool.idle_count().await, 1);
506 }
507
508 #[tokio::test]
509 async fn a_pool_with_static_credentials_never_retires_anything() {
510 let (pool, opened, closed, _) = counting();
513
514 for _ in 0..5 {
515 drop(pool.acquire().await.unwrap());
516 settle().await;
517 }
518
519 assert_eq!(opened.load(std::sync::atomic::Ordering::SeqCst), 1);
520 assert_eq!(closed.load(std::sync::atomic::Ordering::SeqCst), 0);
521 }
522
523 struct Unreachable;
526
527 impl Driver for Unreachable {
528 fn dialect(&self) -> Arc<dyn Dialect> {
529 Arc::new(Postgres)
530 }
531
532 fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
533 Box::pin(async { Err(Error::msg("nothing is listening")) })
534 }
535
536 fn describe(&self) -> String {
537 "test://unreachable".into()
538 }
539
540 fn max_connections(&self) -> usize {
541 3
542 }
543 }
544
545 #[tokio::test]
546 async fn a_pool_opens_nothing_until_it_is_used() {
547 let pool = Pool::new(Arc::new(Unreachable));
548 assert_eq!(pool.idle_count().await, 0);
549 }
550
551 #[tokio::test]
552 async fn acquiring_reports_the_drivers_failure() {
553 let pool = Pool::new(Arc::new(Unreachable));
554
555 let error = match pool.acquire().await {
556 Err(error) => error.to_string(),
557 Ok(_) => panic!("this driver cannot connect"),
558 };
559 assert!(error.contains("nothing is listening"), "{error}");
560 }
561
562 #[tokio::test]
563 async fn the_pool_carries_its_drivers_dialect() {
564 let pool = Pool::new(Arc::new(Unreachable));
565 assert_eq!(pool.dialect().name(), "postgres");
566 }
567}