1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
3
4use crate::{HostId, HostRecord, PoolKey, TopologySnapshot};
5
6pub struct ConnectionCache<C> {
11 entries: RwLock<BTreeMap<PoolKey, Arc<C>>>,
12}
13
14impl<C> Default for ConnectionCache<C> {
15 fn default() -> Self {
16 Self {
17 entries: RwLock::new(BTreeMap::new()),
18 }
19 }
20}
21
22impl<C> ConnectionCache<C> {
23 #[must_use]
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn insert(&self, host: &HostRecord, connection: C) -> Option<Arc<C>> {
33 write_unpoisoned(&self.entries).insert(host.pool_key(), Arc::new(connection))
34 }
35
36 #[must_use]
38 pub fn get(&self, host: &HostRecord) -> Option<Arc<C>> {
39 read_unpoisoned(&self.entries)
40 .get(&host.pool_key())
41 .map(Arc::clone)
42 }
43
44 pub fn remove(&self, host: &HostRecord) -> Option<Arc<C>> {
46 write_unpoisoned(&self.entries).remove(&host.pool_key())
47 }
48
49 pub fn invalidate_host(&self, host: &HostId) -> Vec<Arc<C>> {
51 let mut entries = write_unpoisoned(&self.entries);
52 let keys = entries
53 .keys()
54 .filter(|key| key.host() == host)
55 .cloned()
56 .collect::<Vec<_>>();
57 keys.into_iter()
58 .filter_map(|key| entries.remove(&key))
59 .collect()
60 }
61
62 pub fn retain_snapshot(&self, snapshot: &TopologySnapshot) -> Vec<Arc<C>> {
67 let current = snapshot
68 .hosts()
69 .map(HostRecord::pool_key)
70 .collect::<BTreeSet<_>>();
71 let mut entries = write_unpoisoned(&self.entries);
72 let stale = entries
73 .keys()
74 .filter(|key| !current.contains(*key))
75 .cloned()
76 .collect::<Vec<_>>();
77 stale
78 .into_iter()
79 .filter_map(|key| entries.remove(&key))
80 .collect()
81 }
82
83 #[must_use]
85 pub fn len(&self) -> usize {
86 read_unpoisoned(&self.entries).len()
87 }
88
89 #[must_use]
91 pub fn is_empty(&self) -> bool {
92 read_unpoisoned(&self.entries).is_empty()
93 }
94
95 #[must_use]
97 pub fn keys(&self) -> Vec<PoolKey> {
98 read_unpoisoned(&self.entries).keys().cloned().collect()
99 }
100}
101
102fn read_unpoisoned<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
103 lock.read()
104 .unwrap_or_else(std::sync::PoisonError::into_inner)
105}
106
107fn write_unpoisoned<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
108 lock.write()
109 .unwrap_or_else(std::sync::PoisonError::into_inner)
110}
111
112#[cfg(test)]
113#[path = "cache_tests.rs"]
114mod tests;