use super::Replacer; use crate::buffer::FrameId;
use crate::error::{QuillSQLError, QuillSQLResult};
use std::collections::HashMap;
use std::time::{Duration, Instant};
const DEFAULT_WINDOW_DURATION: Duration = Duration::from_secs(10);
#[derive(Debug, Clone)]
struct WindowLFUNode {
frequency: u64,
last_access_time: Instant,
is_evictable: bool,
}
impl WindowLFUNode {
fn new() -> Self {
Self {
frequency: 1, last_access_time: Instant::now(),
is_evictable: false, }
}
}
#[derive(Debug)]
pub struct WindowLFUReplacer {
node_store: HashMap<FrameId, WindowLFUNode>,
capacity: usize,
current_size: usize, window_duration: Duration,
}
impl Replacer for WindowLFUReplacer {
fn new(capacity: usize) -> Self {
Self::with_window(capacity, DEFAULT_WINDOW_DURATION)
}
fn record_access(&mut self, frame_id: FrameId) -> QuillSQLResult<()> {
let now = Instant::now();
if let Some(node) = self.node_store.get_mut(&frame_id) {
node.frequency += 1;
node.last_access_time = now;
} else {
if self.node_store.len() >= self.capacity {
return Err(QuillSQLError::Internal(format!(
"WindowLFU capacity {} reached when accessing new frame {}",
self.capacity, frame_id
)));
}
self.node_store.insert(frame_id, WindowLFUNode::new());
}
Ok(())
}
fn evict(&mut self) -> Option<FrameId> {
if self.current_size == 0 {
return None;
}
let now = Instant::now();
let mut victim_frame = None;
let mut min_priority: Option<(u64, Instant)> = None;
for (&frame_id, node) in self.node_store.iter() {
if !node.is_evictable {
continue;
}
let effective_frequency =
if now.duration_since(node.last_access_time) <= self.window_duration {
node.frequency
} else {
0
};
let current_priority = (effective_frequency, node.last_access_time);
match min_priority {
None => {
min_priority = Some(current_priority);
victim_frame = Some(frame_id);
}
Some(min_p) => {
if effective_frequency < min_p.0
|| (effective_frequency == min_p.0 && node.last_access_time < min_p.1)
{
min_priority = Some(current_priority);
victim_frame = Some(frame_id);
}
}
}
}
if let Some(victim_id) = victim_frame {
self.remove(victim_id); Some(victim_id)
} else {
None }
}
fn set_evictable(&mut self, frame_id: FrameId, set_evictable: bool) -> QuillSQLResult<()> {
if let Some(node) = self.node_store.get_mut(&frame_id) {
let was_evictable = node.is_evictable;
node.is_evictable = set_evictable;
if set_evictable && !was_evictable {
self.current_size += 1;
} else if !set_evictable && was_evictable {
if self.current_size > 0 {
self.current_size -= 1;
} else {
eprintln!("Warning: Attempted to decrease WindowLFUReplacer size below zero for frame {}", frame_id);
}
}
Ok(())
} else {
Err(QuillSQLError::Internal(format!(
"Frame {} not found in WindowLFUReplacer::set_evictable",
frame_id
)))
}
}
fn remove(&mut self, frame_id: FrameId) {
if let Some(node) = self.node_store.remove(&frame_id) {
if node.is_evictable {
if self.current_size > 0 {
self.current_size -= 1;
} else {
eprintln!("Warning: Attempted to decrease WindowLFUReplacer size below zero during remove for frame {}", frame_id);
}
}
}
}
fn size(&self) -> usize {
self.current_size
}
}
impl WindowLFUReplacer {
pub fn with_window(capacity: usize, window_duration: Duration) -> Self {
Self {
node_store: HashMap::with_capacity(capacity),
capacity,
current_size: 0,
window_duration,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
#[test]
fn test_window_lfu_basic_eviction() {
let mut replacer = WindowLFUReplacer::new(3);
replacer.record_access(1).unwrap(); replacer.record_access(2).unwrap(); replacer.record_access(3).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(2, true).unwrap();
replacer.set_evictable(3, true).unwrap();
assert_eq!(replacer.size(), 3);
assert_eq!(replacer.evict(), Some(1));
assert_eq!(replacer.size(), 2);
assert_eq!(replacer.evict(), Some(2));
assert_eq!(replacer.size(), 1);
assert_eq!(replacer.evict(), Some(3));
assert_eq!(replacer.size(), 0);
assert_eq!(replacer.evict(), None);
}
#[test]
fn test_window_lfu_frequency_effect() {
let mut replacer = WindowLFUReplacer::new(3);
replacer.record_access(1).unwrap(); replacer.record_access(2).unwrap(); replacer.record_access(3).unwrap();
replacer.record_access(1).unwrap(); replacer.record_access(1).unwrap(); replacer.record_access(2).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(2, true).unwrap();
replacer.set_evictable(3, true).unwrap();
assert_eq!(replacer.evict(), Some(3));
assert_eq!(replacer.evict(), Some(2));
assert_eq!(replacer.evict(), Some(1));
}
#[test]
fn test_window_lfu_window_effect() {
let window = Duration::from_millis(50);
let mut replacer = WindowLFUReplacer::with_window(3, window);
replacer.record_access(1).unwrap(); sleep(window / 2);
replacer.record_access(2).unwrap(); replacer.record_access(2).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(2, true).unwrap();
sleep(window * 2);
replacer.record_access(3).unwrap(); replacer.set_evictable(3, true).unwrap();
assert_eq!(replacer.evict(), Some(1));
assert_eq!(replacer.evict(), Some(2));
assert_eq!(replacer.evict(), Some(3));
}
#[test]
fn test_window_lfu_set_evictable() {
let mut replacer = WindowLFUReplacer::new(3);
replacer.record_access(1).unwrap();
replacer.record_access(2).unwrap();
replacer.record_access(3).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(3, true).unwrap();
assert_eq!(replacer.size(), 2);
assert_eq!(replacer.evict(), Some(1));
assert_eq!(replacer.size(), 1);
replacer.set_evictable(2, true).unwrap();
replacer.set_evictable(3, false).unwrap();
assert_eq!(replacer.size(), 1);
assert_eq!(replacer.evict(), Some(2));
assert_eq!(replacer.size(), 0);
}
#[test]
fn test_window_lfu_remove() {
let mut replacer = WindowLFUReplacer::new(3);
replacer.record_access(1).unwrap();
replacer.record_access(2).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(2, true).unwrap();
assert_eq!(replacer.size(), 2);
replacer.remove(1); assert_eq!(replacer.size(), 1);
assert!(replacer.node_store.get(&1).is_none());
replacer.record_access(3).unwrap(); assert_eq!(replacer.size(), 1);
replacer.remove(3); assert_eq!(replacer.size(), 1); assert!(replacer.node_store.get(&3).is_none());
assert_eq!(replacer.evict(), Some(2)); }
#[test]
fn test_window_lfu_capacity() {
let mut replacer = WindowLFUReplacer::new(2);
replacer.record_access(1).unwrap();
replacer.record_access(2).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(2, true).unwrap();
assert!(replacer.record_access(3).is_err());
assert_eq!(replacer.evict(), Some(1));
replacer.record_access(3).unwrap();
replacer.set_evictable(3, true).unwrap();
assert_eq!(replacer.size(), 2);
assert!(replacer.node_store.contains_key(&3));
}
}