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}