use crate::application::ports::{EvictionCandidate, EvictionPolicy};
use crate::infrastructure::eviction::priority::PriorityFn;
#[derive(Clone)]
pub struct PriorityWithMemoryEviction<K, V>
where
K: Clone,
{
max_entries: usize,
priority_fn: PriorityFn<K, V>,
max_bytes: usize,
}
impl<K, V> PriorityWithMemoryEviction<K, V>
where
K: Clone,
{
pub fn new(max_entries: usize, priority_fn: PriorityFn<K, V>, max_bytes: usize) -> Self {
Self {
max_entries,
priority_fn,
max_bytes,
}
}
pub fn max_bytes(&self) -> usize {
self.max_bytes
}
}
impl<K, V> EvictionPolicy<K, V> for PriorityWithMemoryEviction<K, V>
where
K: Clone,
V: Clone,
{
fn select_victim(&self, candidates: &[EvictionCandidate<K, V>]) -> Option<K> {
candidates
.iter()
.map(|candidate| {
let priority = (self.priority_fn)(&candidate.key, &candidate.value);
(candidate, priority)
})
.min_by_key(|(_, priority)| *priority)
.map(|(candidate, _)| candidate.key.clone())
}
fn should_evict(&self, current_entries: usize, current_memory_bytes: usize) -> bool {
current_entries >= self.max_entries || current_memory_bytes >= self.max_bytes
}
fn tracks_memory(&self) -> bool {
true
}
fn estimate_entry_size(&self, _key: &K, _value: &V) -> usize {
let base_size = std::mem::size_of::<K>() + std::mem::size_of::<V>() + 64;
let estimated_heap = 200;
base_size + estimated_heap
}
}
impl<K, V> std::fmt::Debug for PriorityWithMemoryEviction<K, V>
where
K: Clone,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PriorityWithMemoryEviction")
.field("max_entries", &self.max_entries)
.field("priority_fn", &"<fn>")
.field("max_bytes", &self.max_bytes)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::time::Instant;
#[test]
fn test_priority_with_memory_select_lowest_priority() {
let priority_fn = Arc::new(|_key: &String, value: &i32| *value as u32);
let policy = PriorityWithMemoryEviction::new(10, priority_fn, 1000);
let now = Instant::now();
let candidates = vec![
EvictionCandidate {
key: "key1".to_string(),
value: 100,
last_access: now,
},
EvictionCandidate {
key: "key2".to_string(),
value: 50,
last_access: now,
},
EvictionCandidate {
key: "key3".to_string(),
value: 200,
last_access: now,
},
];
let victim = policy.select_victim(&candidates);
assert_eq!(victim, Some("key2".to_string())); }
#[test]
fn test_priority_with_memory_should_evict_by_entries() {
let priority_fn = Arc::new(|_key: &String, value: &i32| *value as u32);
let policy = PriorityWithMemoryEviction::new(100, priority_fn, 10000);
assert!(!policy.should_evict(99, 500));
assert!(policy.should_evict(100, 500));
assert!(policy.should_evict(101, 500));
}
#[test]
fn test_priority_with_memory_should_evict_by_memory() {
let priority_fn = Arc::new(|_key: &String, value: &i32| *value as u32);
let policy = PriorityWithMemoryEviction::new(100, priority_fn, 1000);
assert!(!policy.should_evict(50, 999));
assert!(policy.should_evict(50, 1000));
assert!(policy.should_evict(50, 1001));
}
#[test]
fn test_priority_with_memory_tracks_memory() {
let priority_fn = Arc::new(|_key: &String, value: &i32| *value as u32);
let policy = PriorityWithMemoryEviction::new(100, priority_fn, 1000);
assert!(policy.tracks_memory());
}
}