From 98722bbf4d9c863ee377b37e715eeef5641c1eda Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Mon, 22 Jan 2024 18:07:30 -0800 Subject: [PATCH] boilerplate for fs caching Signed-off-by: wadeking98 --- libindy_vdr/Cargo.toml | 1 + libindy_vdr/src/pool/cache/fscache.rs | 6 + libindy_vdr/src/pool/cache/helpers.rs | 154 +++++++++++++++++-------- libindy_vdr/src/pool/cache/memcache.rs | 24 ++-- libindy_vdr/src/pool/cache/mod.rs | 7 +- 5 files changed, 132 insertions(+), 60 deletions(-) create mode 100644 libindy_vdr/src/pool/cache/fscache.rs diff --git a/libindy_vdr/Cargo.toml b/libindy_vdr/Cargo.toml index 16daf6cb..71d42402 100644 --- a/libindy_vdr/Cargo.toml +++ b/libindy_vdr/Cargo.toml @@ -62,6 +62,7 @@ url = "2.2.2" zmq = "0.9" async-trait = "0.1.77" async-lock = "3.3.0" +sled = "0.34.7" [dev-dependencies] rstest = "0.18" diff --git a/libindy_vdr/src/pool/cache/fscache.rs b/libindy_vdr/src/pool/cache/fscache.rs new file mode 100644 index 00000000..f3b4f820 --- /dev/null +++ b/libindy_vdr/src/pool/cache/fscache.rs @@ -0,0 +1,6 @@ +use sled; + +pub fn test() { + let sled = sled::open("my_db").unwrap(); + sled.insert(b"yo!", vec![1, 2, 3]).unwrap(); +} diff --git a/libindy_vdr/src/pool/cache/helpers.rs b/libindy_vdr/src/pool/cache/helpers.rs index b476c7ae..d29751f6 100644 --- a/libindy_vdr/src/pool/cache/helpers.rs +++ b/libindy_vdr/src/pool/cache/helpers.rs @@ -1,18 +1,89 @@ +use serde::{de::DeserializeOwned, Serialize}; +use sled::{self, IVec, Tree}; use std::{ collections::{BTreeMap, HashMap}, hash::Hash, }; + +pub trait OrderedStore: Send + Sync { + fn len(&self) -> usize; + fn first_key_value(&self) -> Option<(O, V)>; + fn last_key_value(&self) -> Option<(O, V)>; + fn get(&self, key: &O) -> Option; + fn insert(&mut self, key: O, value: V) -> Option; + fn remove(&mut self, key: &O) -> Option; +} +impl OrderedStore for Tree { + fn len(&self) -> usize { + Tree::len(self) + } + fn first_key_value(&self) -> Option<(IVec, V)> { + match self.first() { + Ok(Some((k, v))) => serde_json::from_slice(v.as_ref()).ok().map(|v| (k, v)), + _ => None, + } + } + fn last_key_value(&self) -> Option<(IVec, V)> { + match self.last() { + Ok(Some((k, v))) => serde_json::from_slice(v.as_ref()).ok().map(|v| (k, v)), + _ => None, + } + } + fn get(&self, key: &IVec) -> Option { + match self.get(key) { + Ok(Some(v)) => serde_json::from_slice(v.as_ref()).ok(), + _ => None, + } + } + fn insert(&mut self, key: IVec, value: V) -> Option { + match Tree::insert(self, key, serde_json::to_vec(&value).unwrap()) { + Ok(Some(v)) => serde_json::from_slice(v.as_ref()).ok(), + _ => None, + } + } + fn remove(&mut self, key: &IVec) -> Option { + match Tree::remove(self, key).map(|v| v) { + Ok(Some(v)) => serde_json::from_slice(&v).ok(), + _ => None, + } + } +} +impl OrderedStore for BTreeMap { + fn len(&self) -> usize { + BTreeMap::len(self) + } + fn first_key_value(&self) -> Option<(O, V)> { + BTreeMap::first_key_value(self).map(|(o, v)| (*o, v.clone())) + } + fn last_key_value(&self) -> Option<(O, V)> { + BTreeMap::last_key_value(self).map(|(o, v)| (*o, v.clone())) + } + fn get(&self, key: &O) -> Option { + BTreeMap::get(self, key).map(|v| v.clone()) + } + fn insert(&mut self, key: O, value: V) -> Option { + BTreeMap::insert(self, key, value) + } + fn remove(&mut self, key: &O) -> Option { + BTreeMap::remove(self, key) + } +} /// A hashmap that also maintains a BTreeMap of keys ordered by a given value /// This is useful for structures that need fast O(1) lookups, but also need to evict the oldest or least recently used entries -pub(crate) struct OrderedHashMap((HashMap, BTreeMap>)); +pub struct OrderedHashMap( + ( + HashMap, + Box> + Send + Sync>, + ), +); -impl OrderedHashMap { - pub(crate) fn new() -> Self { - Self((HashMap::new(), BTreeMap::new())) +impl OrderedHashMap { + pub fn new(order: impl OrderedStore> + 'static) -> Self { + Self((HashMap::new(), Box::new(order))) } } -impl OrderedHashMap { +impl OrderedHashMap { pub fn len(&self) -> usize { let (lookup, _) = &self.0; lookup.len() @@ -23,63 +94,55 @@ impl OrderedHashMap { } fn get_key_value( &self, - selector: Box>) -> Option<(&O, &Vec)>>, - ) -> Option<(&K, &O, &V)> { + selector: Box< + dyn Fn(&Box> + Send + Sync>) -> Option<(O, Vec)>, + >, + ) -> Option<(K, O, V)> { let (lookup, ordered_lookup) = &self.0; selector(ordered_lookup).and_then(|(_, keys)| { - keys.first() - .and_then(|key| lookup.get(key).and_then(|(o, v)| Some((key, o, v)))) + keys.first().and_then(|key| { + lookup + .get(key) + .and_then(|(o, v)| Some((key.clone(), *o, v.clone()))) + }) }) } /// gets the entry with the lowest order value - pub fn get_first_key_value(&self) -> Option<(&K, &O, &V)> { + pub fn get_first_key_value(&self) -> Option<(K, O, V)> { self.get_key_value(Box::new(|ordered_lookup| ordered_lookup.first_key_value())) } /// gets the entry with the highest order value - pub fn get_last_key_value(&self) -> Option<(&K, &O, &V)> { + pub fn get_last_key_value(&self) -> Option<(K, O, V)> { self.get_key_value(Box::new(|ordered_lookup| ordered_lookup.last_key_value())) } - /// re-orders the entry with the given key + /// re-orders the entry with the given new order pub fn re_order(&mut self, key: &K, new_order: O) { - let (lookup, order_lookup) = &mut self.0; - if let Some((old_order, _)) = lookup.get(key) { - // remove entry in btree - match order_lookup.get_mut(old_order) { - Some(keys) => { - keys.retain(|k| k != key); - if keys.len() == 0 { - order_lookup.remove(old_order); - } - } - None => {} - } + if let Some((_, value)) = self.remove(key) { + self.insert(key.clone(), value, new_order); } - order_lookup - .entry(new_order) - .or_insert(vec![]) - .push(key.clone()); - lookup.get_mut(key).map(|(o, _)| *o = new_order); } /// inserts a new entry with the given key and value and order pub fn insert(&mut self, key: K, value: V, order: O) -> Option { let (lookup, order_lookup) = &mut self.0; if let Some((old_order, _)) = lookup.get(&key) { - // remove entry in btree - match order_lookup.get_mut(old_order) { - Some(keys) => { - keys.retain(|k| k != &key); - if keys.len() == 0 { - order_lookup.remove(old_order); - } + // if entry already exists, remove it from the btree + if let Some(mut keys) = order_lookup.remove(old_order) { + keys.retain(|k| *k != key); + // insert modified keys back into btree + if !keys.is_empty() { + order_lookup.insert(*old_order, keys); } - None => {} } } - order_lookup - .entry(order) - .or_insert(vec![]) - .push(key.clone()); + let keys = match order_lookup.remove(&order) { + Some(mut ks) => { + ks.push(key.clone()); + ks + } + None => vec![key.clone()], + }; + order_lookup.insert(order, keys); lookup .insert(key, (order, value)) .and_then(|(_, v)| Some(v)) @@ -88,11 +151,12 @@ impl OrderedHashMap { pub fn remove(&mut self, key: &K) -> Option<(O, V)> { let (lookup, order_lookup) = &mut self.0; lookup.remove(key).and_then(|(order, v)| { - match order_lookup.get_mut(&order) { - Some(keys) => { + match order_lookup.remove(&order) { + Some(mut keys) => { keys.retain(|k| k != key); - if keys.len() == 0 { - order_lookup.remove(&order); + // insert remaining keys back in + if !keys.is_empty() { + order_lookup.insert(order, keys); } } None => {} diff --git a/libindy_vdr/src/pool/cache/memcache.rs b/libindy_vdr/src/pool/cache/memcache.rs index 962b32b8..a5e00dbd 100644 --- a/libindy_vdr/src/pool/cache/memcache.rs +++ b/libindy_vdr/src/pool/cache/memcache.rs @@ -2,7 +2,7 @@ use super::helpers::OrderedHashMap; use super::CacheStorage; use async_lock::Mutex; use async_trait::async_trait; -use std::{hash::Hash, sync::Arc, time::SystemTime}; +use std::{collections::BTreeMap, fmt::Debug, hash::Hash, sync::Arc, time::SystemTime}; /// A simple in-memory cache that uses timestamps to expire entries. Once the cache fills up, the oldest entry is evicted. /// Uses a hashmap for lookups and a BTreeMap for ordering by age pub struct MemCacheStorageTTL { @@ -12,11 +12,11 @@ pub struct MemCacheStorageTTL { expire_after: u128, } -impl MemCacheStorageTTL { +impl MemCacheStorageTTL { /// Create a new cache with the given capacity and expiration time in milliseconds pub fn new(capacity: usize, expire_after: u128) -> Self { Self { - store: OrderedHashMap::new(), + store: OrderedHashMap::new(BTreeMap::new()), capacity, startup_time: SystemTime::now(), expire_after, @@ -25,7 +25,7 @@ impl MemCacheStorageTTL { } #[async_trait] -impl +impl CacheStorage for MemCacheStorageTTL { async fn get(&self, key: &K) -> Option { @@ -35,7 +35,7 @@ impl 0 && self .store .get_first_key_value() - .map(|(_, ts, _)| ts + exp_offset < current_ts) + .map(|(_, ts, _)| ts.clone() < current_ts) .unwrap_or(false) { self.store.remove_first(); @@ -74,7 +73,8 @@ impl { capacity: usize, } -impl MemCacheStorageLRU { +impl MemCacheStorageLRU { pub fn new(capacity: usize) -> Self { Self { - store: Arc::new(Mutex::new(OrderedHashMap::new())), + store: Arc::new(Mutex::new(OrderedHashMap::new(BTreeMap::new()))), capacity, } } @@ -145,7 +145,7 @@ mod tests { #[rstest] fn test_cache_lru() { - let mut cache = Cache::new(MemCacheStorageLRU::new(2)); + let cache = Cache::new(MemCacheStorageLRU::new(2)); block_on(async { cache.insert("key".to_string(), "value".to_string()).await; assert_eq!( @@ -170,7 +170,7 @@ mod tests { #[rstest] fn test_cache_ttl() { - let mut cache = Cache::new(MemCacheStorageTTL::new(2, 5)); + let cache = Cache::new(MemCacheStorageTTL::new(2, 5)); block_on(async { cache.insert("key".to_string(), "value".to_string()).await; thread::sleep(std::time::Duration::from_millis(1)); diff --git a/libindy_vdr/src/pool/cache/mod.rs b/libindy_vdr/src/pool/cache/mod.rs index b657d0f8..2fb6483c 100644 --- a/libindy_vdr/src/pool/cache/mod.rs +++ b/libindy_vdr/src/pool/cache/mod.rs @@ -4,6 +4,7 @@ use std::sync::Arc; mod helpers; pub mod memcache; +pub mod fscache; #[async_trait] pub trait CacheStorage: Send + Sync + 'static { @@ -24,13 +25,13 @@ impl Cache { storage: Arc::new(RwLock::new(storage)), } } - pub async fn get(&mut self, key: &K) -> Option { + pub async fn get(&self, key: &K) -> Option { self.storage.read().await.get(key).await } - pub async fn remove(&mut self, key: &K) -> Option { + pub async fn remove(&self, key: &K) -> Option { self.storage.write().await.remove(key).await } - pub async fn insert(&mut self, key: K, value: V) -> Option { + pub async fn insert(&self, key: K, value: V) -> Option { self.storage.write().await.insert(key, value).await } }