use super::entry::CacheEntry;
use std::time::Duration;
use tracing::debug;
#[derive(Debug, Clone)]
pub enum EvictionPolicy {
Lru,
Ttl(Duration),
Size(usize),
AccessCount(u64),
}
pub struct EvictionStrategy {
policy: EvictionPolicy,
memory_limit: usize,
}
impl EvictionStrategy {
pub fn new(policy: EvictionPolicy) -> Self {
Self {
policy,
memory_limit: 500 * 1024 * 1024, }
}
pub fn with_memory_limit(policy: EvictionPolicy, memory_limit: usize) -> Self {
Self {
policy,
memory_limit,
}
}
pub fn policy(&self) -> &EvictionPolicy {
&self.policy
}
pub fn memory_limit(&self) -> usize {
self.memory_limit
}
pub fn should_evict_by_ttl<T>(&self, entry: &CacheEntry<T>, ttl: Duration) -> bool {
if ttl.is_zero() {
return false;
}
entry.is_expired(ttl)
}
pub fn should_evict_by_access<T>(&self, entry: &CacheEntry<T>, min_count: u64) -> bool {
entry.access_count() < min_count
}
pub fn should_evict_by_memory(&self, current_size: usize) -> bool {
current_size >= self.memory_limit
}
}
impl Default for EvictionStrategy {
fn default() -> Self {
Self::new(EvictionPolicy::Lru)
}
}
#[derive(Debug, Default, Clone)]
pub struct EvictionStats {
pub ttl_evictions: u64,
pub size_evictions: u64,
pub access_evictions: u64,
pub lru_evictions: u64,
}
impl EvictionStats {
pub fn new() -> Self {
Self::default()
}
pub fn total_evictions(&self) -> u64 {
self.ttl_evictions + self.size_evictions + self.access_evictions + self.lru_evictions
}
pub fn record_ttl_eviction(&mut self) {
self.ttl_evictions += 1;
debug!("Recorded TTL eviction (total: {})", self.ttl_evictions);
}
pub fn record_size_eviction(&mut self) {
self.size_evictions += 1;
debug!("Recorded size eviction (total: {})", self.size_evictions);
}
pub fn record_access_eviction(&mut self) {
self.access_evictions += 1;
debug!(
"Recorded access eviction (total: {})",
self.access_evictions
);
}
pub fn record_lru_eviction(&mut self) {
self.lru_evictions += 1;
debug!("Recorded LRU eviction (total: {})", self.lru_evictions);
}
pub fn reset(&mut self) {
self.ttl_evictions = 0;
self.size_evictions = 0;
self.access_evictions = 0;
self.lru_evictions = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eviction_strategy_ttl() {
let strategy = EvictionStrategy::new(EvictionPolicy::Ttl(Duration::from_secs(60)));
let entry = CacheEntry::new("test");
assert!(!strategy.should_evict_by_ttl(&entry, Duration::from_secs(60)));
let old_entry = CacheEntry::new("old_test");
std::thread::sleep(Duration::from_secs(1));
assert!(strategy.should_evict_by_ttl(&old_entry, Duration::from_millis(500)));
}
#[test]
fn test_eviction_strategy_access_count() {
let strategy = EvictionStrategy::new(EvictionPolicy::AccessCount(5));
let mut entry = CacheEntry::new("test");
assert!(strategy.should_evict_by_access(&entry, 5));
for _ in 0..5 {
entry.touch();
}
assert!(!strategy.should_evict_by_access(&entry, 5));
}
#[test]
fn test_eviction_strategy_memory() {
let strategy = EvictionStrategy::with_memory_limit(
EvictionPolicy::Size(100 * 1024 * 1024), 100 * 1024 * 1024,
);
assert!(!strategy.should_evict_by_memory(50 * 1024 * 1024));
assert!(strategy.should_evict_by_memory(100 * 1024 * 1024));
assert!(strategy.should_evict_by_memory(150 * 1024 * 1024));
}
#[test]
fn test_eviction_strategy_zero_ttl() {
let strategy = EvictionStrategy::new(EvictionPolicy::Ttl(Duration::from_secs(0)));
let entry = CacheEntry::new("test");
assert!(!strategy.should_evict_by_ttl(&entry, Duration::from_secs(0)));
}
#[test]
fn test_eviction_stats() {
let mut stats = EvictionStats::new();
assert_eq!(stats.total_evictions(), 0);
stats.record_ttl_eviction();
stats.record_size_eviction();
stats.record_access_eviction();
stats.record_lru_eviction();
assert_eq!(stats.total_evictions(), 4);
assert_eq!(stats.ttl_evictions, 1);
assert_eq!(stats.size_evictions, 1);
assert_eq!(stats.access_evictions, 1);
assert_eq!(stats.lru_evictions, 1);
stats.reset();
assert_eq!(stats.total_evictions(), 0);
}
#[test]
fn test_default_eviction_strategy() {
let strategy = EvictionStrategy::default();
assert_eq!(strategy.memory_limit(), 500 * 1024 * 1024); }
}