Skip to content

Backing improvements #12

New issue

Have a question about this project? No Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “No Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? No Sign in to your account

Merged
merged 3 commits into from
Jan 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cache-loader-async-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub fn test_with_features(item: proc_macro::TokenStream) -> proc_macro::TokenStr
let result = quote! {
#[tokio::test]
async fn #fn_ident_default() {
let #ident: LoadingCache<#key_type, #value_type, #error_type> = LoadingCache::new(move |key: #key_type| {
let #ident: LoadingCache<#key_type, #value_type, #error_type, HashMapBacking<_, _>> = LoadingCache::new(move |key: #key_type| {
async move #loader
});

Expand All @@ -91,7 +91,7 @@ pub fn test_with_features(item: proc_macro::TokenStream) -> proc_macro::TokenStr
#[cfg(feature = "lru-cache")]
#[tokio::test]
async fn #fn_ident_lru() {
let #ident: LoadingCache<#key_type, #value_type, #error_type> = LoadingCache::with_backing(LruCacheBacking::new(100), move |key: #key_type| {
let #ident: LoadingCache<#key_type, #value_type, #error_type, LruCacheBacking<_, _>> = LoadingCache::with_backing(LruCacheBacking::new(100), move |key: #key_type| {
async move #loader
});

Expand All @@ -101,7 +101,7 @@ pub fn test_with_features(item: proc_macro::TokenStream) -> proc_macro::TokenStr
#[cfg(feature = "ttl-cache")]
#[tokio::test]
async fn #fn_ident_ttl() {
let #ident: LoadingCache<#key_type, #value_type, #error_type> = LoadingCache::with_backing(TtlCacheBacking::new(Duration::from_secs(3)), move |key: #key_type| {
let #ident: LoadingCache<#key_type, #value_type, #error_type, TtlCacheBacking<_, _>> = LoadingCache::with_backing(TtlCacheBacking::new(Duration::from_secs(3)), move |key: #key_type| {
async move #loader
});

Expand Down
180 changes: 116 additions & 64 deletions src/backing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::hash::Hash;
use lru::LruCache;
#[cfg(feature = "ttl-cache")]
use std::collections::VecDeque;
use std::fmt::Debug;
use thiserror::Error;
#[cfg(feature = "ttl-cache")]
use std::ops::Add;
#[cfg(feature = "ttl-cache")]
Expand All @@ -12,15 +14,26 @@ use tokio::time::{Instant, Duration};
pub trait CacheBacking<K, V>
where K: Eq + Hash + Sized + Clone + Send,
V: Sized + Clone + Send {
fn get_mut(&mut self, key: &K) -> Option<&mut V>;
fn get(&mut self, key: &K) -> Option<&V>;
fn set(&mut self, key: K, value: V) -> Option<V>;
fn remove(&mut self, key: &K) -> Option<V>;
fn contains_key(&self, key: &K) -> bool;
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send + 'static>);
fn clear(&mut self);
type Meta: Clone + Send;

fn get_mut(&mut self, key: &K) -> Result<Option<&mut V>, BackingError>;
fn get(&mut self, key: &K) -> Result<Option<&V>, BackingError>;
fn set(&mut self, key: K, value: V, meta: Option<Self::Meta>) -> Result<Option<V>, BackingError>;
fn remove(&mut self, key: &K) -> Result<Option<V>, BackingError>;
fn contains_key(&self, key: &K) -> Result<bool, BackingError>;
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send + 'static>) -> Result<(), BackingError>;
fn clear(&mut self) -> Result<(), BackingError>;
}

