Skip to main content

postgresql/
pool.rs

1//! A bounded connection pool.
2
3use std::{
4    future::Future,
5    ops::Deref,
6    sync::{
7        Arc, Mutex, Weak,
8        atomic::{AtomicBool, Ordering},
9    },
10    time::Instant,
11};
12
13use tokio::sync::{Notify, OwnedSemaphorePermit, Semaphore};
14
15use crate::{
16    config::{Config, PoolConfig},
17    connection::{Connection, RowStream},
18    encode::ToSql,
19    error::Error,
20    transaction::Transaction,
21};
22
23pub(crate) struct IdleConn {
24    pub(crate) conn: Connection,
25    pub(crate) created: Instant,
26    pub(crate) idle_since: Instant,
27    pub(crate) permit: OwnedSemaphorePermit,
28}
29
30pub(crate) struct PoolInner {
31    config: Config,
32    settings: PoolConfig,
33    idle: Mutex<Vec<IdleConn>>,
34    semaphore: Arc<Semaphore>,
35    closed: AtomicBool,
36    notify: Notify,
37}
38
39impl PoolInner {
40    fn lock_idle(&self) -> std::sync::MutexGuard<'_, Vec<IdleConn>> {
41        self.idle.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
42    }
43
44    pub(crate) fn pop_idle(&self) -> Option<IdleConn> {
45        let mut idle = self.lock_idle();
46        loop {
47            let conn = idle.pop()?;
48            if conn.conn.is_closed() {
49                // Drop the dead connection (releasing its permit) and try the
50                // next one.
51                continue;
52            }
53            return Some(conn);
54        }
55    }
56
57    pub(crate) fn push_idle(&self, conn: Connection, permit: OwnedSemaphorePermit, created: Instant) {
58        {
59            let mut idle = self.lock_idle();
60            if self.closed.load(Ordering::Acquire) {
61                // The pool was closed: drop the connection (and release its
62                // permit) instead of storing it in a pool that will never hand
63                // it out again.
64                return;
65            }
66            idle.push(IdleConn {
67                conn,
68                created,
69                idle_since: Instant::now(),
70                permit,
71            });
72        }
73        self.notify.notify_one();
74    }
75
76    pub(crate) fn is_closed(&self) -> bool {
77        self.closed.load(Ordering::Acquire)
78    }
79}
80
81/// Ownership of one checked-out connection.
82///
83/// The connection is returned to its pool — or discarded when the pool is
84/// closed or the connection is broken — when the **last** clone of the `Arc`
85/// is dropped. A [`PooledConnection`] or [`Transaction`] holds one clone and
86/// every [`RowStream`] taken from it holds another, so a connection can never
87/// be handed to another user while rows are still being streamed from it.
88pub(crate) struct Checkout {
89    conn: Option<Connection>,
90    pool: Option<Arc<PoolInner>>,
91    permit: Option<OwnedSemaphorePermit>,
92    created: Instant,
93}
94
95impl Checkout {
96    /// A checkout backed by a pool.
97    pub(crate) fn pooled(
98        conn: Connection,
99        pool: Arc<PoolInner>,
100        permit: OwnedSemaphorePermit,
101        created: Instant,
102    ) -> Self {
103        Checkout {
104            conn: Some(conn),
105            pool: Some(pool),
106            permit: Some(permit),
107            created,
108        }
109    }
110
111    /// A checkout that is not backed by a pool (a bare `Transaction`).
112    pub(crate) fn detached(conn: Connection) -> Self {
113        Checkout {
114            conn: Some(conn),
115            pool: None,
116            permit: None,
117            created: Instant::now(),
118        }
119    }
120
121    pub(crate) fn connection(&self) -> &Connection {
122        self.conn.as_ref().expect("checkout already returned")
123    }
124}
125
126impl Drop for Checkout {
127    fn drop(&mut self) {
128        let Some(conn) = self.conn.take() else { return };
129        match self.pool.take() {
130            Some(pool) => {
131                let Some(permit) = self.permit.take() else { return };
132                if pool.is_closed() || conn.is_closed() {
133                    // Discard the connection (releasing the permit) rather than
134                    // storing it in a pool that will never reuse it.
135                    drop(permit);
136                    drop(conn);
137                } else {
138                    pool.push_idle(conn, permit, self.created);
139                }
140            }
141            None => drop(conn),
142        }
143    }
144}
145
146/// A pool of reusable connections.
147#[derive(Clone)]
148pub struct Pool {
149    inner: Arc<PoolInner>,
150}
151
152impl Pool {
153    /// Creates a pool from a `postgres://` URL.
154    ///
155    /// Pool settings are read from the URL (`pool_size`, `min_connections`,
156    /// `acquire_timeout`, `idle_timeout`, `max_lifetime`); when `config` is
157    /// `Some`, it overrides them entirely.
158    ///
159    /// # TLS
160    ///
161    /// TLS is always required; plaintext connections are not possible.
162    ///
163    /// | `sslmode` | Chain verified | Hostname verified |
164    /// | --- | --- | --- |
165    /// | `require` (default) | no | no |
166    /// | `verify-ca` | yes | no |
167    /// | `verify-full` | yes | yes |
168    ///
169    /// `disable`, `prefer` and `allow` are rejected. `verify-ca` and
170    /// `verify-full` read trusted roots from `sslrootcert` (a PEM file) or the
171    /// operating system trust store.
172    ///
173    /// ```no_run
174    /// # async fn run() -> Result<(), postgresql::Error> {
175    /// use postgresql::{Pool, PoolConfig};
176    ///
177    /// // Pool of up to 16 connections, TLS required (default).
178    /// let pool = Pool::connect("postgres://user:pass@db.example.com/app?pool_size=16", None).await?;
179    ///
180    /// // Verify the certificate chain and hostname; override the pool size.
181    /// let other = Pool::connect(
182    ///     "postgres://user:pass@db.example.com/app?sslmode=verify-full&sslrootcert=/etc/ssl/db-ca.pem",
183    ///     Some(PoolConfig { pool_size: 32, min_connections: 2, ..PoolConfig::default() }),
184    /// )
185    /// .await?;
186    /// # let _ = (pool, other);
187    /// # Ok(())
188    /// # }
189    /// ```
190    ///
191    /// # Errors
192    ///
193    /// Returns a configuration error for an invalid URL. Opening the
194    /// `min_connections` is **best-effort**: if some cannot be established the
195    /// pool starts with fewer and opens more on demand (the minimum is not
196    /// maintained afterwards).
197    pub async fn connect(url: &str, config: Option<PoolConfig>) -> Result<Self, Error> {
198        let mut parsed = Config::parse(url)?;
199        if let Some(overrides) = config {
200            // The explicit pool config wins over the URL for pool-wide settings.
201            parsed.max_message_len = overrides.max_message_len;
202            parsed.pool = overrides;
203        }
204        Pool::from_config(parsed).await
205    }
206
207    async fn from_config(config: Config) -> Result<Self, Error> {
208        let settings = config.pool.clone();
209        let pool = Pool {
210            inner: Arc::new(PoolInner {
211                config,
212                settings: settings.clone(),
213                idle: Mutex::new(Vec::new()),
214                semaphore: Arc::new(Semaphore::new(settings.pool_size as usize)),
215                closed: AtomicBool::new(false),
216                notify: Notify::new(),
217            }),
218        };
219
220        for _ in 0..settings.min_connections {
221            let permit = match pool.inner.semaphore.clone().acquire_owned().await {
222                Ok(permit) => permit,
223                Err(_) => break,
224            };
225            match Connection::connect_with_config(pool.inner.config.clone()).await {
226                Ok(conn) => pool.inner.push_idle(conn, permit, Instant::now()),
227                Err(_) => break,
228            }
229        }
230
231        // The reaper only holds a `Weak` reference, so dropping the last
232        // `Pool` handle (without `close`) ends the task and the idle
233        // connections with it instead of leaking both forever.
234        tokio::spawn(Self::reap_loop(Arc::downgrade(&pool.inner)));
235
236        Ok(pool)
237    }
238
239    /// Acquires a connection, waiting up to the configured acquire timeout.
240    ///
241    /// # Errors
242    ///
243    /// Returns [`Error::PoolClosed`] if the pool is closed,
244    /// [`Error::PoolTimedOut`] if no connection becomes available in time, or a
245    /// connection error if a new connection cannot be established.
246    pub async fn get(&self) -> Result<PooledConnection, Error> {
247        if self.inner.is_closed() {
248            return Err(Error::PoolClosed);
249        }
250
251        loop {
252            if let Some(idle) = self.inner.pop_idle() {
253                return Ok(self.wrap_idle(idle));
254            }
255
256            let notified = self.inner.notify.notified();
257
258            if self.inner.is_closed() {
259                return Err(Error::PoolClosed);
260            }
261            if let Some(idle) = self.inner.pop_idle() {
262                return Ok(self.wrap_idle(idle));
263            }
264
265            tokio::select! {
266                _ = notified => continue,
267                acquired = tokio::time::timeout(
268                    self.inner.settings.acquire_timeout,
269                    self.inner.semaphore.clone().acquire_owned(),
270                ) => {
271                    let permit = acquired
272                        .map_err(|_| Error::PoolTimedOut)?
273                        .map_err(|_| Error::PoolClosed)?;
274
275                    // A connection may have been returned while we waited.
276                    if let Some(idle) = self.inner.pop_idle() {
277                        drop(permit);
278                        return Ok(self.wrap_idle(idle));
279                    }
280
281                    let conn = Connection::connect_with_config(self.inner.config.clone()).await?;
282                    return Ok(PooledConnection {
283                        checkout: Some(Arc::new(Checkout::pooled(
284                            conn,
285                            self.inner.clone(),
286                            permit,
287                            Instant::now(),
288                        ))),
289                    });
290                }
291            }
292        }
293    }
294
295    fn wrap_idle(&self, idle: IdleConn) -> PooledConnection {
296        PooledConnection {
297            checkout: Some(Arc::new(Checkout::pooled(
298                idle.conn,
299                self.inner.clone(),
300                idle.permit,
301                idle.created,
302            ))),
303        }
304    }
305
306    /// Acquires a connection and starts a transaction on it.
307    pub async fn begin(&self) -> Result<Transaction, Error> {
308        let pooled = self.get().await?;
309        pooled.begin().await
310    }
311
312    /// Returns `true` if the pool has been closed.
313    pub fn is_closed(&self) -> bool {
314        self.inner.is_closed()
315    }
316
317    /// Closes the pool and discards idle connections.
318    ///
319    /// Connections still checked out are closed when they are returned, not
320    /// reused.
321    pub fn close(&self) {
322        self.inner.closed.store(true, Ordering::Release);
323        self.inner.semaphore.close();
324        self.inner.lock_idle().clear();
325        self.inner.notify.notify_waiters();
326    }
327
328    async fn reap_loop(inner: Weak<PoolInner>) {
329        loop {
330            tokio::time::sleep(std::time::Duration::from_secs(30)).await;
331            // The pool is gone: drop the idle connections and stop.
332            let Some(inner) = inner.upgrade() else { return };
333            if inner.is_closed() {
334                return;
335            }
336            let now = Instant::now();
337            let idle_timeout = inner.settings.idle_timeout;
338            let max_lifetime = inner.settings.max_lifetime;
339            let min = inner.settings.min_connections as usize;
340
341            let mut idle = inner.lock_idle();
342            let mut keep: Vec<IdleConn> = Vec::with_capacity(idle.len());
343            let mut expired: Vec<IdleConn> = Vec::new();
344            for conn in idle.drain(..) {
345                let is_expired = now.duration_since(conn.idle_since) > idle_timeout
346                    || now.duration_since(conn.created) > max_lifetime;
347                if is_expired {
348                    expired.push(conn);
349                } else {
350                    keep.push(conn);
351                }
352            }
353            // Keep at least `min` connections even if some have expired.
354            while keep.len() < min {
355                let Some(conn) = expired.pop() else { break };
356                keep.push(conn);
357            }
358            drop(expired);
359            *idle = keep;
360        }
361    }
362}
363
364impl crate::query::sealed::Sealed for Pool {}
365
366impl crate::query::Capable for Pool {
367    fn execute<'a>(
368        &'a self,
369        sql: &'a str,
370        params: &'a [&'a dyn ToSql],
371    ) -> impl Future<Output = Result<u64, Error>> + Send + 'a {
372        async move {
373            let conn = self.get().await?;
374            conn.execute(sql, params).await
375        }
376    }
377
378    fn fetch_rows<'a>(
379        &'a self,
380        sql: &'a str,
381        params: &'a [&'a dyn ToSql],
382        max_rows: i32,
383    ) -> impl Future<Output = Result<RowStream, Error>> + Send + 'a {
384        async move {
385            // The checkout is held until the stream is finished or dropped:
386            // handing it back while rows are still streaming would let another
387            // query block behind, or interleave with, this one.
388            let conn = self.get().await?;
389            conn.fetch_rows(sql, params, max_rows).await
390        }
391    }
392}
393
394impl crate::query::ScriptExecutor for Pool {
395    fn execute_script<'a>(&'a self, sql: &'a str) -> impl Future<Output = Result<u64, Error>> + Send + 'a {
396        async move {
397            let conn = self.get().await?;
398            conn.execute_script(sql).await
399        }
400    }
401}
402
403/// A connection checked out of a [`Pool`].
404///
405/// Dereferences to [`Connection`]. When dropped, the connection is returned to
406/// the pool rather than closed — but only once any [`RowStream`] taken from it
407/// has also been dropped, so the connection is never handed out twice while a
408/// query is still streaming.
409pub struct PooledConnection {
410    checkout: Option<Arc<Checkout>>,
411}
412
413impl PooledConnection {
414    /// Starts a transaction on this connection.
415    pub async fn begin(mut self) -> Result<Transaction, Error> {
416        let checkout = self.checkout.take().expect("connection already taken");
417        Transaction::begin_checkout(checkout).await
418    }
419
420    fn checkout(&self) -> &Checkout {
421        self.checkout.as_deref().expect("connection already taken")
422    }
423}
424
425impl Deref for PooledConnection {
426    type Target = Connection;
427
428    fn deref(&self) -> &Connection {
429        self.checkout().connection()
430    }
431}
432
433impl Drop for PooledConnection {
434    fn drop(&mut self) {
435        // Dropping our clone returns the connection to the pool when no stream
436        // still holds one (see `Checkout::drop`).
437        let _ = self.checkout.take();
438    }
439}
440
441impl crate::query::sealed::Sealed for PooledConnection {}
442
443impl crate::query::Capable for PooledConnection {
444    fn execute<'a>(
445        &'a self,
446        sql: &'a str,
447        params: &'a [&'a dyn ToSql],
448    ) -> impl Future<Output = Result<u64, Error>> + Send + 'a {
449        async move { self.checkout().connection().execute(sql, params).await }
450    }
451
452    fn fetch_rows<'a>(
453        &'a self,
454        sql: &'a str,
455        params: &'a [&'a dyn ToSql],
456        max_rows: i32,
457    ) -> impl Future<Output = Result<RowStream, Error>> + Send + 'a {
458        async move {
459            // Tie a clone of the checkout to the stream: the connection is only
460            // returned once both this handle and the stream are gone.
461            let checkout = self.checkout.as_ref().expect("connection already taken").clone();
462            let stream = checkout.connection().fetch_rows(sql, params, max_rows).await?;
463            Ok(stream.with_keepalive(checkout))
464        }
465    }
466}
467
468impl crate::query::ScriptExecutor for PooledConnection {
469    fn execute_script<'a>(&'a self, sql: &'a str) -> impl Future<Output = Result<u64, Error>> + Send + 'a {
470        async move { self.checkout().connection().execute_script(sql).await }
471    }
472}