use ahash::AHasher;
use ahash::HashMap;
use parking_lot::RwLock;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::time::Instant;
#[derive(Clone)]
struct CacheEntry<V> {
value: V,
last_access: Instant,
}
pub struct LruCache<V: Clone> {
entries: RwLock<HashMap<u64, CacheEntry<V>>>,
capacity: usize,
}
#[allow(dead_code)]
impl<V: Clone> LruCache<V> {
pub fn new(capacity: usize) -> Self {
Self {
entries: RwLock::new(HashMap::default()),
capacity,
}
}
pub fn hash_key<K: Hash>(key: &K) -> u64 {
let mut hasher = AHasher::default();
key.hash(&mut hasher);
hasher.finish()
}
pub fn get(&self, key_hash: u64) -> Option<V> {
{
let entries = self.entries.read();
if let Some(entry) = entries.get(&key_hash) {
return Some(entry.value.clone());
}
}
None
}
pub fn get_and_touch(&self, key_hash: u64) -> Option<V> {
let mut entries = self.entries.write();
if let Some(entry) = entries.get_mut(&key_hash) {
entry.last_access = Instant::now();
return Some(entry.value.clone());
}
None
}
pub fn insert(&self, key_hash: u64, value: V) {
let mut entries = self.entries.write();
if entries.len() >= self.capacity && !entries.contains_key(&key_hash) {
self.evict_oldest_locked(&mut entries);
}
entries.insert(
key_hash,
CacheEntry {
value,
last_access: Instant::now(),
},
);
}
fn evict_oldest_locked(&self, entries: &mut HashMap<u64, CacheEntry<V>>) {
if let Some((&oldest_key, _)) = entries.iter().min_by_key(|(_, entry)| entry.last_access) {
entries.remove(&oldest_key);
log::trace!("Evicted oldest cache entry (hash: {})", oldest_key);
}
}
pub fn remove(&self, key_hash: u64) -> Option<V> {
let mut entries = self.entries.write();
entries.remove(&key_hash).map(|e| e.value)
}
pub fn len(&self) -> usize {
self.entries.read().len()
}
pub fn is_empty(&self) -> bool {
self.entries.read().is_empty()
}
pub fn clear(&self) {
self.entries.write().clear();
}
pub fn capacity(&self) -> usize {
self.capacity
}
}
pub type SharedLruCache<V> = Arc<LruCache<V>>;
pub fn new_shared_cache<V: Clone>(capacity: usize) -> SharedLruCache<V> {
Arc::new(LruCache::new(capacity))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lru_cache_basic() {
let cache: LruCache<String> = LruCache::new(2);
let key1 = LruCache::<String>::hash_key(&"key1");
let key2 = LruCache::<String>::hash_key(&"key2");
let key3 = LruCache::<String>::hash_key(&"key3");
cache.insert(key1, "value1".to_string());
cache.insert(key2, "value2".to_string());
assert_eq!(cache.len(), 2);
assert_eq!(cache.get(key1), Some("value1".to_string()));
cache.insert(key3, "value3".to_string());
assert_eq!(cache.len(), 2);
}
#[test]
fn test_lru_cache_eviction() {
let cache: LruCache<i32> = LruCache::new(2);
cache.insert(1, 100);
std::thread::sleep(std::time::Duration::from_millis(10));
cache.insert(2, 200);
cache.get_and_touch(1);
std::thread::sleep(std::time::Duration::from_millis(10));
cache.insert(3, 300);
assert!(cache.get(1).is_some());
assert!(cache.get(2).is_none()); assert!(cache.get(3).is_some());
}
}