Skip to content

Commit 4fdf7ea

Browse files
authored
open up ttl backing for nested backings (#13)
1 parent dd09af1 commit 4fdf7ea

File tree

3 files changed

+107
-55
lines changed

3 files changed

+107
-55
lines changed

cache-loader-async-macros/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub fn test_with_features(item: proc_macro::TokenStream) -> proc_macro::TokenStr
101101
#[cfg(feature = "ttl-cache")]
102102
#[tokio::test]
103103
async fn #fn_ident_ttl() {
104-
let #ident: LoadingCache<#key_type, #value_type, #error_type, TtlCacheBacking<_, _>> = LoadingCache::with_backing(TtlCacheBacking::new(Duration::from_secs(3)), move |key: #key_type| {
104+
let #ident: LoadingCache<#key_type, #value_type, #error_type, TtlCacheBacking<_, _, _>> = LoadingCache::with_backing(TtlCacheBacking::new(Duration::from_secs(3)), move |key: #key_type| {
105105
async move #loader
106106
});
107107

src/backing.rs

Lines changed: 81 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use lru::LruCache;
55
#[cfg(feature = "ttl-cache")]
66
use std::collections::VecDeque;
77
use std::fmt::Debug;
8+
use std::marker::PhantomData;
89
use thiserror::Error;
910
#[cfg(feature = "ttl-cache")]
1011
use std::ops::Add;
@@ -20,8 +21,8 @@ pub trait CacheBacking<K, V>
2021
fn get(&mut self, key: &K) -> Result<Option<&V>, BackingError>;
2122
fn set(&mut self, key: K, value: V, meta: Option<Self::Meta>) -> Result<Option<V>, BackingError>;
2223
fn remove(&mut self, key: &K) -> Result<Option<V>, BackingError>;
23-
fn contains_key(&self, key: &K) -> Result<bool, BackingError>;
24-
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send + 'static>) -> Result<(), BackingError>;
24+
fn contains_key(&mut self, key: &K) -> Result<bool, BackingError>;
25+
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send + Sync + 'static>) -> Result<Vec<(K, V)>, BackingError>;
2526
fn clear(&mut self) -> Result<(), BackingError>;
2627
}
2728

@@ -62,11 +63,12 @@ impl<
6263
Ok(self.lru.pop(key))
6364
}
6465

