Skip to content

Commit 547d705

Browse files
committed
Add FIFO cache implementation with metrics support
- Introduced a FIFO cache that evicts the oldest items when capacity is reached, providing predictable eviction behavior. - Implemented two versions: one with metrics tracking and another without, allowing for flexible usage based on feature flags. - Added comprehensive documentation detailing architecture, eviction flow, and performance characteristics. - Included tests for core operations and stale entry handling to ensure reliability and correctness of the cache behavior.
1 parent ec3a0a3 commit 547d705

3 files changed

Lines changed: 1781 additions & 2 deletions

File tree

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@
260260
//! ## Thread Safety
261261
//!
262262
//! - `FIFOCache` is **NOT thread-safe**
263-
//! - Wrap in `Arc<RwLock<FIFOCache>>` for concurrent access
263+
//! - Use `ConcurrentFIFOCache` for thread-safe access
264264
//! - Designed for single-threaded use with external synchronization
265265
//! - Follows Rust's zero-cost abstractions principle
266266
//!
@@ -273,7 +273,8 @@
273273
274274
use crate::metrics::metrics_impl::CacheMetrics;
275275
use crate::metrics::snapshot::CacheMetricsSnapshot;
276-
use crate::traits::{CoreCache, FIFOCacheTrait};
276+
use crate::traits::{ConcurrentCache, CoreCache, FIFOCacheTrait};
277+
use parking_lot::RwLock;
277278
use std::collections::{HashMap, VecDeque, hash_map};
278279
use std::fmt::Debug;
279280
use std::hash::Hash;
@@ -427,6 +428,101 @@ where
427428
}
428429
}
429430

431+
/// Thread-safe FIFO cache wrapper using RwLock.
432+
#[derive(Clone, Debug)]
433+
pub struct ConcurrentFIFOCache<K, V>
434+
where
435+
K: Eq + Hash,
436+
{
437+
inner: Arc<RwLock<FIFOCache<K, V>>>,
438+
}
439+
440+
impl<K, V> ConcurrentFIFOCache<K, V>
441+
where
442+
K: Eq + Hash + Debug,
443+
V: Debug,
444+
{
445+
pub fn new(capacity: usize) -> Self {
446+
Self {
447+
inner: Arc::new(RwLock::new(FIFOCache::new(capacity))),
448+
}
449+
}
450+
451+
pub fn insert(&self, key: K, value: V) -> Option<V> {
452+
let mut cache = self.inner.write();
453+
cache.insert(key, value)
454+
}
455+
456+
pub fn get(&self, key: &K) -> Option<V>
457+
where
458+
V: Clone,
459+
{
460+
let mut cache = self.inner.write();
461+
cache.get(key).cloned()
462+
}
463+
464+
pub fn contains(&self, key: &K) -> bool {
465+
let cache = self.inner.read();
466+
cache.contains(key)
467+
}
468+
469+
pub fn len(&self) -> usize {
470+
let cache = self.inner.read();
471+
cache.len()
472+
}
473+
474+
pub fn is_empty(&self) -> bool {
475+
let cache = self.inner.read();
476+
cache.is_empty()
477+
}
478+
479+
pub fn capacity(&self) -> usize {
480+
let cache = self.inner.read();
481+
cache.capacity()
482+
}
483+
484+
pub fn clear(&self) {
485+
let mut cache = self.inner.write();
486+
cache.clear();
487+
}
488+
489+
pub fn pop_oldest(&self) -> Option<(K, V)> {
490+
let mut cache = self.inner.write();
491+
cache.pop_oldest()
492+
}
493+
494+
pub fn peek_oldest(&self) -> Option<(K, V)>
495+
where
496+
K: Clone,
497+
V: Clone,
498+
{
499+
let cache = self.inner.read();
500+
cache.peek_oldest().map(|(k, v)| (k.clone(), v.clone()))
501+
}
502+
503+
pub fn pop_oldest_batch(&self, count: usize) -> Vec<(K, V)> {
504+
let mut cache = self.inner.write();
505+
cache.pop_oldest_batch(count)
506+
}
507+
508+
pub fn age_rank(&self, key: &K) -> Option<usize> {
509+
let cache = self.inner.read();
510+
cache.age_rank(key)
511+
}
512+
513+
pub fn metrics_snapshot(&self) -> CacheMetricsSnapshot {
514+
let cache = self.inner.read();
515+
cache.metrics_snapshot()
516+
}
517+
}
518+
519+
impl<K, V> ConcurrentCache for ConcurrentFIFOCache<K, V>
520+
where
521+
K: Eq + Hash + Debug + Send + Sync,
522+
V: Debug + Send + Sync,
523+
{
524+
}
525+
430526
impl<K, V> CoreCache<K, V> for FIFOCache<K, V>
431527
where
432528
K: Eq + Hash,

0 commit comments

Comments
 (0)