#[derive(Debug, Clone, Error)]
pub enum BackingError {
#[error(transparent)]
TtlError(#[from] TtlError),
}

#[derive(Copy, Clone, Debug, Default)]
pub struct NoMeta {}

#[cfg(feature = "lru-cache")]
pub struct LruCacheBacking<K, V> {
lru: LruCache<K, V>,
Expand All @@ -31,27 +44,29 @@ impl<
K: Eq + Hash + Sized + Clone + Send,
V: Sized + Clone + Send
> CacheBacking<K, V> for LruCacheBacking<K, V> {
fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.lru.get_mut(key)
type Meta = NoMeta;

fn get_mut(&mut self, key: &K) -> Result<Option<&mut V>, BackingError> {
Ok(self.lru.get_mut(key))
}

fn get(&mut self, key: &K) -> Option<&V> {
self.lru.get(key)
fn get(&mut self, key: &K) -> Result<Option<&V>, BackingError> {
Ok(self.lru.get(key))
}

fn set(&mut self, key: K, value: V) -> Option<V> {
self.lru.put(key, value)
fn set(&mut self, key: K, value: V, _meta: Option<Self::Meta>) -> Result<Option<V>, BackingError> {
Ok(self.lru.put(key, value))
}

fn remove(&mut self, key: &K) -> Option<V> {
self.lru.pop(key)
fn remove(&mut self, key: &K) -> Result<Option<V>, BackingError> {
Ok(self.lru.pop(key))
}

fn contains_key(&self, key: &K) -> bool {
self.lru.contains(&key.clone())
fn contains_key(&self, key: &K) -> Result<bool, BackingError> {
Ok(self.lru.contains(&key.clone()))
}

fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) {
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) -> Result<(), BackingError> {
let keys = self.lru.iter()
.filter_map(|(key, value)| {
if predicate((key, value)) {
Expand All @@ -65,10 +80,12 @@ impl<
for key in keys.into_iter() {
self.lru.pop(&key);
}
Ok(())
}

fn clear(&mut self) {
fn clear(&mut self) -> Result<(), BackingError> {
self.lru.clear();
Ok(())
}
}

Expand Down Expand Up @@ -114,51 +131,71 @@ impl<K> From<(K, Instant)> for TTlEntry<K> {
}
}

#[derive(Debug, Clone, Error)]
pub enum TtlError {
#[error("The expiry for key not found")]
ExpiryNotFound,
#[error("No key for expiry matched key")]
ExpiryKeyNotFound,
}

#[cfg(feature = "ttl-cache")]
#[derive(Debug, Copy, Clone)]
pub struct TtlMeta {
pub ttl: Duration,
}

#[cfg(feature = "ttl-cache")]
impl From<Duration> for TtlMeta {
fn from(ttl: Duration) -> Self {
Self { ttl }
}
}

#[cfg(feature = "ttl-cache")]
impl<
K: Eq + Hash + Sized + Clone + Send,
V: Sized + Clone + Send
> CacheBacking<K, V> for TtlCacheBacking<K, V> {
fn get_mut(&mut self, key: &K) -> Option<&mut V> {
type Meta = TtlMeta;

fn get_mut(&mut self, key: &K) -> Result<Option<&mut V>, BackingError> {
self.remove_old();
self.map.get_mut(key)
.map(|(value, _)| value)
Ok(self.map.get_mut(key)
.map(|(value, _)| value))
}

fn get(&mut self, key: &K) -> Option<&V> {
fn get(&mut self, key: &K) -> Result<Option<&V>, BackingError> {
self.remove_old();
self.map.get(key)
.map(|(value, _)| value)
Ok(self.map.get(key)
.map(|(value, _)| value))
}

fn set(&mut self, key: K, value: V) -> Option<V> {
fn set(&mut self, key: K, value: V, meta: Option<Self::Meta>) -> Result<Option<V>, BackingError> {
self.remove_old();
let expiry = Instant::now().add(self.ttl);
let result = self.replace(key.clone(), value, expiry);
match self.expiry_queue.binary_search_by_key(&expiry, |entry| entry.expiry) {
Ok(found) => {
self.expiry_queue.insert(found + 1, (key, expiry).into());
}
Err(idx) => {
self.expiry_queue.insert(idx, (key, expiry).into());
}
}
result
let ttl = if let Some(meta) = meta {
meta.ttl
} else {
self.ttl
};
let expiry = Instant::now().add(ttl);
let result = self.replace(key.clone(), value, expiry)?;
Ok(result)
}

fn remove(&mut self, key: &K) -> Option<V> {
fn remove(&mut self, key: &K) -> Result<Option<V>, BackingError> {
self.remove_old();
self.remove_key(key)
Ok(self.remove_key(key)?)
}

fn contains_key(&self, key: &K) -> bool {
fn contains_key(&self, key: &K) -> Result<bool, BackingError> {
// we cant clean old keys on this, since the self ref is not mutable :(
self.map.get(key)
Ok(self.map.get(key)
.filter(|(_, expiry)| Instant::now().lt(expiry))
.is_some()
.is_some())
}

fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) {
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) -> Result<(), BackingError> {
let keys = self.map.iter()
.filter_map(|(key, (value, _))| {
if predicate((key, value)) {
Expand All @@ -174,11 +211,13 @@ impl<
// optimize looping through expiry_queue multiple times?
self.expiry_queue.retain(|entry| entry.key.ne(&key))
}
Ok(())
}

fn clear(&mut self) {
fn clear(&mut self) -> Result<(), BackingError> {
self.expiry_queue.clear();
self.map.clear();
Ok(())
}
}

Expand All @@ -203,34 +242,43 @@ impl<K: Eq + Hash + Sized + Clone + Send, V: Sized + Clone + Send> TtlCacheBacki
}
}

fn replace(&mut self, key: K, value: V, expiry: Instant) -> Option<V> {
fn replace(&mut self, key: K, value: V, expiry: Instant) -> Result<Option<V>, TtlError> {
let entry = self.map.insert(key.clone(), (value, expiry));
self.cleanup_expiry(entry, &key)
let res = self.cleanup_expiry(entry, &key);
match self.expiry_queue.binary_search_by_key(&expiry, |entry| entry.expiry) {
Ok(found) => {
self.expiry_queue.insert(found + 1, (key, expiry).into());
}
Err(idx) => {
self.expiry_queue.insert(idx, (key, expiry).into());
}
}
res
}

fn remove_key(&mut self, key: &K) -> Option<V> {
fn remove_key(&mut self, key: &K) -> Result<Option<V>, TtlError> {
let entry = self.map.remove(key);
self.cleanup_expiry(entry, key)
}

fn cleanup_expiry(&mut self, entry: Option<(V, Instant)>, key: &K) -> Option<V> {
fn cleanup_expiry(&mut self, entry: Option<(V, Instant)>, key: &K) -> Result<Option<V>, TtlError> {
if let Some((value, old_expiry)) = entry {
match self.expiry_queue.binary_search_by_key(&old_expiry, |entry| entry.expiry) {
Ok(found) => {
let index = self.expiry_index_on_key_eq(found, &old_expiry, key);
if let Some(index) = index {
self.expiry_queue.remove(index);
} else {
// expiry not found (key)???
return Err(TtlError::ExpiryKeyNotFound);
}
}
Err(_) => {
// expiry not found???
return Err(TtlError::ExpiryNotFound);
}
}
Some(value)
Ok(Some(value))
} else {
None
Ok(None)
}
}

Expand Down Expand Up @@ -274,32 +322,36 @@ impl<
K: Eq + Hash + Sized + Clone + Send,
V: Sized + Clone + Send
> CacheBacking<K, V> for HashMapBacking<K, V> {
fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.map.get_mut(key)
type Meta = NoMeta;

fn get_mut(&mut self, key: &K) -> Result<Option<&mut V>, BackingError> {
Ok(self.map.get_mut(key))
}

fn get(&mut self, key: &K) -> Option<&V> {
self.map.get(key)
fn get(&mut self, key: &K) -> Result<Option<&V>, BackingError> {
Ok(self.map.get(key))
}

fn set(&mut self, key: K, value: V) -> Option<V> {
self.map.insert(key, value)
fn set(&mut self, key: K, value: V, _meta: Option<Self::Meta>) -> Result<Option<V>, BackingError> {
Ok(self.map.insert(key, value))
}

fn remove(&mut self, key: &K) -> Option<V> {
self.map.remove(key)
fn remove(&mut self, key: &K) -> Result<Option<V>, BackingError> {
Ok(self.map.remove(key))
}

fn contains_key(&self, key: &K) -> bool {
self.map.contains_key(key)
fn contains_key(&self, key: &K) -> Result<bool, BackingError> {
Ok(self.map.contains_key(key))
}

fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) {
fn remove_if(&mut self, predicate: Box<dyn Fn((&K, &V)) -> bool + Send>) -> Result<(), BackingError> {
self.map.retain(|k, v| !predicate((k, v)));
Ok(())
}

fn clear(&mut self) {
fn clear(&mut self) -> Result<(), BackingError> {
self.map.clear();
Ok(())
}
}

Expand Down
Loading