use crate::policy::AdmissionDecision;
use super::CachePolicy;
use parking_lot::Mutex;
use std::collections::{HashMap, VecDeque};
use std::hash::Hash;
#[derive(Clone, Copy, Debug)]
struct SieveEntry {
cost: u64,
visited: bool,
}
#[derive(Debug)]
pub struct SievePolicy<K> {
order: Mutex<VecDeque<K>>,
items: Mutex<HashMap<K, SieveEntry>>,
hand: Mutex<usize>,
}
impl<K> SievePolicy<K> {
pub fn new() -> Self {
Self {
order: Mutex::new(VecDeque::new()),
items: Mutex::new(HashMap::new()),
hand: Mutex::new(0),
}
}
}
impl<K, V> CachePolicy<K, V> for SievePolicy<K>
where
K: Eq + Hash + Clone + Send + Sync,
V: Send + Sync,
{
fn on_access(&self, key: &K, cost: u64) {
let mut items = self.items.lock();
if let Some(entry) = items.get_mut(key) {
entry.visited = true;
}
}
fn on_admit(&self, key: &K, cost: u64) -> AdmissionDecision<K> {
let mut order = self.order.lock();
let mut items = self.items.lock();
if items.contains_key(key) {
order.retain(|k| k != key);
}
items.insert(
key.clone(),
SieveEntry {
cost,
visited: false,
},
);
order.push_front(key.clone());
AdmissionDecision::Admit }
fn on_remove(&self, key: &K) {
let mut order = self.order.lock();
let mut items = self.items.lock();
items.remove(key);
order.retain(|k| k != key);
let mut hand = self.hand.lock();
if *hand >= order.len() {
*hand = 0;
}
}
fn evict(&self, mut cost_to_free: u64) -> (Vec<K>, u64) {
let mut victims = Vec::new();
let mut total_cost_freed = 0;
let mut order = self.order.lock();
let mut items = self.items.lock();
let mut hand = self.hand.lock();
while cost_to_free > 0 && !order.is_empty() {
let mut found_victim = false;
while *hand < order.len() {
let index = order.len() - 1 - *hand;
let key = &order[index];
if let Some(entry) = items.get_mut(key) {
if !entry.visited {
let key_to_evict = order.remove(index).unwrap();
let cost = items.remove(&key_to_evict).unwrap().cost;
cost_to_free = cost_to_free.saturating_sub(cost);
total_cost_freed += cost;
victims.push(key_to_evict);
found_victim = true;
break;
} else {
entry.visited = false;
*hand += 1;
}
} else {
*hand += 1;
}
}
if !found_victim {
*hand = 0;
}
if !found_victim && cost_to_free > 0 && !order.is_empty() {
let lru_index = order.len() - 1;
let key_to_evict = order.remove(lru_index).unwrap();
let cost = items.remove(&key_to_evict).unwrap().cost;
cost_to_free = cost_to_free.saturating_sub(cost);
total_cost_freed += cost;
victims.push(key_to_evict);
*hand = 0;
}
}
(victims, total_cost_freed)
}
fn clear(&self) {
self.order.lock().clear();
self.items.lock().clear();
*self.hand.lock() = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_item_admitted() {
let policy = SievePolicy::<i32>::new();
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &2, 1);
let items = policy.items.lock();
let order = policy.order.lock();
assert!(items.contains_key(&1));
assert!(items.contains_key(&2));
assert!(
!items.get(&1).unwrap().visited,
"New item should not be visited"
);
assert!(!items.get(&2).unwrap().visited);
assert_eq!(*order, vec![2, 1]);
}
#[test]
fn test_access_sets_visited_bit() {
let policy = SievePolicy::<i32>::new();
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1);
assert!(!policy.items.lock().get(&1).unwrap().visited);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_access(&policy, &1, 1);
assert!(
policy.items.lock().get(&1).unwrap().visited,
"Accessed item's visited bit should be true"
);
assert_eq!(*policy.order.lock(), vec![1]);
}
#[test]
fn test_re_admit_moves_to_front() {
let policy = SievePolicy::<i32>::new();
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &2, 1);
assert_eq!(*policy.order.lock(), vec![2, 1]);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1);
assert_eq!(*policy.order.lock(), vec![1, 2]);
}
#[test]
fn test_evict_unvisited_item() {
let policy = SievePolicy::<i32>::new();
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1); <SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &2, 1);
let (victims, cost_freed) = <SievePolicy<i32> as CachePolicy<i32, ()>>::evict(&policy, 1);
assert_eq!(victims, vec![1]);
assert_eq!(cost_freed, 1);
assert!(!policy.items.lock().contains_key(&1));
assert_eq!(*policy.order.lock(), vec![2]);
assert_eq!(
*policy.hand.lock(),
0,
"Hand should not advance after eviction"
);
}
#[test]
fn test_evict_spares_visited_item() {
let policy = SievePolicy::<i32>::new();
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1); <SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &2, 1);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_access(&policy, &1, 1);
let (victims, cost_freed) = <SievePolicy<i32> as CachePolicy<i32, ()>>::evict(&policy, 1);
assert_eq!(victims, vec![2]);
assert_eq!(cost_freed, 1);
let items = policy.items.lock();
assert!(items.contains_key(&1));
assert!(
!items.get(&1).unwrap().visited,
"Item 1's visited bit should be cleared"
);
assert_eq!(
*policy.hand.lock(),
1,
"Hand should have advanced past item 1"
);
}
#[test]
fn test_evict_resets_hand_after_full_sweep() {
let policy = SievePolicy::<i32>::new();
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &2, 1);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_access(&policy, &1, 1);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_access(&policy, &2, 1);
let (victims, cost_freed) = <SievePolicy<i32> as CachePolicy<i32, ()>>::evict(&policy, 0);
assert!(victims.is_empty());
assert_eq!(
*policy.hand.lock(),
0,
"Hand should reset after a full sweep with no evictions"
);
let (victims2, _) = <SievePolicy<i32> as CachePolicy<i32, ()>>::evict(&policy, 1);
assert_eq!(victims2, vec![1]);
}
#[test]
fn test_on_remove_cleans_up_state() {
let policy = SievePolicy::<i32>::new();
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &2, 1);
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_remove(&policy, &1);
let items = policy.items.lock();
let order = policy.order.lock();
assert!(!items.contains_key(&1));
assert_eq!(*order, vec![2]);
}
#[test]
fn test_clear_resets_state() {
let policy = SievePolicy::<i32>::new();
<SievePolicy<i32> as CachePolicy<i32, ()>>::on_admit(&policy, &1, 1);
*policy.hand.lock() = 5;
<SievePolicy<i32> as CachePolicy<i32, ()>>::clear(&policy);
assert!(policy.items.lock().is_empty());
assert!(policy.order.lock().is_empty());
assert_eq!(*policy.hand.lock(), 0);
}
}