use crate::{CacheEntry, EvictionPolicy};
use once_cell::sync::Lazy;
use parking_lot::lock_api::MutexGuard;
use parking_lot::{Mutex, RawMutex, RwLock};
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use crate::utils::{
find_arc_eviction_key, find_min_frequency_key, find_tlru_eviction_key, move_key_to_end,
remove_key_from_global_cache,
};
#[cfg(feature = "stats")]
use crate::CacheStats;
pub struct GlobalCache<R: 'static> {
pub map: &'static Lazy<RwLock<HashMap<String, CacheEntry<R>>>>,
pub order: &'static Lazy<Mutex<VecDeque<String>>>,
pub limit: Option<usize>,
pub max_memory: Option<usize>,
pub policy: EvictionPolicy,
pub ttl: Option<u64>,
pub frequency_weight: Option<f64>,
pub window_ratio: Option<f64>,
pub sketch_width: Option<usize>,
pub sketch_depth: Option<usize>,
pub decay_interval: Option<u64>,
#[cfg(feature = "stats")]
pub stats: &'static Lazy<CacheStats>,
}
impl<R: Clone + 'static> GlobalCache<R> {
#[cfg(feature = "stats")]
pub fn new(
map: &'static Lazy<RwLock<HashMap<String, CacheEntry<R>>>>,
order: &'static Lazy<Mutex<VecDeque<String>>>,
limit: Option<usize>,
max_memory: Option<usize>,
policy: EvictionPolicy,
ttl: Option<u64>,
frequency_weight: Option<f64>,
window_ratio: Option<f64>,
sketch_width: Option<usize>,
sketch_depth: Option<usize>,
decay_interval: Option<u64>,
stats: &'static Lazy<CacheStats>,
) -> Self {
Self {
map,
order,
limit,
max_memory,
policy,
ttl,
frequency_weight,
window_ratio,
sketch_width,
sketch_depth,
decay_interval,
stats,
}
}
#[cfg(not(feature = "stats"))]
pub fn new(
map: &'static Lazy<RwLock<HashMap<String, CacheEntry<R>>>>,
order: &'static Lazy<Mutex<VecDeque<String>>>,
limit: Option<usize>,
max_memory: Option<usize>,
policy: EvictionPolicy,
ttl: Option<u64>,
frequency_weight: Option<f64>,
window_ratio: Option<f64>,
sketch_width: Option<usize>,
sketch_depth: Option<usize>,
decay_interval: Option<u64>,
) -> Self {
Self {
map,
order,
limit,
max_memory,
policy,
ttl,
frequency_weight,
window_ratio,
sketch_width,
sketch_depth,
decay_interval,
}
}
pub fn get(&self, key: &str) -> Option<R> {
let mut result = None;
let mut expired = false;
{
let m = self.map.read();
if let Some(entry) = m.get(key) {
if entry.is_expired(self.ttl) {
expired = true;
} else {
result = Some(entry.value.clone());
}
}
}
if expired {
let mut o = self.order.lock();
let mut map_write = self.map.write();
remove_key_from_global_cache(&mut map_write, &mut o, key);
#[cfg(feature = "stats")]
self.stats.record_miss();
return None;
}
#[cfg(feature = "stats")]
{
if result.is_some() {
self.stats.record_hit();
} else {
self.stats.record_miss();
}
}
if result.is_some() {
match self.policy {
EvictionPolicy::LRU => {
move_key_to_end(&mut self.order.lock(), key);
}
EvictionPolicy::LFU => {
self.increment_frequency(key);
}
EvictionPolicy::ARC => {
move_key_to_end(&mut self.order.lock(), key);
self.increment_frequency(key);
}
EvictionPolicy::TLRU => {
move_key_to_end(&mut self.order.lock(), key);
self.increment_frequency(key);
}
EvictionPolicy::WTinyLFU => {
move_key_to_end(&mut self.order.lock(), key);
self.increment_frequency(key);
}
EvictionPolicy::FIFO | EvictionPolicy::Random => {
}
}
}
result
}
fn increment_frequency(&self, key: &str) {
let mut m = self.map.write();
if let Some(entry) = m.get_mut(key) {
entry.increment_frequency();
}
}
pub fn insert(&self, key: &str, value: R) {
let key_s = key.to_string();
let entry = CacheEntry::new(value);
self.map.write().insert(key_s.clone(), entry);
let mut o = self.order.lock();
if let Some(pos) = o.iter().position(|k| *k == key_s) {
o.remove(pos);
}
o.push_back(key_s.clone());
self.handle_entry_limit_eviction(&mut o);
}
fn handle_entry_limit_eviction(&self, mut o: &mut MutexGuard<RawMutex, VecDeque<String>>) {
if let Some(limit) = self.limit {
if o.len() > limit {
match self.policy {
EvictionPolicy::LFU => {
let mut map_write = self.map.write();
let min_freq_key = find_min_frequency_key(&map_write, &o);
if let Some(evict_key) = min_freq_key {
remove_key_from_global_cache(&mut map_write, &mut o, &evict_key);
}
}
EvictionPolicy::ARC => {
let mut map_write = self.map.write();
if let Some(evict_key) =
find_arc_eviction_key(&map_write, o.iter().enumerate())
{
remove_key_from_global_cache(&mut map_write, &mut o, &evict_key);
}
}
EvictionPolicy::TLRU => {
let mut map_write = self.map.write();
if let Some(evict_key) = find_tlru_eviction_key(
&map_write,
o.iter().enumerate(),
self.ttl,
self.frequency_weight,
) {
remove_key_from_global_cache(&mut map_write, &mut o, &evict_key);
}
}
EvictionPolicy::WTinyLFU => {
let window_ratio = self.window_ratio.unwrap_or(0.20); let window_size = crate::utils::calculate_window_size(limit, window_ratio);
let mut map_write = self.map.write();
if o.len() <= window_size {
while let Some(evict_key) = o.pop_front() {
if map_write.contains_key(&evict_key) {
map_write.remove(&evict_key);
break;
}
}
} else {
let mut evicted = false;
for i in 0..window_size.min(o.len()) {
if let Some(evict_key) = o.get(i) {
if map_write.contains_key(evict_key) {
let key_to_remove = evict_key.clone();
map_write.remove(&key_to_remove);
o.remove(i);
evicted = true;
break;
}
}
}
if !evicted {
let protected_keys: VecDeque<String> =
o.iter().skip(window_size).cloned().collect();
if let Some(evict_key) =
find_min_frequency_key(&map_write, &protected_keys)
{
remove_key_from_global_cache(
&mut map_write,
&mut o,
&evict_key,
);
}
}
}
}
EvictionPolicy::Random => {
if !o.is_empty() {
let pos = fastrand::usize(..o.len());
if let Some(evict_key) = o.remove(pos) {
let mut map_write = self.map.write();
map_write.remove(&evict_key);
}
}
}
EvictionPolicy::FIFO | EvictionPolicy::LRU => {
let mut map_write = self.map.write();
while let Some(evict_key) = o.pop_front() {
if map_write.contains_key(&evict_key) {
map_write.remove(&evict_key);
break;
}
}
}
}
}
}
}
}
impl<R: Clone + 'static + crate::MemoryEstimator> GlobalCache<R> {
pub fn insert_with_memory(&self, key: &str, value: R) {
let key_s = key.to_string();
let entry = CacheEntry::new(value);
self.map.write().insert(key_s.clone(), entry);
let mut o = self.order.lock();
if let Some(pos) = o.iter().position(|k| *k == key_s) {
o.remove(pos);
}
o.push_back(key_s.clone());
if let Some(max_mem) = self.max_memory {
let new_value_size = {
let map_read = self.map.read();
map_read
.get(&key_s)
.map(|e| e.value.estimate_memory())
.unwrap_or(0)
};
if new_value_size > max_mem {
self.map.write().remove(&key_s);
o.pop_back(); return;
}
loop {
let current_mem = {
let map_read = self.map.read();
map_read
.values()
.map(|e| e.value.estimate_memory())
.sum::<usize>()
};
if current_mem <= max_mem {
break;
}
let evicted = match self.policy {
EvictionPolicy::LFU => {
let mut map_write = self.map.write();
let min_freq_key = find_min_frequency_key(&map_write, &o);
if let Some(evict_key) = min_freq_key {
remove_key_from_global_cache(&mut map_write, &mut o, &evict_key);
true
} else {
false
}
}
EvictionPolicy::ARC => {
let mut map_write = self.map.write();
if let Some(evict_key) =
find_arc_eviction_key(&map_write, o.iter().enumerate())
{
remove_key_from_global_cache(&mut map_write, &mut o, &evict_key);
true
} else {
false
}
}
EvictionPolicy::TLRU => {
let mut map_write = self.map.write();
if let Some(evict_key) = find_tlru_eviction_key(
&map_write,
o.iter().enumerate(),
self.ttl,
self.frequency_weight,
) {
remove_key_from_global_cache(&mut map_write, &mut o, &evict_key);
true
} else {
false
}
}
EvictionPolicy::WTinyLFU => {
let mut map_write = self.map.write();
if let Some(evict_key) = find_min_frequency_key(&map_write, &o) {
remove_key_from_global_cache(&mut map_write, &mut o, &evict_key);
true
} else {
false
}
}
EvictionPolicy::Random => {
if !o.is_empty() {
let pos = fastrand::usize(..o.len());
if let Some(evict_key) = o.remove(pos) {
let mut map_write = self.map.write();
map_write.remove(&evict_key);
true
} else {
false
}
} else {
false
}
}
EvictionPolicy::FIFO | EvictionPolicy::LRU => {
let mut successfully_evicted = false;
let mut map_write = self.map.write();
while let Some(evict_key) = o.pop_front() {
if map_write.contains_key(&evict_key) {
map_write.remove(&evict_key);
successfully_evicted = true;
break;
}
}
successfully_evicted
}
};
if !evicted {
break; }
}
}
self.handle_entry_limit_eviction(&mut o);
}
#[cfg(feature = "stats")]
pub fn stats(&self) -> &CacheStats {
self.stats
}
pub fn clear(&self) {
self.map.write().clear();
self.order.lock().clear();
}
}
impl<T: Clone + Debug + 'static, E: Clone + Debug + 'static> GlobalCache<Result<T, E>> {
pub fn insert_result(&self, key: &str, value: &Result<T, E>) {
if let Ok(v) = value {
self.insert(key, Ok(v.clone()));
}
}
}
impl<
T: Clone + Debug + 'static + crate::MemoryEstimator,
E: Clone + Debug + 'static + crate::MemoryEstimator,
> GlobalCache<Result<T, E>>
{
pub fn insert_result_with_memory(&self, key: &str, value: &Result<T, E>) {
if let Ok(v) = value {
self.insert_with_memory(key, Ok(v.clone()));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_global_basic_insert_get() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("key1", 100);
assert_eq!(cache.get("key1"), Some(100));
}
#[test]
fn test_global_missing_key() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
assert_eq!(cache.get("nonexistent"), None);
}
#[test]
fn test_global_update_existing() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("key", 1);
cache.insert("key", 2);
assert_eq!(cache.get("key"), Some(2));
}
#[test]
fn test_global_fifo_eviction() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(2),
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
cache.insert("k3", 3);
assert_eq!(cache.get("k1"), None);
assert_eq!(cache.get("k2"), Some(2));
assert_eq!(cache.get("k3"), Some(3));
}
#[test]
fn test_global_lru_eviction() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(2),
None,
EvictionPolicy::LRU,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
let _ = cache.get("k1");
cache.insert("k3", 3);
assert_eq!(cache.get("k1"), Some(1));
assert_eq!(cache.get("k2"), None);
assert_eq!(cache.get("k3"), Some(3));
}
#[test]
fn test_global_lru_multiple_accesses() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(3),
None,
EvictionPolicy::LRU,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
cache.insert("k3", 3);
let _ = cache.get("k1");
let _ = cache.get("k1");
cache.insert("k4", 4);
assert_eq!(cache.get("k1"), Some(1));
assert_eq!(cache.get("k2"), None);
assert_eq!(cache.get("k3"), Some(3));
assert_eq!(cache.get("k4"), Some(4));
}
#[test]
fn test_global_thread_safety() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let handles: Vec<_> = (0..10)
.map(|i| {
thread::spawn(move || {
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert(&format!("key{}", i), i);
thread::sleep(Duration::from_millis(10));
cache.get(&format!("key{}", i))
})
})
.collect();
for (i, handle) in handles.into_iter().enumerate() {
let result = handle.join().unwrap();
assert_eq!(result, Some(i as i32));
}
}
#[test]
fn test_global_ttl_expiration() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
Some(1),
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("expires", 999);
assert_eq!(cache.get("expires"), Some(999));
thread::sleep(Duration::from_secs(2));
assert_eq!(cache.get("expires"), None);
}
#[test]
fn test_global_result_ok() {
static RES_MAP: Lazy<RwLock<HashMap<String, CacheEntry<Result<i32, String>>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static RES_ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&RES_MAP,
&RES_ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
let ok_result = Ok(42);
cache.insert_result("success", &ok_result);
assert_eq!(cache.get("success"), Some(Ok(42)));
}
#[test]
fn test_global_result_err() {
static RES_MAP: Lazy<RwLock<HashMap<String, CacheEntry<Result<i32, String>>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static RES_ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&RES_MAP,
&RES_ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
let err_result: Result<i32, String> = Err("error".to_string());
cache.insert_result("failure", &err_result);
assert_eq!(cache.get("failure"), None); }
#[test]
fn test_global_concurrent_lru_access() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(5),
None,
EvictionPolicy::LRU,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for i in 0..5 {
cache.insert(&format!("k{}", i), i);
}
let handles: Vec<_> = (0..5)
.map(|_| {
thread::spawn(|| {
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(5),
None,
EvictionPolicy::LRU,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for _ in 0..10 {
let _ = cache.get("k0");
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(cache.get("k0"), Some(0));
}
#[test]
fn test_global_no_limit() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for i in 0..100 {
cache.insert(&format!("k{}", i), i);
}
for i in 0..100 {
assert_eq!(cache.get(&format!("k{}", i)), Some(i));
}
}
#[test]
fn test_memory_eviction_skips_orphan_and_removes_real_entry() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
Some(std::mem::size_of::<i32>()),
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert_with_memory("k1", 1i32);
{
let mut o = ORDER.lock();
o.push_front("orphan".to_string());
}
cache.insert_with_memory("k2", 2i32);
assert_eq!(cache.get("k1"), None);
assert_eq!(cache.get("k2"), Some(2));
let order_contents: Vec<String> = {
let o = ORDER.lock();
o.iter().cloned().collect()
};
assert!(order_contents.iter().all(|k| k != "orphan"));
}
#[test]
fn test_rwlock_concurrent_reads() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for i in 0..10 {
cache.insert(&format!("key{}", i), i);
}
let handles: Vec<_> = (0..20)
.map(|_thread_id| {
thread::spawn(move || {
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
let mut results = Vec::new();
for i in 0..10 {
results.push(cache.get(&format!("key{}", i)));
}
results
})
})
.collect();
for handle in handles {
let results = handle.join().unwrap();
for (i, result) in results.iter().enumerate() {
assert_eq!(*result, Some(i as i32));
}
}
}
#[test]
fn test_rwlock_write_excludes_reads() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("key1", 100);
let write_handle = thread::spawn(|| {
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for i in 0..50 {
cache.insert(&format!("key{}", i), i);
thread::sleep(Duration::from_micros(100));
}
});
let read_handles: Vec<_> = (0..5)
.map(|_| {
thread::spawn(|| {
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for i in 0..50 {
let _ = cache.get(&format!("key{}", i));
thread::sleep(Duration::from_micros(100));
}
})
})
.collect();
write_handle.join().unwrap();
for handle in read_handles {
handle.join().unwrap();
}
}
#[test]
#[cfg(feature = "stats")]
fn test_global_stats_basic() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
let _ = cache.get("k1"); let _ = cache.get("k2"); let _ = cache.get("k3");
let stats = cache.stats();
assert_eq!(stats.hits(), 2);
assert_eq!(stats.misses(), 1);
assert_eq!(stats.total_accesses(), 3);
assert!((stats.hit_rate() - 0.6666).abs() < 0.001);
}
#[test]
#[cfg(feature = "stats")]
fn test_global_stats_expired_counts_as_miss() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
Some(1),
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("expires", 999);
let _ = cache.get("expires");
assert_eq!(cache.stats().hits(), 1);
assert_eq!(cache.stats().misses(), 0);
thread::sleep(Duration::from_secs(2));
let _ = cache.get("expires");
assert_eq!(cache.stats().hits(), 1);
assert_eq!(cache.stats().misses(), 1);
}
#[test]
#[cfg(feature = "stats")]
fn test_global_stats_reset() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
let _ = cache.get("k1");
let _ = cache.get("k2");
let stats = cache.stats();
assert_eq!(stats.hits(), 1);
assert_eq!(stats.misses(), 1);
stats.reset();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 0);
}
#[test]
#[cfg(feature = "stats")]
fn test_global_stats_concurrent_access() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
let handles: Vec<_> = (0..10)
.map(|_| {
thread::spawn(|| {
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for _ in 0..10 {
let _ = cache.get("k1"); let _ = cache.get("k2"); let _ = cache.get("k3"); }
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let stats = cache.stats();
assert_eq!(stats.hits(), 200);
assert_eq!(stats.misses(), 100);
assert_eq!(stats.total_accesses(), 300);
}
#[test]
#[cfg(feature = "stats")]
fn test_global_stats_all_hits() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
for _ in 0..10 {
let _ = cache.get("k1");
let _ = cache.get("k2");
}
let stats = cache.stats();
assert_eq!(stats.hits(), 20);
assert_eq!(stats.misses(), 0);
assert_eq!(stats.hit_rate(), 1.0);
assert_eq!(stats.miss_rate(), 0.0);
}
#[test]
#[cfg(feature = "stats")]
fn test_global_stats_all_misses() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
None,
None,
EvictionPolicy::FIFO,
None,
None,
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for i in 0..10 {
let _ = cache.get(&format!("k{}", i));
}
let stats = cache.stats();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 10);
assert_eq!(stats.hit_rate(), 0.0);
assert_eq!(stats.miss_rate(), 1.0);
}
#[test]
fn test_tlru_with_low_frequency_weight() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(3),
None,
EvictionPolicy::TLRU,
Some(10),
Some(0.3), None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
cache.insert("k3", 3);
for _ in 0..10 {
let _ = cache.get("k1");
}
thread::sleep(Duration::from_millis(100));
cache.insert("k4", 4);
assert_eq!(cache.get("k4"), Some(4));
}
#[test]
fn test_tlru_with_high_frequency_weight() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(3),
None,
EvictionPolicy::TLRU,
Some(10),
Some(1.5), None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
cache.insert("k3", 3);
for _ in 0..10 {
let _ = cache.get("k1");
}
thread::sleep(Duration::from_millis(100));
cache.insert("k4", 4);
assert_eq!(cache.get("k1"), Some(1));
assert_eq!(cache.get("k4"), Some(4));
}
#[test]
fn test_tlru_default_frequency_weight() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(2),
None,
EvictionPolicy::TLRU,
Some(5),
None, None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
for _ in 0..3 {
let _ = cache.get("k1");
}
cache.insert("k3", 3);
assert_eq!(cache.get("k1"), Some(1));
assert_eq!(cache.get("k3"), Some(3));
}
#[test]
fn test_tlru_frequency_weight_comparison() {
static MAP_LOW: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER_LOW: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
static MAP_HIGH: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER_HIGH: Lazy<Mutex<VecDeque<String>>> =
Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS_LOW: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
#[cfg(feature = "stats")]
static STATS_HIGH: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache_low = GlobalCache::new(
&MAP_LOW,
&ORDER_LOW,
Some(2),
None,
EvictionPolicy::TLRU,
Some(10),
Some(0.3), None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS_LOW,
);
let cache_high = GlobalCache::new(
&MAP_HIGH,
&ORDER_HIGH,
Some(2),
None,
EvictionPolicy::TLRU,
Some(10),
Some(2.0), None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS_HIGH,
);
cache_low.insert("k1", 1);
cache_low.insert("k2", 2);
cache_high.insert("k1", 1);
cache_high.insert("k2", 2);
for _ in 0..5 {
let _ = cache_low.get("k1");
let _ = cache_high.get("k1");
}
thread::sleep(Duration::from_millis(50));
cache_low.insert("k3", 3);
cache_high.insert("k3", 3);
assert_eq!(cache_low.get("k3"), Some(3));
assert_eq!(cache_high.get("k3"), Some(3));
}
#[test]
fn test_tlru_no_ttl_with_frequency_weight() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(3),
None,
EvictionPolicy::TLRU,
None, Some(1.5),
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
cache.insert("k3", 3);
for _ in 0..10 {
let _ = cache.get("k1");
}
cache.insert("k4", 4);
assert_eq!(cache.get("k1"), Some(1));
}
#[test]
fn test_tlru_concurrent_with_frequency_weight() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(5),
None,
EvictionPolicy::TLRU,
Some(10),
Some(1.2), None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
let handles: Vec<_> = (0..5)
.map(|i| {
thread::spawn(move || {
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(5),
None,
EvictionPolicy::TLRU,
Some(10),
Some(1.2),
None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
for _ in 0..3 {
let _ = cache.get("k1");
}
cache.insert(&format!("k{}", i + 3), i + 3);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(cache.get("k1"), Some(1));
}
#[test]
fn test_tlru_frequency_weight_edge_cases() {
static MAP: Lazy<RwLock<HashMap<String, CacheEntry<i32>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static ORDER: Lazy<Mutex<VecDeque<String>>> = Lazy::new(|| Mutex::new(VecDeque::new()));
#[cfg(feature = "stats")]
static STATS: Lazy<CacheStats> = Lazy::new(|| CacheStats::new());
let cache = GlobalCache::new(
&MAP,
&ORDER,
Some(2),
None,
EvictionPolicy::TLRU,
Some(5),
Some(0.1), None,
None,
None,
None,
#[cfg(feature = "stats")]
&STATS,
);
cache.insert("k1", 1);
cache.insert("k2", 2);
for _ in 0..100 {
let _ = cache.get("k1");
}
thread::sleep(Duration::from_millis(50));
cache.insert("k3", 3);
assert!(cache.get("k3").is_some());
}
}