use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone)]
struct CacheEntry {
data: Vec<u8>,
size: usize,
access_order: u64,
}
#[derive(Debug, Clone)]
pub struct LruCache {
inner: Arc<RwLock<LruCacheInner>>,
}
#[derive(Debug)]
struct LruCacheInner {
entries: HashMap<String, CacheEntry>,
access_queue: VecDeque<String>,
current_size: usize,
max_size: usize,
max_entries: usize,
max_object_size: usize,
access_counter: u64,
hits: u64,
misses: u64,
evictions: u64,
}
impl LruCache {
pub fn new(max_size: usize) -> Self {
Self::with_limits(max_size, 0)
}
pub fn with_limits(max_size: usize, max_entries: usize) -> Self {
Self::with_all_limits(max_size, max_entries, 50 * 1024 * 1024) }
pub fn with_all_limits(max_size: usize, max_entries: usize, max_object_size: usize) -> Self {
Self {
inner: Arc::new(RwLock::new(LruCacheInner {
entries: HashMap::new(),
access_queue: VecDeque::new(),
current_size: 0,
max_size,
max_entries,
max_object_size,
access_counter: 0,
hits: 0,
misses: 0,
evictions: 0,
})),
}
}
pub async fn get(&self, key: &str) -> Option<Vec<u8>> {
let mut inner = self.inner.write().await;
if !inner.entries.contains_key(key) {
inner.misses += 1;
return None;
}
inner.hits += 1;
let data = inner.entries.get(key).map(|e| e.data.clone());
inner.access_counter += 1;
let access_order = inner.access_counter;
if let Some(entry) = inner.entries.get_mut(key) {
entry.access_order = access_order;
}
if let Some(pos) = inner.access_queue.iter().position(|k| k == key) {
inner.access_queue.remove(pos);
}
inner.access_queue.push_back(key.to_string());
data
}
pub async fn put(&self, key: impl Into<String>, data: Vec<u8>) {
let key = key.into();
let size = data.len();
let mut inner = self.inner.write().await;
if size > inner.max_object_size {
return;
}
if let Some(old_entry) = inner.entries.remove(&key) {
inner.current_size -= old_entry.size;
if let Some(pos) = inner.access_queue.iter().position(|k| k == &key) {
inner.access_queue.remove(pos);
}
}
while inner.current_size + size > inner.max_size
|| (inner.max_entries > 0 && inner.entries.len() >= inner.max_entries)
{
if let Some(evict_key) = inner.access_queue.pop_front() {
if let Some(evicted) = inner.entries.remove(&evict_key) {
inner.current_size -= evicted.size;
inner.evictions += 1;
}
} else {
break; }
}
inner.access_counter += 1;
let access_order = inner.access_counter;
inner.entries.insert(
key.clone(),
CacheEntry {
data,
size,
access_order,
},
);
inner.access_queue.push_back(key);
inner.current_size += size;
}
pub async fn contains(&self, key: &str) -> bool {
let inner = self.inner.read().await;
inner.entries.contains_key(key)
}
pub async fn remove(&self, key: &str) -> Option<Vec<u8>> {
let mut inner = self.inner.write().await;
if let Some(entry) = inner.entries.remove(key) {
inner.current_size -= entry.size;
if let Some(pos) = inner.access_queue.iter().position(|k| k == key) {
inner.access_queue.remove(pos);
}
Some(entry.data)
} else {
None
}
}
pub async fn clear(&self) {
let mut inner = self.inner.write().await;
inner.entries.clear();
inner.access_queue.clear();
inner.current_size = 0;
inner.access_counter = 0;
}
pub async fn stats(&self) -> CacheStats {
let inner = self.inner.read().await;
let total_accesses = inner.hits + inner.misses;
let hit_rate = if total_accesses > 0 {
inner.hits as f64 / total_accesses as f64
} else {
0.0
};
CacheStats {
entry_count: inner.entries.len(),
total_size: inner.current_size,
max_size: inner.max_size,
max_entries: inner.max_entries,
max_object_size: inner.max_object_size,
hits: inner.hits,
misses: inner.misses,
evictions: inner.evictions,
hit_rate,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CacheStats {
pub entry_count: usize,
pub total_size: usize,
pub max_size: usize,
pub max_entries: usize,
pub max_object_size: usize,
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub hit_rate: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_basic_get_put() {
let cache = LruCache::new(1024);
cache.put("key1", vec![1, 2, 3]).await;
assert_eq!(cache.get("key1").await, Some(vec![1, 2, 3]));
assert_eq!(cache.get("key2").await, None);
}
#[tokio::test]
async fn test_size_eviction() {
let cache = LruCache::new(10);
cache.put("key1", vec![1, 2, 3, 4]).await; cache.put("key2", vec![5, 6, 7, 8]).await; cache.put("key3", vec![9, 10, 11]).await;
assert_eq!(cache.get("key1").await, None);
assert_eq!(cache.get("key2").await, Some(vec![5, 6, 7, 8]));
assert_eq!(cache.get("key3").await, Some(vec![9, 10, 11]));
}
#[tokio::test]
async fn test_count_eviction() {
let cache = LruCache::with_limits(1024, 2);
cache.put("key1", vec![1]).await;
cache.put("key2", vec![2]).await;
cache.put("key3", vec![3]).await;
assert_eq!(cache.get("key1").await, None);
assert_eq!(cache.get("key2").await, Some(vec![2]));
assert_eq!(cache.get("key3").await, Some(vec![3]));
}
#[tokio::test]
async fn test_lru_ordering() {
let cache = LruCache::with_limits(1024, 2);
cache.put("key1", vec![1]).await;
cache.put("key2", vec![2]).await;
let _ = cache.get("key1").await;
cache.put("key3", vec![3]).await;
assert_eq!(cache.get("key1").await, Some(vec![1]));
assert_eq!(cache.get("key2").await, None);
assert_eq!(cache.get("key3").await, Some(vec![3]));
}
#[tokio::test]
async fn test_update_existing() {
let cache = LruCache::new(1024);
cache.put("key1", vec![1, 2, 3]).await;
cache.put("key1", vec![4, 5, 6]).await;
assert_eq!(cache.get("key1").await, Some(vec![4, 5, 6]));
}
#[tokio::test]
async fn test_remove() {
let cache = LruCache::new(1024);
cache.put("key1", vec![1, 2, 3]).await;
assert_eq!(cache.remove("key1").await, Some(vec![1, 2, 3]));
assert_eq!(cache.get("key1").await, None);
}
#[tokio::test]
async fn test_clear() {
let cache = LruCache::new(1024);
cache.put("key1", vec![1]).await;
cache.put("key2", vec![2]).await;
cache.clear().await;
assert_eq!(cache.get("key1").await, None);
assert_eq!(cache.get("key2").await, None);
}
#[tokio::test]
async fn test_stats() {
let cache = LruCache::new(1024);
cache.put("key1", vec![1, 2, 3]).await;
cache.put("key2", vec![4, 5]).await;
let stats = cache.stats().await;
assert_eq!(stats.entry_count, 2);
assert_eq!(stats.total_size, 5);
assert_eq!(stats.max_size, 1024);
}
#[tokio::test]
async fn test_concurrent_access() {
use tokio::task;
let cache = Arc::new(LruCache::new(1024 * 1024));
let mut handles = vec![];
for i in 0..10 {
let cache = cache.clone();
handles.push(task::spawn(async move {
for j in 0..100 {
let key = format!("key_{}_{}", i, j);
let data = vec![i as u8; 100];
cache.put(key, data).await;
}
}));
}
for i in 0..10 {
let cache = cache.clone();
handles.push(task::spawn(async move {
for j in 0..100 {
let key = format!("key_{}_{}", i, j);
let _ = cache.get(&key).await;
}
}));
}
for handle in handles {
handle.await.unwrap();
}
let stats = cache.stats().await;
assert!(stats.entry_count > 0);
}
#[tokio::test]
async fn test_cache_metrics_hits_misses() {
let cache = LruCache::new(1024);
let stats = cache.stats().await;
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 0);
assert_eq!(cache.get("nonexistent").await, None);
let stats = cache.stats().await;
assert_eq!(stats.misses, 1);
assert_eq!(stats.hits, 0);
cache.put("key1", vec![1, 2, 3]).await;
assert!(cache.get("key1").await.is_some());
let stats = cache.stats().await;
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 1);
let _ = cache.get("key1").await;
let stats = cache.stats().await;
assert_eq!(stats.hits, 2);
assert_eq!(stats.misses, 1);
}
#[tokio::test]
async fn test_cache_hit_rate() {
let cache = LruCache::new(1024);
let stats = cache.stats().await;
assert_eq!(stats.hit_rate, 0.0);
cache.put("key1", vec![1, 2, 3]).await;
let _ = cache.get("key1").await; let _ = cache.get("key1").await; let _ = cache.get("missing").await;
let stats = cache.stats().await;
assert!(stats.hit_rate > 0.6);
assert!(stats.hit_rate < 0.7);
}
#[tokio::test]
async fn test_cache_eviction_count() {
let cache = LruCache::with_limits(10, 2);
let stats = cache.stats().await;
assert_eq!(stats.evictions, 0);
cache.put("key1", vec![1]).await;
cache.put("key2", vec![2]).await;
cache.put("key3", vec![3]).await;
let stats = cache.stats().await;
assert_eq!(stats.evictions, 1);
cache.put("key4", vec![4]).await;
let stats = cache.stats().await;
assert_eq!(stats.evictions, 2);
}
#[tokio::test]
async fn test_skip_large_objects() {
let cache = LruCache::with_all_limits(1024, 10, 100);
let small_data = vec![1u8; 50];
cache.put("small", small_data.clone()).await;
assert_eq!(cache.get("small").await, Some(small_data));
let large_data = vec![2u8; 150];
cache.put("large", large_data).await;
assert_eq!(cache.get("large").await, None);
let stats = cache.stats().await;
assert_eq!(stats.entry_count, 1);
assert_eq!(stats.total_size, 50);
}
#[tokio::test]
async fn test_max_object_size_threshold() {
let cache = LruCache::with_limits(100 * 1024 * 1024, 100);
let stats = cache.stats().await;
assert_eq!(stats.max_object_size, 50 * 1024 * 1024);
}
}