Skip to main content

deadpool_postgres/
statement_cache.rs

1//! Caching of prepared statements.
2
3use std::{
4    borrow::Cow,
5    collections::HashMap,
6    fmt,
7    future::Future,
8    sync::{Arc, Mutex, RwLock, Weak},
9};
10
11use tokio::sync::OnceCell;
12use tokio_postgres::{Client as PgClient, Error, Statement, types::Type};
13
14/// Structure holding a reference to all [`StatementCache`]s and providing
15/// access for clearing all caches and removing single statements from them.
16#[derive(Default, Debug)]
17pub struct StatementCaches {
18    caches: Mutex<Vec<Weak<StatementCache>>>,
19}
20
21impl StatementCaches {
22    pub(crate) fn attach(&self, cache: &Arc<StatementCache>) {
23        let cache = Arc::downgrade(cache);
24        self.caches.lock().unwrap().push(cache);
25    }
26
27    pub(crate) fn detach(&self, cache: &Arc<StatementCache>) {
28        let cache = Arc::downgrade(cache);
29        self.caches.lock().unwrap().retain(|sc| !sc.ptr_eq(&cache));
30    }
31
32    /// Clears [`StatementCache`] of all connections which were handed out by a
33    /// [`Manager`](crate::Manager).
34    pub fn clear(&self) {
35        let caches = self.caches.lock().unwrap();
36        for cache in caches.iter() {
37            if let Some(cache) = cache.upgrade() {
38                cache.clear();
39            }
40        }
41    }
42
43    /// Removes statement from all caches which were handed out by a
44    /// [`Manager`](crate::Manager).
45    pub fn remove(&self, query: &str, types: &[Type]) {
46        let caches = self.caches.lock().unwrap();
47        for cache in caches.iter() {
48            if let Some(cache) = cache.upgrade() {
49                drop(cache.remove(query, types));
50            }
51        }
52    }
53}
54
55/// Key of a [`StatementCacheInner`]: a query plus the types of its parameters.
56///
57/// Storing [`Cow`]s lets the map own its keys while lookups pass a key borrowing
58/// from the caller's `&str` and `&[Type]`, so a cache hit allocates nothing.
59#[derive(Debug, Eq, Hash, PartialEq)]
60struct StatementCacheKey<'a> {
61    query: Cow<'a, str>,
62    types: Cow<'a, [Type]>,
63}
64
65impl<'a> StatementCacheKey<'a> {
66    /// Builds a key borrowing from `query` and `types`, as lookups use.
67    fn borrowed(query: &'a str, types: &'a [Type]) -> Self {
68        Self {
69            query: Cow::Borrowed(query),
70            types: Cow::Borrowed(types),
71        }
72    }
73
74    /// Builds an owned key, as insertion and removal need.
75    fn owned(query: &str, types: &[Type]) -> StatementCacheKey<'static> {
76        StatementCacheKey {
77            query: Cow::Owned(query.to_owned()),
78            types: Cow::Owned(types.to_owned()),
79        }
80    }
81}
82
83/// The inside of a [`StatementCache`]: a keyed map of lazily prepared values.
84///
85/// Every key maps to a [`OnceCell`] that is initialized at most once. Concurrent
86/// callers asking for the same key therefore share one initialization: the first
87/// one runs it while the others wait, and if it returns an error or is
88/// cancelled, one of the waiters takes over.
89///
90/// The cells are held behind an [`Arc`] so that an initialization in flight
91/// keeps working on its own cell even if [`clear()`](Self::clear) or
92/// [`remove()`](Self::remove) evicts it in the meantime. Such a detached cell is
93/// no longer reachable through the map, so an eviction can never be undone by an
94/// initialization that started before it.
95///
96/// This is generic over the value type `V` purely so that it can be tested
97/// without a live PostgreSQL — [`Statement`] has no public constructor, so a
98/// test cannot build one. It is not meant to be reused for anything else;
99/// [`StatementCache`] instantiates it with `V = Statement`.
100struct StatementCacheInner<V> {
101    map: RwLock<HashMap<StatementCacheKey<'static>, Arc<OnceCell<V>>>>,
102}
103
104impl<V: Clone> StatementCacheInner<V> {
105    fn new() -> Self {
106        Self {
107            map: RwLock::new(HashMap::new()),
108        }
109    }
110
111    /// Returns the number of initialized values in the map.
112    ///
113    /// Cells that are still being initialized (or whose initialization failed)
114    /// are uninitialized and never count towards the size.
115    fn size(&self) -> usize {
116        self.map
117            .read()
118            .unwrap()
119            .values()
120            .filter(|cell| cell.initialized())
121            .count()
122    }
123
124    /// Removes all entries.
125    fn clear(&self) {
126        self.map.write().unwrap().clear();
127    }
128
129    /// Removes a value from the map.
130    ///
131    /// Removing a cell that is not initialized yet returns `None`.
132    ///
133    /// Unlike a lookup this has to build an owned key: removal needs a `&mut`
134    /// borrow of the map, and `&mut T` is invariant in `T`, so the `'static`
135    /// keys of the map cannot be viewed as shorter-lived borrowed ones here.
136    /// Eviction is the cold path, so that allocation does not matter.
137    fn remove(&self, query: &str, types: &[Type]) -> Option<V> {
138        let cell = self
139            .map
140            .write()
141            .unwrap()
142            .remove(&StatementCacheKey::owned(query, types))?;
143        cell.get().cloned()
144    }
145
146    /// Returns the cell for `query`/`types`, inserting an empty one if the key
147    /// is not present yet.
148    fn cell(&self, query: &str, types: &[Type]) -> Arc<OnceCell<V>> {
149        // Fast path: the key is already known, so a read lock suffices and no
150        // owned key has to be built.
151        if let Some(cell) = self
152            .map
153            .read()
154            .unwrap()
155            .get(&StatementCacheKey::borrowed(query, types))
156        {
157            return cell.clone();
158        }
159        // Slow path: allocate the owned key and insert a cell, unless another
160        // task beat us to it while we waited for the write lock.
161        self.map
162            .write()
163            .unwrap()
164            .entry(StatementCacheKey::owned(query, types))
165            .or_default()
166            .clone()
167    }
168
169    /// Returns the value for `query`/`types`, initializing it via `init` if it
170    /// is not present yet.
171    ///
172    /// Concurrent callers for the same key are coalesced so that `init` runs at
173    /// most once per key and successful initialization. If `init` returns an
174    /// error the cell is left uninitialized, so a subsequent call retries.
175    async fn get_or_try_init<F, Fut, E>(&self, query: &str, types: &[Type], init: F) -> Result<V, E>
176    where
177        F: FnOnce() -> Fut,
178        Fut: Future<Output = Result<V, E>>,
179    {
180        self.cell(query, types).get_or_try_init(init).await.cloned()
181    }
182}
183
184/// Representation of a cache of [`Statement`]s.
185///
186/// [`StatementCache`] is bound to one [`Client`](crate::Client), and
187/// [`Statement`]s generated by that [`Client`](crate::Client) must not be used
188/// with other [`Client`](crate::Client)s.
189///
190/// Preparing the same statement from several tasks at once is coalesced: one of
191/// them sends a `PREPARE` to the database while the others wait for its result,
192/// so a burst of concurrent requests for an uncached statement costs a single
193/// round trip rather than one per task. If that preparation fails or its task is
194/// cancelled, one of the waiting tasks starts a fresh one instead of failing
195/// along with it, and nothing is cached until a preparation succeeds.
196///
197/// It can be used like that:
198/// ```rust,ignore
199/// let client = pool.get().await?;
200/// let stmt = client
201///     .statement_cache
202///     .prepare(&client, "SELECT 1")
203///     .await;
204/// let rows = client.query(stmt, &[]).await?;
205/// ...
206/// ```
207///
208/// Normally, you probably want to use the
209/// [`ClientWrapper::prepare_cached()`](crate::ClientWrapper::prepare_cached)
210/// and
211/// [`ClientWrapper::prepare_typed_cached()`](crate::ClientWrapper::prepare_typed_cached)
212/// methods instead (or the similar ones on [`Transaction`](crate::Transaction)).
213pub struct StatementCache {
214    inner: StatementCacheInner<Statement>,
215}
216
217impl fmt::Debug for StatementCache {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        f.debug_struct("StatementCache")
220            .field("size", &self.inner.size())
221            .finish()
222    }
223}
224
225impl StatementCache {
226    pub(crate) fn new() -> Self {
227        Self {
228            inner: StatementCacheInner::new(),
229        }
230    }
231
232    /// Returns current size of this [`StatementCache`].
233    pub fn size(&self) -> usize {
234        self.inner.size()
235    }
236
237    /// Clears this [`StatementCache`].
238    ///
239    /// **Important:** This only clears the [`StatementCache`] of one
240    /// [`Client`](crate::Client) instance. If you want to clear the
241    /// [`StatementCache`] of all [`Client`](crate::Client)s
242    /// you should be calling `pool.manager().statement_caches.clear()` instead.
243    pub fn clear(&self) {
244        self.inner.clear();
245    }
246
247    /// Removes a [`Statement`] from this [`StatementCache`].
248    ///
249    /// **Important:** This only removes a [`Statement`] from one
250    /// [`Client`](crate::Client) cache. If you want to remove a [`Statement`]
251    /// from all
252    /// [`StatementCaches`] you should be calling
253    /// `pool.manager().statement_caches.remove()` instead.
254    pub fn remove(&self, query: &str, types: &[Type]) -> Option<Statement> {
255        self.inner.remove(query, types)
256    }
257
258    /// Creates a new prepared [`Statement`] using this [`StatementCache`], if
259    /// possible.
260    ///
261    /// See [`tokio_postgres::Client::prepare()`].
262    pub async fn prepare(&self, client: &PgClient, query: &str) -> Result<Statement, Error> {
263        self.prepare_typed(client, query, &[]).await
264    }
265
266    /// Creates a new prepared [`Statement`] with specifying its [`Type`]s
267    /// explicitly using this [`StatementCache`], if possible.
268    ///
269    /// See [`tokio_postgres::Client::prepare_typed()`].
270    pub async fn prepare_typed(
271        &self,
272        client: &PgClient,
273        query: &str,
274        types: &[Type],
275    ) -> Result<Statement, Error> {
276        self.inner
277            .get_or_try_init(query, types, || client.prepare_typed(query, types))
278            .await
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use std::sync::{
285        Arc,
286        atomic::{AtomicUsize, Ordering},
287    };
288
289    use super::*;
290
291    /// A miss runs `init` once and caches the value; a subsequent call for the
292    /// same key is a hit and does not run `init` again.
293    #[tokio::test]
294    async fn initializes_on_miss_and_caches() {
295        let cache = StatementCacheInner::<u32>::new();
296        let calls = AtomicUsize::new(0);
297
298        let first = cache
299            .get_or_try_init("q", &[], || async {
300                let _ = calls.fetch_add(1, Ordering::Relaxed);
301                Ok::<u32, ()>(42)
302            })
303            .await
304            .unwrap();
305        assert_eq!(first, 42);
306        assert_eq!(cache.size(), 1);
307
308        // The second closure returns a different value; since the key is cached
309        // it must never run, and the original value is returned.
310        let second = cache
311            .get_or_try_init("q", &[], || async {
312                let _ = calls.fetch_add(1, Ordering::Relaxed);
313                Ok::<u32, ()>(99)
314            })
315            .await
316            .unwrap();
317        assert_eq!(second, 42);
318        assert_eq!(calls.load(Ordering::Relaxed), 1);
319        assert_eq!(cache.size(), 1);
320    }
321
322    /// A key borrowing from the caller's `&str`/`&[Type]` must match the owned
323    /// key stored for it, whatever those borrows point at, and the parameter
324    /// types are part of the key rather than just the query. No integration test
325    /// covers a cache *hit* — they all prepare exactly once — so this is the
326    /// only coverage of the borrowed/owned [`Cow`] matching.
327    #[tokio::test]
328    async fn borrowed_key_matches_stored_owned_key() {
329        let cache = StatementCacheInner::<u32>::new();
330
331        // Stored from owned data ...
332        let query = String::from("SELECT $1");
333        let types = vec![Type::INT4];
334        let first = cache
335            .get_or_try_init(&query, &types, || async { Ok::<u32, ()>(1) })
336            .await
337            .unwrap();
338        assert_eq!(first, 1);
339
340        // ... and found again from unrelated borrows with the same contents, so
341        // the second closure never runs.
342        let second = cache
343            .get_or_try_init("SELECT $1", &[Type::INT4], || async { Ok::<u32, ()>(2) })
344            .await
345            .unwrap();
346        assert_eq!(second, 1);
347        assert_eq!(cache.size(), 1);
348
349        // Same query, different parameter types: a distinct key.
350        let other = cache
351            .get_or_try_init("SELECT $1", &[Type::TEXT], || async { Ok::<u32, ()>(3) })
352            .await
353            .unwrap();
354        assert_eq!(other, 3);
355        assert_eq!(cache.size(), 2);
356
357        // `remove` reaches the entry through a borrowed key too.
358        assert_eq!(cache.remove("SELECT $1", &types), Some(1));
359        assert_eq!(cache.size(), 1);
360    }
361
362    /// Many tasks racing on the same key must all end up on the *same* cell, so
363    /// that `init` runs once and everyone observes the same value. The
364    /// once-per-cell part is [`OnceCell`]'s job; what is tested here is the
365    /// read-lock-then-write-lock lookup in [`StatementCacheInner::cell()`],
366    /// which is where a second cell could sneak in.
367    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
368    async fn coalesces_concurrent_initializers() {
369        // Repeat with fresh caches to shake out ordering-dependent races.
370        for _ in 0..20 {
371            let cache = Arc::new(StatementCacheInner::<u32>::new());
372            let calls = Arc::new(AtomicUsize::new(0));
373
374            let handles = (0..128)
375                .map(|_| {
376                    let cache = cache.clone();
377                    let calls = calls.clone();
378                    tokio::spawn(async move {
379                        cache
380                            .get_or_try_init("q", &[], || async {
381                                let _ = calls.fetch_add(1, Ordering::Relaxed);
382                                // Widen the race window so multiple tasks pile
383                                // up on the semaphore.
384                                tokio::task::yield_now().await;
385                                Ok::<u32, ()>(7)
386                            })
387                            .await
388                            .unwrap()
389                    })
390                })
391                .collect::<Vec<_>>();
392
393            for handle in handles {
394                assert_eq!(handle.await.unwrap(), 7);
395            }
396            assert_eq!(calls.load(Ordering::Relaxed), 1);
397            assert_eq!(cache.size(), 1);
398        }
399    }
400
401    /// Distinct keys are initialized independently, once each.
402    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
403    async fn distinct_keys_initialized_independently() {
404        let cache = Arc::new(StatementCacheInner::<u32>::new());
405        let calls = Arc::new(AtomicUsize::new(0));
406
407        let handles = (0..16u32)
408            .map(|i| {
409                let cache = cache.clone();
410                let calls = calls.clone();
411                tokio::spawn(async move {
412                    let query = format!("q{i}");
413                    cache
414                        .get_or_try_init(&query, &[], || async {
415                            let _ = calls.fetch_add(1, Ordering::Relaxed);
416                            Ok::<u32, ()>(i)
417                        })
418                        .await
419                        .unwrap()
420                })
421            })
422            .collect::<Vec<_>>();
423
424        for handle in handles {
425            let _ = handle.await.unwrap();
426        }
427        assert_eq!(calls.load(Ordering::Relaxed), 16);
428        assert_eq!(cache.size(), 16);
429    }
430
431    /// A leftover uninitialized cell never counts towards the size, `remove` of
432    /// such a key returns `None`, and initializing a value into it counts
433    /// exactly once.
434    #[tokio::test]
435    async fn size_accounting_with_uninitialized_cells() {
436        let cache = StatementCacheInner::<u32>::new();
437
438        // A failed initialization leaves an uninitialized cell behind.
439        let _ = cache
440            .get_or_try_init("q", &[], || async { Err::<u32, ()>(()) })
441            .await;
442        assert_eq!(cache.size(), 0);
443
444        // Removing a key whose cell is uninitialized is a no-op for the size.
445        assert_eq!(cache.remove("q", &[]), None);
446        assert_eq!(cache.size(), 0);
447
448        // Initializing a value counts once.
449        let value = cache
450            .get_or_try_init("q", &[], || async { Ok::<u32, ()>(1) })
451            .await
452            .unwrap();
453        assert_eq!(value, 1);
454        assert_eq!(cache.size(), 1);
455
456        // Removing the ready value decrements back to zero.
457        assert_eq!(cache.remove("q", &[]), Some(1));
458        assert_eq!(cache.size(), 0);
459    }
460
461    /// `clear` empties the cache and resets the size; afterwards keys are misses
462    /// again.
463    #[tokio::test]
464    async fn clear_resets() {
465        let cache = StatementCacheInner::<u32>::new();
466
467        for i in 0..5u32 {
468            let query = format!("q{i}");
469            let _ = cache
470                .get_or_try_init(&query, &[], || async { Ok::<u32, ()>(i) })
471                .await
472                .unwrap();
473        }
474        assert_eq!(cache.size(), 5);
475
476        cache.clear();
477        assert_eq!(cache.size(), 0);
478
479        // After clearing, a previously cached key is a miss again.
480        let calls = AtomicUsize::new(0);
481        let _ = cache
482            .get_or_try_init("q0", &[], || async {
483                let _ = calls.fetch_add(1, Ordering::Relaxed);
484                Ok::<u32, ()>(0)
485            })
486            .await
487            .unwrap();
488        assert_eq!(calls.load(Ordering::Relaxed), 1);
489        assert_eq!(cache.size(), 1);
490    }
491
492    /// An initialization that started before a `clear` must not resurrect the
493    /// cleared entry when it finishes afterwards.
494    #[tokio::test(flavor = "current_thread")]
495    async fn clear_during_initialization_does_not_resurrect() {
496        assert_eq!(evict_during_initialization(|cache| cache.clear()).await, 1);
497    }
498
499    /// Same for a targeted `remove`, which is the case that actually matters:
500    /// removing a statement means it went stale, so an initialization still in
501    /// flight must not put it back.
502    #[tokio::test(flavor = "current_thread")]
503    async fn remove_during_initialization_does_not_resurrect() {
504        assert_eq!(
505            evict_during_initialization(|cache| {
506                // The value is not ready yet, so there is nothing to hand back.
507                assert_eq!(cache.remove("q", &[]), None);
508            })
509            .await,
510            1
511        );
512    }
513
514    /// Runs `evict` while an initialization for `"q"` is in flight, then lets
515    /// that initialization finish and returns the value a subsequent lookup
516    /// observes.
517    async fn evict_during_initialization(evict: impl FnOnce(&StatementCacheInner<u32>)) -> u32 {
518        use tokio::sync::oneshot;
519
520        let cache = Arc::new(StatementCacheInner::<u32>::new());
521        let (gate_tx, gate_rx) = oneshot::channel::<()>();
522        let (started_tx, started_rx) = oneshot::channel::<()>();
523
524        // Task A blocks inside `init` until we open the gate.
525        let task_a = {
526            let cache = cache.clone();
527            tokio::spawn(async move {
528                cache
529                    .get_or_try_init("q", &[], || async move {
530                        let _ = started_tx.send(());
531                        let _ = gate_rx.await;
532                        Ok::<u32, ()>(0)
533                    })
534                    .await
535            })
536        };
537
538        // Evict the entry while task A is still initializing it, ...
539        let _ = started_rx.await;
540        assert_eq!(cache.size(), 0);
541        evict(&cache);
542
543        // ... then let task A finish. It initializes the cell it holds, which is
544        // detached from the map by now.
545        let _ = gate_tx.send(());
546        assert_eq!(task_a.await.unwrap(), Ok(0));
547        assert_eq!(cache.size(), 0);
548
549        // The next caller must not see task A's value.
550        cache
551            .get_or_try_init("q", &[], || async { Ok::<u32, ()>(1) })
552            .await
553            .unwrap()
554    }
555}