Skip to content

Commit

Permalink
moved cache functionality to module
Browse files Browse the repository at this point in the history
Signed-off-by: wadeking98 <wkingnumber2@gmail.com>
  • Loading branch information
wadeking98 committed Jan 19, 2024
1 parent c54dc01 commit bd281a7
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 165 deletions.
2 changes: 1 addition & 1 deletion indy-vdr-proxy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use hyper_tls::HttpsConnector;
#[cfg(unix)]
use hyper_unix_connector::UnixConnector;

use indy_vdr::pool::cache::{Cache, MemCacheStorageTTL};
use indy_vdr::pool::cache::{memcache::MemCacheStorageTTL, Cache};
#[cfg(feature = "tls")]
use rustls_pemfile::{certs, pkcs8_private_keys};
#[cfg(feature = "tls")]
Expand Down
113 changes: 113 additions & 0 deletions libindy_vdr/src/pool/cache/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use std::{
collections::{BTreeMap, HashMap},
hash::Hash,
};
/// 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<K, O, V>((HashMap<K, (O, V)>, BTreeMap<O, Vec<K>>));

impl<K, O, V> OrderedHashMap<K, O, V> {
pub(crate) fn new() -> Self {
Self((HashMap::new(), BTreeMap::new()))
}
}

impl<K: Hash + Eq + Clone, O: Ord + Copy, V> OrderedHashMap<K, O, V> {
pub fn len(&self) -> usize {
let (lookup, _) = &self.0;
lookup.len()
}
pub fn get(&self, key: &K) -> Option<&(O, V)> {
let (lookup, _) = &self.0;
lookup.get(key)
}
fn get_key_value(
&self,
selector: Box<dyn Fn(&BTreeMap<O, Vec<K>>) -> Option<(&O, &Vec<K>)>>,
) -> 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))))
})
}
/// gets the entry with the lowest order value
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)> {
self.get_key_value(Box::new(|ordered_lookup| ordered_lookup.last_key_value()))
}
/// re-orders the entry with the given key
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 => {}
}
}
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<V> {
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 => {}
}
}
order_lookup
.entry(order)
.or_insert(vec![])
.push(key.clone());
lookup
.insert(key, (order, value))
.and_then(|(_, v)| Some(v))
}
/// removes the entry with the given key
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) => {
keys.retain(|k| k != key);
if keys.len() == 0 {
order_lookup.remove(&order);
}
}
None => {}
}
Some((order, v))
})
}
/// removes the entry with the lowest order value
pub fn remove_first(&mut self) -> Option<(K, O, V)> {
let first_key = self.get_first_key_value().map(|(k, _, _)| k.clone());
if let Some(first_key) = first_key {
self.remove(&first_key)
.map(|(order, v)| (first_key, order, v))
} else {
None
}
}
}
Original file line number Diff line number Diff line change
@@ -1,163 +1,8 @@
use super::helpers::OrderedHashMap;
use super::CacheStorage;
use async_lock::Mutex;
use async_trait::async_trait;
use std::{
collections::{BTreeMap, HashMap},
fmt::Debug,
hash::Hash,
sync::Arc,
time::SystemTime,
};

use async_lock::{Mutex, RwLock};

#[async_trait]
pub trait CacheStorage<K, V>: Send + Sync + 'static {
async fn get(&self, key: &K) -> Option<V>;

async fn remove(&mut self, key: &K) -> Option<V>;

async fn insert(&mut self, key: K, value: V) -> Option<V>;
}

pub struct Cache<K, V> {
storage: Arc<RwLock<dyn CacheStorage<K, V>>>,
}

impl<K: 'static, V: 'static> Cache<K, V> {
pub fn new(storage: impl CacheStorage<K, V>) -> Self {
Self {
storage: Arc::new(RwLock::new(storage)),
}
}
pub async fn get(&mut self, key: &K) -> Option<V> {
self.storage.read().await.get(key).await
}
pub async fn remove(&mut self, key: &K) -> Option<V> {
self.storage.write().await.remove(key).await
}
pub async fn insert(&mut self, key: K, value: V) -> Option<V> {
self.storage.write().await.insert(key, value).await
}
}

// need to implement Clone manually because Mutex<dyn CacheStorage> doesn't implement Clone
impl<K, V> Clone for Cache<K, V> {
fn clone(&self) -> Self {
Self {
storage: self.storage.clone(),
}
}
}

