use ferrum_types::RequestId;
use std::collections::{HashMap, VecDeque};
use std::time::Instant;
pub trait EvictionPolicy: Send + Sync + std::fmt::Debug {
fn record_access(&mut self, request_id: RequestId);
fn select_eviction_candidates(&mut self, count: usize) -> Vec<RequestId>;
fn remove_request(&mut self, request_id: RequestId);
fn name(&self) -> &str;
fn clear(&mut self);
}
#[derive(Debug)]
pub struct LRUEviction {
access_order: VecDeque<RequestId>,
access_times: HashMap<RequestId, Instant>,
}
impl LRUEviction {
pub fn new() -> Self {
Self {
access_order: VecDeque::new(),
access_times: HashMap::new(),
}
}
}
impl Default for LRUEviction {
fn default() -> Self {
Self::new()
}
}
impl EvictionPolicy for LRUEviction {
fn record_access(&mut self, request_id: RequestId) {
if let Some(pos) = self.access_order.iter().position(|id| *id == request_id) {
self.access_order.remove(pos);
}
self.access_order.push_back(request_id.clone());
self.access_times.insert(request_id, Instant::now());
}
fn select_eviction_candidates(&mut self, count: usize) -> Vec<RequestId> {
self.access_order.iter().take(count).cloned().collect()
}
fn remove_request(&mut self, request_id: RequestId) {
if let Some(pos) = self.access_order.iter().position(|id| *id == request_id) {
self.access_order.remove(pos);
}
self.access_times.remove(&request_id);
}
fn name(&self) -> &str {
"LRU"
}
fn clear(&mut self) {
self.access_order.clear();
self.access_times.clear();
}
}
#[derive(Debug)]
pub struct FIFOEviction {
arrival_order: VecDeque<RequestId>,
arrival_times: HashMap<RequestId, Instant>,
}
impl FIFOEviction {
pub fn new() -> Self {
Self {
arrival_order: VecDeque::new(),
arrival_times: HashMap::new(),
}
}
}
impl Default for FIFOEviction {
fn default() -> Self {
Self::new()
}
}
impl EvictionPolicy for FIFOEviction {
fn record_access(&mut self, request_id: RequestId) {
if !self.arrival_times.contains_key(&request_id) {
self.arrival_order.push_back(request_id.clone());
self.arrival_times.insert(request_id, Instant::now());
}
}
fn select_eviction_candidates(&mut self, count: usize) -> Vec<RequestId> {
self.arrival_order.iter().take(count).cloned().collect()
}
fn remove_request(&mut self, request_id: RequestId) {
if let Some(pos) = self.arrival_order.iter().position(|id| *id == request_id) {
self.arrival_order.remove(pos);
}
self.arrival_times.remove(&request_id);
}
fn name(&self) -> &str {
"FIFO"
}
fn clear(&mut self) {
self.arrival_order.clear();
self.arrival_times.clear();
}
}
#[derive(Debug)]
pub struct ClockEviction {
requests: Vec<RequestId>,
reference_bits: HashMap<RequestId, bool>,
clock_hand: usize,
}
impl ClockEviction {
pub fn new() -> Self {
Self {
requests: Vec::new(),
reference_bits: HashMap::new(),
clock_hand: 0,
}
}
}
impl Default for ClockEviction {
fn default() -> Self {
Self::new()
}
}
impl EvictionPolicy for ClockEviction {
fn record_access(&mut self, request_id: RequestId) {
self.reference_bits.insert(request_id.clone(), true);
if !self.requests.contains(&request_id) {
self.requests.push(request_id);
}
}
fn select_eviction_candidates(&mut self, count: usize) -> Vec<RequestId> {
let mut candidates = Vec::new();
let mut attempts = 0;
let max_attempts = self.requests.len() * 2;
while candidates.len() < count && attempts < max_attempts {
if self.requests.is_empty() {
break;
}
let request_id = self.requests[self.clock_hand].clone();
let referenced = self
.reference_bits
.get(&request_id)
.copied()
.unwrap_or(false);
if referenced {
self.reference_bits.insert(request_id, false);
} else {
candidates.push(request_id.clone());
}
self.clock_hand = (self.clock_hand + 1) % self.requests.len();
attempts += 1;
}
candidates
}
fn remove_request(&mut self, request_id: RequestId) {
if let Some(pos) = self.requests.iter().position(|id| *id == request_id) {
self.requests.remove(pos);
if self.clock_hand > pos {
self.clock_hand -= 1;
} else if self.clock_hand >= self.requests.len() && !self.requests.is_empty() {
self.clock_hand = 0;
}
}
self.reference_bits.remove(&request_id);
}
fn name(&self) -> &str {
"Clock"
}
fn clear(&mut self) {
self.requests.clear();
self.reference_bits.clear();
self.clock_hand = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lru_eviction() {
let mut policy = LRUEviction::new();
let req1 = RequestId::new();
let req2 = RequestId::new();
let req3 = RequestId::new();
policy.record_access(req1.clone());
policy.record_access(req2.clone());
policy.record_access(req3);
let candidates = policy.select_eviction_candidates(1);
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0], req1);
policy.record_access(req1.clone());
let candidates = policy.select_eviction_candidates(1);
assert_eq!(candidates[0], req2);
}
#[test]
fn test_fifo_eviction() {
let mut policy = FIFOEviction::new();
let req1 = RequestId::new();
let req2 = RequestId::new();
let req3 = RequestId::new();
policy.record_access(req1.clone());
policy.record_access(req2.clone());
policy.record_access(req3);
let candidates = policy.select_eviction_candidates(1);
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0], req1);
policy.record_access(req1.clone());
let candidates = policy.select_eviction_candidates(1);
assert_eq!(candidates[0], req1);
}
#[test]
fn test_clock_eviction() {
let mut policy = ClockEviction::new();
let req1 = RequestId::new();
let req2 = RequestId::new();
policy.record_access(req1.clone());
policy.record_access(req2.clone());
let candidates = policy.select_eviction_candidates(1);
assert_eq!(candidates.len(), 1);
}
#[test]
fn test_policy_removal() {
let mut policy = LRUEviction::new();
let req1 = RequestId::new();
let req2 = RequestId::new();
policy.record_access(req1.clone());
policy.record_access(req2.clone());
policy.remove_request(req1);
let candidates = policy.select_eviction_candidates(1);
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0], req2);
}
#[test]
fn test_policy_clear() {
let mut policy = LRUEviction::new();
let req1 = RequestId::new();
policy.record_access(req1.clone());
policy.clear();
let candidates = policy.select_eviction_candidates(1);
assert_eq!(candidates.len(), 0);
}
}