65-
fn contains_key(&self, key: &K) -> Result<bool, BackingError> {
66+
fn contains_key(&mut self, key: &K) -> Result<bool, BackingError> {
6667
Ok(self.lru.contains(&key.clone()))
6768
}
6869

69-
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) -> Result<(), BackingError> {
70+
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send + Sync>) -> Result<Vec<(K, V)>, BackingError> {
71+
let mut removed = Vec::new();
7072
let keys = self.lru.iter()
7173
.filter_map(|(key, value)| {
7274
if predicate((key, value)) {
@@ -78,9 +80,10 @@ impl<
7880
.cloned()
7981
.collect::<Vec<K>>();
8082
for key in keys.into_iter() {
81-
self.lru.pop(&key);
83+
let val = self.lru.pop(&key);
84+
removed.push((key, val.expect("LRU value is empty")))
8285
}
83-
Ok(())
86+
Ok(removed)
8487
}
8588

8689
fn clear(&mut self) -> Result<(), BackingError> {
@@ -108,10 +111,15 @@ impl<
108111
}
109112

110113
#[cfg(feature = "ttl-cache")]
111-
pub struct TtlCacheBacking<K, V> {
114+
pub struct TtlCacheBacking<
115+
K: Clone + Eq + Hash + Send,
116+
V: Clone + Sized + Send,
117+
B: CacheBacking<K, (V, Instant)>
118+
> {
119+
phantom: PhantomData<V>,
112120
ttl: Duration,
113121
expiry_queue: VecDeque<TTlEntry<K>>,
114-
map: HashMap<K, (V, Instant)>,
122+
map: B,
115123
}
116124

117125
#[cfg(feature = "ttl-cache")]
@@ -154,25 +162,26 @@ impl From<Duration> for TtlMeta {
154162

155163
#[cfg(feature = "ttl-cache")]
156164
impl<
157-
K: Eq + Hash + Sized + Clone + Send,
158-
V: Sized + Clone + Send
159-
> CacheBacking<K, V> for TtlCacheBacking<K, V> {
165+
K: Clone + Eq + Hash + Send + 'static,
166+
V: Clone + Sized + Send + 'static,
167+
B: CacheBacking<K, (V, Instant)>
168+
> CacheBacking<K, V> for TtlCacheBacking<K, V, B> {
160169
type Meta = TtlMeta;
161170

162171
fn get_mut(&mut self, key: &K) -> Result<Option<&mut V>, BackingError> {
163-
self.remove_old();
164-
Ok(self.map.get_mut(key)
172+
self.remove_old()?;
173+
Ok(self.map.get_mut(key)?
165174
.map(|(value, _)| value))
166175
}
167176

168177
fn get(&mut self, key: &K) -> Result<Option<&V>, BackingError> {
169-
self.remove_old();
170-
Ok(self.map.get(key)
178+
self.remove_old()?;
179+
Ok(self.map.get(key)?
171180
.map(|(value, _)| value))
172181
}
173182

174183
fn set(&mut self, key: K, value: V, meta: Option<Self::Meta>) -> Result<Option<V>, BackingError> {
175-
self.remove_old();
184+
self.remove_old()?;
176185
let ttl = if let Some(meta) = meta {
177186
meta.ttl
178187
} else {
@@ -184,66 +193,77 @@ impl<
184193
}
185194

186195
fn remove(&mut self, key: &K) -> Result<Option<V>, BackingError> {
187-
self.remove_old();
196+
self.remove_old()?;
188197
Ok(self.remove_key(key)?)
189198
}
190199

191-
fn contains_key(&self, key: &K) -> Result<bool, BackingError> {
192-
// we cant clean old keys on this, since the self ref is not mutable :(
193-
Ok(self.map.get(key)
194-
.filter(|(_, expiry)| Instant::now().lt(expiry))
195-
.is_some())
200+
fn contains_key(&mut self, key: &K) -> Result<bool, BackingError> {
201+
self.remove_old()?;
202+
Ok(self.map.get(key)?.is_some())
196203
}
197204

198-
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) -> Result<(), BackingError> {
199-
let keys = self.map.iter()
200-
.filter_map(|(key, (value, _))| {
201-
if predicate((key, value)) {
202-
Some(key)
203-
} else {
204-
None
205-
}
206-
})
207-
.cloned()
208-
.collect::<Vec<K>>();
209-
for key in keys.into_iter() {
210-
self.map.remove(&key);
205+
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send + Sync>) -> Result<Vec<(K, V)>, BackingError> {
206+
let values = self.map.remove_if(Box::new(move |(key, (value, _))| predicate((key, value))))?;
207+
let mut mapped = Vec::with_capacity(values.len());
208+
for (key, (value, _)) in values {
211209
// optimize looping through expiry_queue multiple times?
212-
self.expiry_queue.retain(|entry| entry.key.ne(&key))
210+
self.expiry_queue.retain(|entry| entry.key.ne(&key));
211+
mapped.push((key, value));
213212
}
214-
Ok(())
213+
Ok(mapped)
215214
}
216215

217216
fn clear(&mut self) -> Result<(), BackingError> {
218217
self.expiry_queue.clear();
219-
self.map.clear();
218+
self.map.clear()?;
220219
Ok(())
221220
}
222221
}
223222

224223
#[cfg(feature = "ttl-cache")]
225-
impl<K: Eq + Hash + Sized + Clone + Send, V: Sized + Clone + Send> TtlCacheBacking<K, V> {
226-
pub fn new(ttl: Duration) -> TtlCacheBacking<K, V> {
224+
impl<
225+
K: Eq + Hash + Sized + Clone + Send,
226+
V: Sized + Clone + Send,
227+
> TtlCacheBacking<K, V, HashMapBacking<K, (V, Instant)>> {
228+
pub fn new(ttl: Duration) -> TtlCacheBacking<K, V, HashMapBacking<K, (V, Instant)>> {
229+
TtlCacheBacking {
230+
phantom: Default::default(),
231+
ttl,
232+
map: HashMapBacking::new(),
233+
expiry_queue: VecDeque::new(),
234+
}
235+
}
236+
}
237+
238+
#[cfg(feature = "ttl-cache")]
239+
impl<
240+
K: Eq + Hash + Sized + Clone + Send,
241+
V: Sized + Clone + Send,
242+
B: CacheBacking<K, (V, Instant)>
243+
> TtlCacheBacking<K, V, B> {
244+
pub fn with_backing(ttl: Duration, backing: B) -> TtlCacheBacking<K, V, B> {
227245
TtlCacheBacking {
246+
phantom: Default::default(),
228247
ttl,
229-
map: HashMap::new(),
248+
map: backing,
230249
expiry_queue: VecDeque::new(),
231250
}
232251
}
233252

234-
fn remove_old(&mut self) {
253+
fn remove_old(&mut self) -> Result<(), BackingError> {
235254
let now = Instant::now();
236255
while let Some(entry) = self.expiry_queue.pop_front() {
237256
if now.lt(&entry.expiry) {
238257
self.expiry_queue.push_front(entry);
239258
break;
240259
}
241-
self.map.remove(&entry.key);
260+
self.map.remove(&entry.key)?;
242261
}
262+
Ok(())
243263
}
244264

245-
fn replace(&mut self, key: K, value: V, expiry: Instant) -> Result<Option<V>, TtlError> {
246-
let entry = self.map.insert(key.clone(), (value, expiry));
265+
fn replace(&mut self, key: K, value: V, expiry: Instant) -> Result<Option<V>, BackingError> {
266+
let entry = self.map.set(key.clone(), (value, expiry), None)?;
247267
let res = self.cleanup_expiry(entry, &key);
248268
match self.expiry_queue.binary_search_by_key(&expiry, |entry| entry.expiry) {
249269
Ok(found) => {
@@ -256,24 +276,24 @@ impl<K: Eq + Hash + Sized + Clone + Send, V: Sized + Clone + Send> TtlCacheBacki
256276
res
257277
}
258278

259-
fn remove_key(&mut self, key: &K) -> Result<Option<V>, TtlError> {
260-
let entry = self.map.remove(key);
279+
fn remove_key(&mut self, key: &K) -> Result<Option<V>, BackingError> {
280+
let entry = self.map.remove(key)?;
261281
self.cleanup_expiry(entry, key)
262282
}
263283

264-
fn cleanup_expiry(&mut self, entry: Option<(V, Instant)>, key: &K) -> Result<Option<V>, TtlError> {
284+
fn cleanup_expiry(&mut self, entry: Option<(V, Instant)>, key: &K) -> Result<Option<V>, BackingError> {
265285
if let Some((value, old_expiry)) = entry {
266286
match self.expiry_queue.binary_search_by_key(&old_expiry, |entry| entry.expiry) {
267287
Ok(found) => {
268288
let index = self.expiry_index_on_key_eq(found, &old_expiry, key);
269289
if let Some(index) = index {
270290
self.expiry_queue.remove(index);
271291
} else {
272-
return Err(TtlError::ExpiryKeyNotFound);
292+
return Err(TtlError::ExpiryKeyNotFound.into());
273293
}
274294
}
275295
Err(_) => {
276-
return Err(TtlError::ExpiryNotFound);
296+
return Err(TtlError::ExpiryNotFound.into());
277297
}
278298
}
279299
Ok(Some(value))
@@ -340,13 +360,20 @@ impl<
340360
Ok(self.map.remove(key))
341361
}
342362

343-
fn contains_key(&self, key: &K) -> Result<bool, BackingError> {
363+
fn contains_key(&mut self, key: &K) -> Result<bool, BackingError> {
344364
Ok(self.map.contains_key(key))
345365
}
346366

347-
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) -> Result<(), BackingError> {
348-
self.map.retain(|k, v| !predicate((k, v)));
349-
Ok(())
367+
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send + Sync>) -> Result<Vec<(K, V)>, BackingError> {
368+
let removed = self.map.iter()
369+
.filter(|(k, v)| predicate((k, v)))
370+
.map(|(k, v)| (k.clone(), v.clone()))
371+
.collect::<Vec<(K, V)>>();
372+
373+
for (k, _) in removed.iter() {
374+
self.map.remove(k);
375+
}
376+
Ok(removed)
350377
}
351378

352379
fn clear(&mut self) -> Result<(), BackingError> {

src/test.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,4 +396,29 @@ async fn test_ttl_backing() {
396396
tokio::time::sleep(Duration::from_secs(2)).await;
397397

398398
assert_eq!(cache.exists("key1".to_owned()).await.unwrap(), false);
399+
}
400+
401+
#[cfg(all(feature = "ttl-cache", feature = "lru-cache"))]
402+
#[tokio::test]
403+
async fn test_ttl_lru_backing() {
404+
let cache: LoadingCache<String, _, u8, _> = LoadingCache::with_meta_loader(TtlCacheBacking::with_backing(Duration::from_secs(1), LruCacheBacking::new(2)), move |key: String| {
405+
async move {
406+
if key.len() < 3 {
407+
Ok(key.to_lowercase())
408+
.with_meta(Some(TtlMeta::from(Duration::from_secs(15))))
409+
} else {
410+
Ok(key.to_lowercase())
411+
.with_meta(None)
412+
}
413+
}
414+
});
415+
assert_eq!(cache.get("a".to_owned()).await.unwrap(), "a");
416+
assert_eq!(cache.get("bbbbb".to_owned()).await.unwrap(), "bbbbb");
417+
tokio::time::sleep(Duration::from_secs(2)).await;
418+
assert!(cache.exists("a".to_owned()).await.unwrap());
419+
assert!(!cache.exists("bbbbb".to_owned()).await.unwrap());
420+
assert_eq!(cache.get("bbbbb".to_owned()).await.unwrap(), "bbbbb");
421+
assert_eq!(cache.get("ccccc".to_owned()).await.unwrap(), "ccccc");
422+
assert_eq!(cache.get("ddddd".to_owned()).await.unwrap(), "ddddd");
423+
assert!(!cache.exists("a".to_owned()).await.unwrap());
399424
}

0 commit comments

Comments
 (0)