use crate::backend::policy::lru::ResourceCounter;
use crate::backend::policy::{lru::LruCachePolicy, CachePolicy, CachePolicyPutResult};
use hashbrown::hash_map::DefaultHashBuilder;
use hashlink::linked_hash_map::{self, IntoIter, Iter, IterMut, LinkedHashMap};
use std::any::Any;
use std::fmt;
use std::fmt::Debug;
use std::hash::{BuildHasher, Hash};
pub struct LruCache<K, V, H = DefaultHashBuilder>
where
K: Clone + Eq + Hash + Ord + Debug + Send + 'static,
V: Clone + Debug + Send + 'static,
{
map: LinkedHashMap<K, V, H>,
resource_counter: Box<dyn ResourceCounter<K = K, V = V>>,
}
impl<K, V> LruCache<K, V>
where
K: Clone + Eq + Hash + Ord + Debug + Send + 'static,
V: Clone + Debug + Send + 'static,
{
pub fn with_resource_counter<R>(resource_counter: R) -> Self
where
R: ResourceCounter<K = K, V = V>,
{
LruCache {
map: LinkedHashMap::new(),
resource_counter: Box::new(resource_counter),
}
}
}
impl<K, V, H> LruCache<K, V, H>
where
K: Clone + Eq + Hash + Ord + Debug + Send + 'static,
V: Clone + Debug + Send + 'static,
{
pub fn with_resource_counter_and_hasher<R>(
resource_counter: R,
hash_builder: H,
) -> Self
where
R: ResourceCounter<K = K, V = V>,
{
LruCache {
map: LinkedHashMap::with_hasher(hash_builder),
resource_counter: Box::new(resource_counter),
}
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn iter(&self) -> Iter<K, V> {
self.map.iter()
}
pub fn iter_mut(&mut self) -> IterMut<K, V> {
self.map.iter_mut()
}
}
impl<K, V, H> LruCachePolicy for LruCache<K, V, H>
where
K: 'static + Clone + Debug + Eq + Hash + Ord + Send,
H: 'static + BuildHasher + Debug + Send,
V: 'static + Clone + Debug + Send,
{
fn get_lru(&mut self, k: &Self::K) -> Option<Self::V> {
match self.map.raw_entry_mut().from_key(k) {
linked_hash_map::RawEntryMut::Occupied(mut occupied) => {
occupied.to_back();
Some(occupied.into_mut().clone())
}
linked_hash_map::RawEntryMut::Vacant(_) => None,
}
}
fn put_lru(
&mut self,
k: Self::K,
v: Self::V,
) -> CachePolicyPutResult<Self::K, Self::V> {
let old_val = self.map.insert(k.clone(), v.clone());
self.resource_counter.consume(&k, &v);
if let Some(old_val) = &old_val {
self.resource_counter.restore(&k, old_val);
}
let mut popped_entries = vec![];
while self.resource_counter.exceed_capacity() {
if let Some(entry) = self.pop_lru() {
popped_entries.push(entry);
}
}
(old_val, popped_entries)
}
fn pop_lru(&mut self) -> Option<(Self::K, Self::V)> {
if let Some(entry) = self.map.pop_front() {
self.resource_counter.restore(&entry.0, &entry.1);
Some(entry)
} else {
None
}
}
}
impl<K, V, H> CachePolicy for LruCache<K, V, H>
where
K: 'static + Clone + Debug + Eq + Hash + Ord + Send,
H: 'static + BuildHasher + Debug + Send,
V: 'static + Clone + Debug + Send,
{
type K = K;
type V = V;
fn get(&mut self, k: &Self::K) -> Option<Self::V> {
self.get_lru(k)
}
fn peek(&mut self, k: &Self::K) -> Option<Self::V> {
self.map.get(k).cloned()
}
fn put(&mut self, k: Self::K, v: Self::V) -> CachePolicyPutResult<Self::K, Self::V> {
self.put_lru(k, v)
}
fn remove(&mut self, k: &Self::K) -> Option<Self::V> {
if let Some(v) = self.map.remove(k) {
self.resource_counter.restore(k, &v);
Some(v)
} else {
None
}
}
fn pop(&mut self) -> Option<(Self::K, Self::V)> {
self.pop_lru()
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl<K, V, H> IntoIterator for LruCache<K, V, H>
where
K: 'static + Clone + Debug + Eq + Hash + Ord + Send,
V: 'static + Clone + Debug + Send,
{
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> IntoIter<K, V> {
self.map.into_iter()
}
}
impl<'a, K, V, H> IntoIterator for &'a LruCache<K, V, H>
where
K: 'static + Clone + Debug + Eq + Hash + Ord + Send,
V: 'static + Clone + Debug + Send,
{
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Iter<'a, K, V> {
self.iter()
}
}
impl<'a, K, V, H> IntoIterator for &'a mut LruCache<K, V, H>
where
K: 'static + Clone + Debug + Eq + Hash + Ord + Send,
V: 'static + Clone + Debug + Send,
{
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> IterMut<'a, K, V> {
self.iter_mut()
}
}
impl<K, V, H> Debug for LruCache<K, V, H>
where
K: 'static + Clone + Debug + Eq + Hash + Ord + Send,
V: 'static + Clone + Debug + Send,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_map().entries(self.iter().rev()).finish()
}
}
#[cfg(test)]
mod tests {
use crate::backend::policy::lru::lru_cache::LruCache;
use crate::backend::policy::lru::{DefaultResourceCounter, ResourceCounter};
use crate::backend::policy::CachePolicy;
use hashbrown::HashMap;
#[test]
fn test_cache_with_lru_policy() {
let mut cache = LruCache::with_resource_counter(DefaultResourceCounter::new(3));
cache.put("1".to_string(), "file1".to_string());
cache.put("2".to_string(), "file2".to_string());
cache.put("3".to_string(), "file3".to_string());
assert_eq!(3, cache.len());
cache.put("4".to_string(), "file4".to_string());
assert_eq!(3, cache.len());
assert!(cache.peek(&"1".to_string()).is_none());
assert!(cache.peek(&"2".to_string()).is_some());
let mut iter = cache.iter();
assert_eq!("2", iter.next().unwrap().0);
assert_eq!("3", iter.next().unwrap().0);
assert_eq!("4", iter.next().unwrap().0);
assert!(cache.get(&"2".to_string()).is_some());
let mut iter = cache.iter();
assert_eq!("3", iter.next().unwrap().0);
assert_eq!("4", iter.next().unwrap().0);
assert_eq!("2", iter.next().unwrap().0);
assert_eq!(Some("file4".to_string()), cache.remove(&"4".to_string()));
assert_eq!("3".to_string(), cache.pop().unwrap().0);
assert_eq!("2".to_string(), cache.pop().unwrap().0);
assert!(cache.pop().is_none());
}
#[test]
fn test_cache_with_size_resource_counter() {
let mut cache =
LruCache::with_resource_counter(get_test_size_resource_counter(50));
cache.put("1".to_string(), "file1".to_string());
cache.put("2".to_string(), "file2".to_string());
cache.put("3".to_string(), "file3".to_string());
assert_eq!(3, cache.len());
cache.put("4".to_string(), "file4".to_string());
assert_eq!(2, cache.len());
assert!(cache.peek(&"1".to_string()).is_none());
assert!(cache.peek(&"2".to_string()).is_none());
assert!(cache.peek(&"3".to_string()).is_some());
let mut iter = cache.iter();
assert_eq!("3", iter.next().unwrap().0);
assert_eq!("4", iter.next().unwrap().0);
assert!(cache.get(&"3".to_string()).is_some());
let mut iter = cache.iter();
assert_eq!("4", iter.next().unwrap().0);
assert_eq!("3", iter.next().unwrap().0);
cache.put("5".to_string(), "file5".to_string());
cache.put("3".to_string(), "file3-bak".to_string());
cache.put("1".to_string(), "file1".to_string());
let mut iter = cache.iter();
assert_eq!("3", iter.next().unwrap().0);
assert_eq!("1", iter.next().unwrap().0);
assert!(iter.next().is_none());
}
fn get_test_size_resource_counter(max_size: usize) -> TestSizeResourceCounter {
let mut size_map = HashMap::new();
size_map.insert(("1".to_string(), "file1".to_string()), 10);
size_map.insert(("2".to_string(), "file2".to_string()), 20);
size_map.insert(("3".to_string(), "file3".to_string()), 15);
size_map.insert(("3".to_string(), "file3-bak".to_string()), 30);
size_map.insert(("4".to_string(), "file4".to_string()), 35);
size_map.insert(("5".to_string(), "file5".to_string()), 25);
TestSizeResourceCounter {
size_map,
max_size,
current_size: 0,
}
}
#[derive(Debug)]
struct TestSizeResourceCounter {
size_map: HashMap<(String, String), usize>,
max_size: usize,
current_size: usize,
}
impl ResourceCounter for TestSizeResourceCounter {
type K = String;
type V = String;
fn consume(&mut self, k: &Self::K, v: &Self::V) {
let s = self.size_map.get(&(k.clone(), v.clone())).unwrap();
self.current_size += s;
}
fn restore(&mut self, k: &Self::K, v: &Self::V) {
let s = self.size_map.get(&(k.clone(), v.clone())).unwrap();
self.current_size -= s;
}
fn exceed_capacity(&self) -> bool {
self.current_size > self.max_size
}
}
}