1use super::{Cached, SizedCache, Status};
2use crate::CloneCached;
3use std::hash::Hash;
4
5pub trait CanExpire {
8 fn is_expired(&self) -> bool;
10}
11
12#[derive(Clone, Debug)]
20pub struct ExpiringValueCache<K: Hash + Eq, V: CanExpire> {
21 pub(super) store: SizedCache<K, V>,
22 pub(super) hits: u64,
23 pub(super) misses: u64,
24}
25
26impl<K: Clone + Hash + Eq, V: CanExpire> ExpiringValueCache<K, V> {
27 #[must_use]
30 pub fn with_size(size: usize) -> ExpiringValueCache<K, V> {
31 ExpiringValueCache {
32 store: SizedCache::with_size(size),
33 hits: 0,
34 misses: 0,
35 }
36 }
37
38 fn status<Q>(&mut self, k: &Q) -> Status
39 where
40 K: std::borrow::Borrow<Q>,
41 Q: std::hash::Hash + Eq + ?Sized,
42 {
43 let v = self.store.cache_get(k);
44 match v {
45 Some(v) => {
46 if v.is_expired() {
47 Status::Expired
48 } else {
49 Status::Found
50 }
51 }
52 None => Status::NotFound,
53 }
54 }
55
56 pub fn flush(&mut self) {
58 self.store.retain(|_, v| !v.is_expired());
59 }
60}
61
62impl<K: Hash + Eq + Clone, V: CanExpire> Cached<K, V> for ExpiringValueCache<K, V> {
64 fn cache_get<Q>(&mut self, k: &Q) -> Option<&V>
65 where
66 K: std::borrow::Borrow<Q>,
67 Q: std::hash::Hash + Eq + ?Sized,
68 {
69 match self.status(k) {
70 Status::NotFound => {
71 self.misses += 1;
72 None
73 }
74 Status::Found => {
75 self.hits += 1;
76 self.store.cache_get(k)
77 }
78 Status::Expired => {
79 self.misses += 1;
80 self.store.cache_remove(k);
81 None
82 }
83 }
84 }
85
86 fn cache_get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
87 where
88 K: std::borrow::Borrow<Q>,
89 Q: std::hash::Hash + Eq + ?Sized,
90 {
91 match self.status(k) {
92 Status::NotFound => {
93 self.misses += 1;
94 None
95 }
96 Status::Found => {
97 self.hits += 1;
98 self.store.cache_get_mut(k)
99 }
100 Status::Expired => {
101 self.misses += 1;
102 self.store.cache_remove(k);
103 None
104 }
105 }
106 }
107
108 fn cache_get_or_set_with<F: FnOnce() -> V>(&mut self, k: K, f: F) -> &mut V {
109 let (was_present, was_valid, v) = self.store.get_or_set_with_if(k, f, |v| !v.is_expired());
112 if was_present && was_valid {
113 self.hits += 1;
114 } else {
115 self.misses += 1;
116 }
117 v
118 }
119 fn cache_try_get_or_set_with<F: FnOnce() -> Result<V, E>, E>(
120 &mut self,
121 k: K,
122 f: F,
123 ) -> Result<&mut V, E> {
124 let (was_present, was_valid, v) = self
125 .store
126 .try_get_or_set_with_if(k, f, |v| !v.is_expired())?;
127 if was_present && was_valid {
128 self.hits += 1;
129 } else {
130 self.misses += 1;
131 }
132 Ok(v)
133 }
134 fn cache_set(&mut self, k: K, v: V) -> Option<V> {
135 self.store.cache_set(k, v)
136 }
137 fn cache_remove<Q>(&mut self, k: &Q) -> Option<V>
138 where
139 K: std::borrow::Borrow<Q>,
140 Q: std::hash::Hash + Eq + ?Sized,
141 {
142 self.store.cache_remove(k)
143 }
144 fn cache_clear(&mut self) {
145 self.store.cache_clear();
146 }
147 fn cache_reset(&mut self) {
148 self.store.cache_reset();
149 }
150 fn cache_size(&self) -> usize {
151 self.store.cache_size()
152 }
153 fn cache_hits(&self) -> Option<u64> {
154 Some(self.hits)
155 }
156 fn cache_misses(&self) -> Option<u64> {
157 Some(self.misses)
158 }
159 fn cache_reset_metrics(&mut self) {
160 self.hits = 0;
161 self.misses = 0;
162 }
163}
164
165impl<K: Hash + Eq + Clone, V: CanExpire + Clone> CloneCached<K, V> for ExpiringValueCache<K, V> {
166 fn cache_get_expired<Q>(&mut self, k: &Q) -> (Option<V>, bool)
167 where
168 K: std::borrow::Borrow<Q>,
169 Q: std::hash::Hash + Eq + ?Sized,
170 {
171 match self.status(k) {
172 Status::NotFound => {
173 self.misses += 1;
174 (None, false)
175 }
176 Status::Found => {
177 self.hits += 1;
178 (self.store.cache_get(k).cloned(), false)
179 }
180 Status::Expired => {
181 self.misses += 1;
182 (self.store.cache_remove(k), true)
183 }
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
191 use super::*;
192
193 type ExpiredU8 = u8;
194
195 impl CanExpire for ExpiredU8 {
196 fn is_expired(&self) -> bool {
197 *self > 10
198 }
199 }
200
201 #[test]
202 fn expiring_value_cache_get_miss() {
203 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
204
205 assert!(c.cache_get(&1).is_none());
207 assert_eq!(c.cache_hits(), Some(0));
208 assert_eq!(c.cache_misses(), Some(1));
209 }
210
211 #[test]
212 fn expiring_value_cache_get_hit() {
213 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
214
215 assert!(c.cache_set(1, 2).is_none());
217 assert_eq!(c.cache_get(&1), Some(&2));
218 assert_eq!(c.cache_hits(), Some(1));
219 assert_eq!(c.cache_misses(), Some(0));
220 }
221
222 #[test]
223 fn expiring_value_cache_get_expired() {
224 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
225
226 assert!(c.cache_set(2, 12).is_none());
227
228 assert!(c.cache_get(&2).is_none());
229 assert_eq!(c.cache_hits(), Some(0));
230 assert_eq!(c.cache_misses(), Some(1));
231 }
232
233 #[test]
234 fn expiring_value_cache_get_mut_miss() {
235 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
236
237 assert!(c.cache_get_mut(&1).is_none());
239 assert_eq!(c.cache_hits(), Some(0));
240 assert_eq!(c.cache_misses(), Some(1));
241 }
242
243 #[test]
244 fn expiring_value_cache_get_mut_hit() {
245 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
246
247 assert!(c.cache_set(1, 2).is_none());
249 assert_eq!(c.cache_get_mut(&1), Some(&mut 2));
250 assert_eq!(c.cache_hits(), Some(1));
251 assert_eq!(c.cache_misses(), Some(0));
252 }
253
254 #[test]
255 fn expiring_value_cache_get_mut_expired() {
256 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
257
258 assert!(c.cache_set(2, 12).is_none());
259
260 assert!(c.cache_get(&2).is_none());
261 assert_eq!(c.cache_hits(), Some(0));
262 assert_eq!(c.cache_misses(), Some(1));
263 }
264
265 #[test]
266 fn expiring_value_cache_get_or_set_with_missing() {
267 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
268
269 assert_eq!(c.cache_get_or_set_with(1, || 1), &1);
270 assert_eq!(c.cache_hits(), Some(0));
271 assert_eq!(c.cache_misses(), Some(1));
272 }
273
274 #[test]
275 fn expiring_value_cache_get_or_set_with_present() {
276 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
277 assert!(c.cache_set(1, 5).is_none());
278
279 assert_eq!(c.cache_get_or_set_with(1, || 1), &5);
281 assert_eq!(c.cache_hits(), Some(1));
282 assert_eq!(c.cache_misses(), Some(0));
283 }
284
285 #[test]
286 fn expiring_value_cache_get_or_set_with_expired() {
287 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
288 assert!(c.cache_set(1, 11).is_none());
289
290 assert_eq!(c.cache_get_or_set_with(1, || 1), &1);
292 assert_eq!(c.cache_hits(), Some(0));
293 assert_eq!(c.cache_misses(), Some(1));
294 }
295
296 #[test]
297 fn expiring_value_cache_try_get_or_set_with_missing() {
298 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
299
300 assert_eq!(
301 c.cache_try_get_or_set_with(1, || Ok::<_, ()>(1)),
302 Ok(&mut 1)
303 );
304 assert_eq!(c.cache_hits(), Some(0));
305 assert_eq!(c.cache_misses(), Some(1));
306
307 assert_eq!(c.cache_try_get_or_set_with(1, || Err(())), Ok(&mut 1));
308 assert_eq!(c.cache_hits(), Some(1));
309 assert_eq!(c.cache_misses(), Some(1));
310
311 assert_eq!(
312 c.cache_try_get_or_set_with(2, || Ok::<_, ()>(2)),
313 Ok(&mut 2)
314 );
315 assert_eq!(c.cache_hits(), Some(1));
316 assert_eq!(c.cache_misses(), Some(2));
317 }
318
319 #[test]
320 fn flush_expired() {
321 let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
322
323 assert_eq!(c.cache_set(1, 100), None);
324 assert_eq!(c.cache_set(1, 200), Some(100));
325 assert_eq!(c.cache_set(2, 1), None);
326 assert_eq!(c.cache_size(), 2);
327
328 assert_eq!(2, c.cache_size());
330 c.flush();
331 assert_eq!(1, c.cache_size());
332 }
333}