/// 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
struct OrderedHashMap<K, O, V>((HashMap<K, (O, V)>, BTreeMap<O, Vec<K>>));

impl<K, O, V> OrderedHashMap<K, O, V> {
fn new() -> Self {
Self((HashMap::new(), BTreeMap::new()))
}
}

impl<K: Hash + Eq + Clone, O: Ord + Copy, V> OrderedHashMap<K, O, V> {
fn len(&self) -> usize {
let (lookup, _) = &self.0;
lookup.len()
}
fn get(&self, key: &K) -> Option<&(O, V)> {
let (lookup, _) = &self.0;
lookup.get(key)
}
fn get_key_value(
&self,
selector: Box<dyn Fn(&BTreeMap<O, Vec<K>>) -> Option<(&O, &Vec<K>)>>,
) -> 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))))
})
}
/// gets the entry with the lowest order value
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
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
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 => {}
}
}
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
fn insert(&mut self, key: K, value: V, order: O) -> Option<V> {
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 => {}
}
}
order_lookup
.entry(order)
.or_insert(vec![])
.push(key.clone());
lookup
.insert(key, (order, value))
.and_then(|(_, v)| Some(v))
}
/// removes the entry with the given key
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) => {
keys.retain(|k| k != key);
if keys.len() == 0 {
order_lookup.remove(&order);
}
}
None => {}
}
Some((order, v))
})
}
/// removes the entry with the lowest order value
fn remove_first(&mut self) -> Option<(K, O, V)> {
let first_key = self.get_first_key_value().map(|(k, _, _)| k.clone());
if let Some(first_key) = first_key {
self.remove(&first_key)
.map(|(order, v)| (first_key, order, v))
} else {
None
}
}
}

use std::{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<K, V> {
Expand All @@ -180,7 +25,7 @@ impl<K, V> MemCacheStorageTTL<K, V> {
}

#[async_trait]
impl<K: Hash + Eq + Send + Sync + 'static + Clone + Debug, V: Clone + Send + Sync + 'static>
impl<K: Hash + Eq + Send + Sync + 'static + Clone, V: Clone + Send + Sync + 'static>
CacheStorage<K, V> for MemCacheStorageTTL<K, V>
{
async fn get(&self, key: &K) -> Option<V> {
Expand Down Expand Up @@ -294,12 +139,12 @@ mod tests {

use std::thread;

use super::*;
use crate::pool::cache::Cache;
use futures_executor::block_on;

#[rstest]
fn test_cache_lru() {
use super::*;

let mut cache = Cache::new(MemCacheStorageLRU::new(2));
block_on(async {
cache.insert("key".to_string(), "value".to_string()).await;
Expand All @@ -325,8 +170,6 @@ mod tests {

#[rstest]
fn test_cache_ttl() {
use super::*;

let mut cache = Cache::new(MemCacheStorageTTL::new(2, 5));
block_on(async {
cache.insert("key".to_string(), "value".to_string()).await;
Expand Down
45 changes: 45 additions & 0 deletions libindy_vdr/src/pool/cache/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use async_lock::RwLock;
use async_trait::async_trait;
use std::sync::Arc;

mod helpers;
pub mod memcache;

#[async_trait]
pub trait CacheStorage<K, V>: Send + Sync + 'static {
async fn get(&self, key: &K) -> Option<V>;

async fn remove(&mut self, key: &K) -> Option<V>;

async fn insert(&mut self, key: K, value: V) -> Option<V>;
}

pub struct Cache<K, V> {
storage: Arc<RwLock<dyn CacheStorage<K, V>>>,
}

impl<K: 'static, V: 'static> Cache<K, V> {
pub fn new(storage: impl CacheStorage<K, V>) -> Self {
Self {
storage: Arc::new(RwLock::new(storage)),
}
}
pub async fn get(&mut self, key: &K) -> Option<V> {
self.storage.read().await.get(key).await
}
pub async fn remove(&mut self, key: &K) -> Option<V> {
self.storage.write().await.remove(key).await
}
pub async fn insert(&mut self, key: K, value: V) -> Option<V> {
self.storage.write().await.insert(key, value).await
}
}

// need to implement Clone manually because Mutex<dyn CacheStorage> doesn't implement Clone
impl<K, V> Clone for Cache<K, V> {
fn clone(&self) -> Self {
Self {
storage: self.storage.clone(),
}
}
}

0 comments on commit bd281a7

Please sign in to comment.