use std::collections::VecDeque;
use std::hash::Hash;
use std::sync::Arc;
use rustc_hash::FxHashMap;
use crate::ds::{SlotArena, SlotId};
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::LruKMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::LruKMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{
CoreMetricsRecorder, LruKMetricsReadRecorder, LruKMetricsRecorder, LruMetricsRecorder,
MetricsSnapshotProvider,
};
use crate::traits::{
Cache, EvictingCache, FrequencyTracking, HistoryTracking, RecencyTracking, VictimInspectable,
};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Segment {
Cold,
Hot,
}
struct Node<K, V> {
prev: Option<SlotId>,
next: Option<SlotId>,
segment: Segment,
history: VecDeque<u64>,
key: K,
value: Arc<V>,
}
pub struct LrukCache<K, V> {
k: usize,
capacity: usize,
arena: SlotArena<Node<K, V>>,
map: FxHashMap<K, SlotId>,
cold_head: Option<SlotId>,
cold_tail: Option<SlotId>,
cold_len: usize,
hot_head: Option<SlotId>,
hot_tail: Option<SlotId>,
hot_len: usize,
tick: u64,
#[cfg(feature = "metrics")]
metrics: LruKMetrics,
}
impl<K, V> LrukCache<K, V> {
#[inline(always)]
fn pop_cold_tail_inner(&mut self) -> Option<Node<K, V>> {
let tail_id = self.cold_tail?;
let new_tail = self.arena.get(tail_id)?.prev;
let node = self
.arena
.remove(tail_id)
.expect("pop_cold_tail_inner: stale tail");
self.cold_tail = new_tail;
match self.cold_tail {
Some(t) => {
self.arena
.get_mut(t)
.expect("pop_cold_tail_inner: stale new tail")
.next = None;
},
None => self.cold_head = None,
}
self.cold_len -= 1;
Some(node)
}
#[inline(always)]
fn pop_hot_tail_inner(&mut self) -> Option<Node<K, V>> {
let tail_id = self.hot_tail?;
let new_tail = self.arena.get(tail_id)?.prev;
let node = self
.arena
.remove(tail_id)
.expect("pop_hot_tail_inner: stale tail");
self.hot_tail = new_tail;
match self.hot_tail {
Some(t) => {
self.arena
.get_mut(t)
.expect("pop_hot_tail_inner: stale new tail")
.next = None;
},
None => self.hot_head = None,
}
self.hot_len -= 1;
Some(node)
}
}
impl<K, V> LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
#[must_use]
#[inline]
pub fn new(capacity: usize) -> Self {
Self::with_k(capacity, 2)
}
#[must_use]
#[inline]
pub fn with_k(capacity: usize, k: usize) -> Self {
let k = k.max(1);
LrukCache {
k,
capacity,
arena: SlotArena::with_capacity(capacity),
map: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
cold_head: None,
cold_tail: None,
cold_len: 0,
hot_head: None,
hot_tail: None,
hot_len: 0,
tick: 0,
#[cfg(feature = "metrics")]
metrics: LruKMetrics::default(),
}
}
#[inline(always)]
fn detach(&mut self, id: SlotId) {
let node = self.arena.get(id).expect("detach: stale SlotId");
let prev = node.prev;
let next = node.next;
let segment = node.segment;
match prev {
Some(prev_id) => {
self.arena
.get_mut(prev_id)
.expect("detach: stale prev")
.next = next
},
None => match segment {
Segment::Cold => self.cold_head = next,
Segment::Hot => self.hot_head = next,
},
}
match next {
Some(next_id) => {
self.arena
.get_mut(next_id)
.expect("detach: stale next")
.prev = prev
},
None => match segment {
Segment::Cold => self.cold_tail = prev,
Segment::Hot => self.hot_tail = prev,
},
}
match segment {
Segment::Cold => self.cold_len -= 1,
Segment::Hot => self.hot_len -= 1,
}
}
#[inline(always)]
fn attach_cold_front(&mut self, id: SlotId) {
{
let node = self
.arena
.get_mut(id)
.expect("attach_cold_front: stale SlotId");
node.prev = None;
node.next = self.cold_head;
node.segment = Segment::Cold;
}
match self.cold_head {
Some(old_head) => {
self.arena
.get_mut(old_head)
.expect("attach_cold_front: stale head")
.prev = Some(id);
},
None => self.cold_tail = Some(id),
}
self.cold_head = Some(id);
self.cold_len += 1;
}
#[inline(always)]
fn attach_hot_front(&mut self, id: SlotId) {
{
let node = self
.arena
.get_mut(id)
.expect("attach_hot_front: stale SlotId");
node.prev = None;
node.next = self.hot_head;
node.segment = Segment::Hot;
}
match self.hot_head {
Some(old_head) => {
self.arena
.get_mut(old_head)
.expect("attach_hot_front: stale head")
.prev = Some(id);
},
None => self.hot_tail = Some(id),
}
self.hot_head = Some(id);
self.hot_len += 1;
}
#[inline(always)]
fn record_access(&mut self, id: SlotId) -> usize {
self.tick = self.tick.saturating_add(1);
let tick = self.tick;
let k = self.k;
let node = self.arena.get_mut(id).expect("record_access: stale SlotId");
node.history.push_back(tick);
if node.history.len() > k {
node.history.pop_front();
}
node.history.len()
}
#[inline(always)]
fn move_hot_to_front(&mut self, id: SlotId) {
let is_hot = self
.arena
.get(id)
.expect("move_hot_to_front: stale SlotId")
.segment
== Segment::Hot;
if !is_hot {
return;
}
self.detach(id);
self.attach_hot_front(id);
}
#[inline(always)]
fn promote_if_needed(&mut self, id: SlotId) {
let (is_cold, history_len) = {
let node = self.arena.get(id).expect("promote_if_needed: stale SlotId");
(node.segment == Segment::Cold, node.history.len())
};
if !is_cold || history_len < self.k {
return;
}
self.detach(id);
self.attach_hot_front(id);
}
#[inline]
fn evict_candidate(&mut self) -> Option<Node<K, V>> {
let node = if self.cold_len > 0 {
self.pop_cold_tail_inner()?
} else {
self.pop_hot_tail_inner()?
};
self.map.remove(&node.key);
Some(node)
}
#[inline]
fn peek_candidate(&self) -> Option<SlotId> {
if self.cold_len > 0 {
self.cold_tail
} else {
self.hot_tail
}
}
}
impl<K, V> Cache<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
#[inline]
fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
#[inline]
fn len(&self) -> usize {
self.map.len()
}
#[inline]
fn capacity(&self) -> usize {
self.capacity
}
#[inline]
fn peek(&self, key: &K) -> Option<&V> {
let &id = self.map.get(key)?;
self.arena.get(id).map(|node| node.value.as_ref())
}
#[inline]
fn insert(&mut self, key: K, value: V) -> Option<V> {
#[cfg(feature = "metrics")]
self.metrics.record_insert_call();
if self.capacity == 0 {
return None;
}
if let Some(&id) = self.map.get(&key) {
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
let old_arc = {
let node = self.arena.get_mut(id).expect("insert: stale SlotId");
std::mem::replace(&mut node.value, Arc::new(value))
};
let old_value = (*old_arc).clone();
self.record_access(id);
self.promote_if_needed(id);
self.move_hot_to_front(id);
return Some(old_value);
}
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
if self.map.len() >= self.capacity {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
if self.evict_candidate().is_some() {
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
}
}
self.tick = self.tick.saturating_add(1);
let mut history = VecDeque::with_capacity(self.k);
history.push_back(self.tick);
let id = self.arena.insert(Node {
prev: None,
next: None,
segment: Segment::Cold,
history,
key: key.clone(),
value: Arc::new(value),
});
self.map.insert(key, id);
self.attach_cold_front(id);
None
}
#[inline]
fn get(&mut self, key: &K) -> Option<&V> {
let id = match self.map.get(key) {
Some(&id) => id,
None => {
#[cfg(feature = "metrics")]
self.metrics.record_get_miss();
return None;
},
};
self.record_access(id);
self.promote_if_needed(id);
self.move_hot_to_front(id);
#[cfg(feature = "metrics")]
self.metrics.record_get_hit();
self.arena.get(id).map(|node| node.value.as_ref())
}
#[inline]
fn remove(&mut self, key: &K) -> Option<V> {
let id = self.map.remove(key)?;
self.detach(id);
let node = self.arena.remove(id).expect("remove: stale SlotId");
Some((*node.value).clone())
}
fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
self.map.clear();
self.arena.clear();
self.cold_head = None;
self.cold_tail = None;
self.cold_len = 0;
self.hot_head = None;
self.hot_tail = None;
self.hot_len = 0;
self.tick = 0;
}
}
impl<K, V> LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
#[inline]
pub fn pop_lru_k(&mut self) -> Option<(K, V)> {
#[cfg(feature = "metrics")]
self.metrics.record_pop_lru_k_call();
let node = self.evict_candidate()?;
#[cfg(feature = "metrics")]
self.metrics.record_pop_lru_k_found();
Some((node.key, (*node.value).clone()))
}
#[inline]
pub fn peek_lru_k(&self) -> Option<(&K, &V)> {
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lru_k_call();
let id = self.peek_candidate()?;
let node = self.arena.get(id)?;
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lru_k_found();
Some((&node.key, node.value.as_ref()))
}
#[inline]
pub fn k_value(&self) -> usize {
self.k
}
pub fn access_history(&self, key: &K) -> Option<Vec<u64>> {
let &id = self.map.get(key)?;
let node = self.arena.get(id)?;
Some(node.history.iter().rev().copied().collect())
}
#[inline]
pub fn access_count(&self, key: &K) -> Option<usize> {
let &id = self.map.get(key)?;
Some(self.arena.get(id)?.history.len())
}
#[inline]
pub fn k_distance(&self, key: &K) -> Option<u64> {
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_call();
let result = self.map.get(key).and_then(|&id| {
let node = self.arena.get(id)?;
if node.history.len() >= self.k {
node.history.front().copied()
} else {
None
}
});
#[cfg(feature = "metrics")]
if result.is_some() {
(&self.metrics).record_k_distance_found();
}
result
}
#[inline]
pub fn touch(&mut self, key: &K) -> bool {
#[cfg(feature = "metrics")]
self.metrics.record_touch_call();
let id = match self.map.get(key) {
Some(&id) => id,
None => return false,
};
self.record_access(id);
self.promote_if_needed(id);
self.move_hot_to_front(id);
#[cfg(feature = "metrics")]
self.metrics.record_touch_found();
true
}
pub fn k_distance_rank(&self, key: &K) -> Option<usize> {
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_rank_call();
if !self.map.contains_key(key) {
return None;
}
let mut items_with_distances: Vec<(bool, u64)> = Vec::new();
for &id in self.map.values() {
let history = &self
.arena
.get(id)
.expect("k_distance_rank: stale SlotId")
.history;
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_rank_scan_step();
let num_accesses = history.len();
if num_accesses < self.k {
let earliest = history.front().copied().unwrap_or(u64::MAX);
items_with_distances.push((false, earliest));
} else {
let k_distance = history.front().copied().unwrap_or(u64::MAX);
items_with_distances.push((true, k_distance));
}
}
items_with_distances.sort_by(|a, b| match (a.0, b.0) {
(false, false) => a.1.cmp(&b.1),
(true, true) => a.1.cmp(&b.1),
(false, true) => std::cmp::Ordering::Less,
(true, false) => std::cmp::Ordering::Greater,
});
let &target_id = self.map.get(key)?;
let target_history = &self
.arena
.get(target_id)
.expect("k_distance_rank: stale target SlotId")
.history;
let target_num_accesses = target_history.len();
let target_value = if target_num_accesses < self.k {
(false, target_history.front().copied().unwrap_or(u64::MAX))
} else {
(true, target_history.front().copied().unwrap_or(u64::MAX))
};
items_with_distances
.iter()
.position(|item| item == &target_value)
.inspect(|_| {
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_rank_found();
})
}
}
impl<K, V> EvictingCache<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn evict_one(&mut self) -> Option<(K, V)> {
self.pop_lru_k()
}
}
impl<K, V> VictimInspectable<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn peek_victim(&self) -> Option<(&K, &V)> {
self.peek_lru_k()
}
}
impl<K, V> RecencyTracking<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn touch(&mut self, key: &K) -> bool {
LrukCache::touch(self, key)
}
fn recency_rank(&self, key: &K) -> Option<usize> {
let &target_id = self.map.get(key)?;
let mut rank = 0;
let mut current = self.hot_head;
while let Some(id) = current {
if id == target_id {
return Some(rank);
}
rank += 1;
current = self
.arena
.get(id)
.expect("recency_rank: stale hot SlotId")
.next;
}
let mut current = self.cold_head;
while let Some(id) = current {
if id == target_id {
return Some(rank);
}
rank += 1;
current = self
.arena
.get(id)
.expect("recency_rank: stale cold SlotId")
.next;
}
None
}
}
impl<K, V> FrequencyTracking<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn frequency(&self, key: &K) -> Option<u64> {
self.access_count(key).map(|c| c as u64)
}
}
impl<K, V> HistoryTracking<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn access_count(&self, key: &K) -> Option<usize> {
LrukCache::access_count(self, key)
}
fn k_distance(&self, key: &K) -> Option<u64> {
LrukCache::k_distance(self, key)
}
fn access_history(&self, key: &K) -> Option<Vec<u64>> {
LrukCache::access_history(self, key)
}
fn k_value(&self) -> usize {
LrukCache::k_value(self)
}
}
impl<K, V> std::fmt::Debug for LrukCache<K, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LrukCache")
.field("k", &self.k)
.field("capacity", &self.capacity)
.field("len", &self.map.len())
.field("cold_len", &self.cold_len)
.field("hot_len", &self.hot_len)
.finish_non_exhaustive()
}
}
impl<K, V> Default for LrukCache<K, V> {
fn default() -> Self {
LrukCache {
k: 2,
capacity: 0,
arena: SlotArena::new(),
map: FxHashMap::default(),
cold_head: None,
cold_tail: None,
cold_len: 0,
hot_head: None,
hot_tail: None,
hot_len: 0,
tick: 0,
#[cfg(feature = "metrics")]
metrics: LruKMetrics::default(),
}
}
}
impl<K, V> Clone for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn clone(&self) -> Self {
let mut new = LrukCache {
k: self.k,
capacity: self.capacity,
arena: SlotArena::with_capacity(self.capacity),
map: FxHashMap::with_capacity_and_hasher(self.map.len(), Default::default()),
cold_head: None,
cold_tail: None,
cold_len: 0,
hot_head: None,
hot_tail: None,
hot_len: 0,
tick: self.tick,
#[cfg(feature = "metrics")]
metrics: LruKMetrics::default(),
};
let mut current = self.cold_head;
while let Some(id) = current {
let (next_src, history, key, value) = {
let src = self.arena.get(id).expect("clone: stale cold SlotId");
(
src.next,
src.history.clone(),
src.key.clone(),
Arc::clone(&src.value),
)
};
let prev = new.cold_tail;
let new_id = new.arena.insert(Node {
prev,
next: None,
segment: Segment::Cold,
history,
key: key.clone(),
value,
});
match new.cold_tail {
Some(t) => {
new.arena.get_mut(t).expect("clone: stale cold tail").next = Some(new_id);
},
None => new.cold_head = Some(new_id),
}
new.cold_tail = Some(new_id);
new.cold_len += 1;
new.map.insert(key, new_id);
current = next_src;
}
let mut current = self.hot_head;
while let Some(id) = current {
let (next_src, history, key, value) = {
let src = self.arena.get(id).expect("clone: stale hot SlotId");
(
src.next,
src.history.clone(),
src.key.clone(),
Arc::clone(&src.value),
)
};
let prev = new.hot_tail;
let new_id = new.arena.insert(Node {
prev,
next: None,
segment: Segment::Hot,
history,
key: key.clone(),
value,
});
match new.hot_tail {
Some(t) => {
new.arena.get_mut(t).expect("clone: stale hot tail").next = Some(new_id);
},
None => new.hot_head = Some(new_id),
}
new.hot_tail = Some(new_id);
new.hot_len += 1;
new.map.insert(key, new_id);
current = next_src;
}
new
}
}
#[cfg(feature = "metrics")]
impl<K, V> LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
pub fn metrics_snapshot(&self) -> LruKMetricsSnapshot {
LruKMetricsSnapshot {
get_calls: self.metrics.get_calls,
get_hits: self.metrics.get_hits,
get_misses: self.metrics.get_misses,
insert_calls: self.metrics.insert_calls,
insert_updates: self.metrics.insert_updates,
insert_new: self.metrics.insert_new,
evict_calls: self.metrics.evict_calls,
evicted_entries: self.metrics.evicted_entries,
pop_lru_calls: self.metrics.pop_lru_calls,
pop_lru_found: self.metrics.pop_lru_found,
peek_lru_calls: self.metrics.peek_lru_calls,
peek_lru_found: self.metrics.peek_lru_found,
touch_calls: self.metrics.touch_calls,
touch_found: self.metrics.touch_found,
recency_rank_calls: self.metrics.recency_rank_calls,
recency_rank_found: self.metrics.recency_rank_found,
recency_rank_scan_steps: self.metrics.recency_rank_scan_steps,
pop_lru_k_calls: self.metrics.pop_lru_k_calls,
pop_lru_k_found: self.metrics.pop_lru_k_found,
peek_lru_k_calls: self.metrics.peek_lru_k_calls.get(),
peek_lru_k_found: self.metrics.peek_lru_k_found.get(),
k_distance_calls: self.metrics.k_distance_calls.get(),
k_distance_found: self.metrics.k_distance_found.get(),
k_distance_rank_calls: self.metrics.k_distance_rank_calls.get(),
k_distance_rank_found: self.metrics.k_distance_rank_found.get(),
k_distance_rank_scan_steps: self.metrics.k_distance_rank_scan_steps.get(),
cache_len: self.map.len(),
capacity: self.capacity,
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<LruKMetricsSnapshot> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn snapshot(&self) -> LruKMetricsSnapshot {
self.metrics_snapshot()
}
}
#[cfg(test)]
mod tests {
mod basic_behavior {
use std::thread;
use std::time::Duration;
use super::super::*;
#[test]
fn test_basic_lru_k_insertion_and_retrieval() {
let mut cache = LrukCache::new(2);
cache.insert(1, "one");
assert_eq!(cache.get(&1), Some(&"one"));
cache.insert(2, "two");
assert_eq!(cache.get(&2), Some(&"two"));
assert_eq!(cache.len(), 2);
}
#[test]
fn test_lru_k_eviction_order() {
let mut cache = LrukCache::with_k(3, 2);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
thread::sleep(Duration::from_millis(2));
cache.insert(3, 30);
thread::sleep(Duration::from_millis(2));
cache.insert(4, 40);
assert!(!cache.contains(&1), "1 should be evicted");
assert!(cache.contains(&2));
assert!(cache.contains(&3));
assert!(cache.contains(&4));
thread::sleep(Duration::from_millis(2));
cache.get(&2);
cache.insert(5, 50);
assert!(!cache.contains(&3), "3 should be evicted");
assert!(cache.contains(&2));
assert!(cache.contains(&4));
assert!(cache.contains(&5));
}
#[test]
fn test_capacity_enforcement() {
let mut cache = LrukCache::new(2);
cache.insert(1, 1);
thread::sleep(Duration::from_millis(1));
cache.insert(2, 2);
assert_eq!(cache.len(), 2);
cache.insert(3, 3);
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&1));
assert!(cache.contains(&2));
assert!(cache.contains(&3));
}
#[test]
fn test_update_existing_key() {
let mut cache = LrukCache::new(2);
cache.insert(1, 10);
cache.insert(1, 20);
assert_eq!(cache.get(&1), Some(&20));
assert_eq!(cache.access_count(&1), Some(2));
}
#[test]
fn test_access_history_tracking() {
let mut cache = LrukCache::with_k(2, 3); cache.insert(1, 10); thread::sleep(Duration::from_millis(2));
cache.get(&1); thread::sleep(Duration::from_millis(2));
cache.get(&1); thread::sleep(Duration::from_millis(2));
assert_eq!(cache.access_count(&1), Some(3));
cache.get(&1); assert_eq!(cache.access_count(&1), Some(3));
let history = cache.access_history(&1).unwrap();
assert_eq!(history.len(), 3);
assert!(history[0] > history[1]);
assert!(history[1] > history[2]);
}
#[test]
fn test_k_value_behavior() {
let cache = LrukCache::<i32, i32>::with_k(10, 5);
assert_eq!(cache.k_value(), 5);
}
#[test]
fn test_key_operations_consistency() {
let mut cache = LrukCache::new(2);
cache.insert(1, 10);
assert!(cache.contains(&1));
assert_eq!(cache.get(&1), Some(&10));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_timestamp_ordering() {
let mut cache = LrukCache::with_k(2, 1); cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
let dist1 = cache.k_distance(&1).unwrap();
let dist2 = cache.k_distance(&2).unwrap();
assert!(dist2 > dist1);
}
}
mod edge_cases {
use std::thread;
use std::time::Duration;
use super::super::*;
#[test]
fn test_empty_cache_operations() {
let mut cache = LrukCache::<i32, i32>::new(5);
assert_eq!(cache.get(&1), None);
assert_eq!(cache.remove(&1), None);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&1));
}
#[test]
fn test_single_item_cache() {
let mut cache = LrukCache::new(1);
cache.insert(1, 10);
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&1), Some(&10));
cache.insert(2, 20);
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&2), Some(&20));
assert!(!cache.contains(&1));
}
#[test]
fn test_zero_capacity_cache() {
let mut cache = LrukCache::new(0);
cache.insert(1, 10);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&1));
}
#[test]
fn test_k_equals_one() {
let mut cache = LrukCache::with_k(2, 1);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
thread::sleep(Duration::from_millis(2));
cache.get(&1);
thread::sleep(Duration::from_millis(2));
cache.insert(3, 30);
assert!(cache.contains(&1));
assert!(!cache.contains(&2)); assert!(cache.contains(&3));
}
#[test]
fn test_k_larger_than_capacity() {
let mut cache = LrukCache::with_k(2, 5);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(1)); cache.insert(2, 20);
cache.get(&1);
cache.get(&2);
cache.insert(3, 30);
assert!(!cache.contains(&1));
assert!(cache.contains(&2));
assert!(cache.contains(&3));
}
#[test]
fn test_same_key_rapid_accesses() {
let mut cache = LrukCache::with_k(5, 3);
cache.insert(1, 10);
for _ in 0..10 {
cache.get(&1);
}
assert_eq!(cache.access_count(&1), Some(3)); }
#[test]
fn test_duplicate_key_insertion() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
cache.insert(1, 20);
assert_eq!(cache.get(&1), Some(&20));
assert_eq!(cache.len(), 1);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_large_cache_operations() {
let mut cache = LrukCache::new(100);
cache.insert(0, 0);
thread::sleep(Duration::from_millis(1));
for i in 1..100 {
cache.insert(i, i);
}
assert_eq!(cache.len(), 100);
cache.insert(100, 100);
assert_eq!(cache.len(), 100);
assert!(!cache.contains(&0)); }
#[test]
fn test_access_history_overflow() {
let mut cache = LrukCache::with_k(2, 3); cache.insert(1, 10);
cache.get(&1);
cache.get(&1);
cache.get(&1);
cache.get(&1);
let history = cache.access_history(&1).unwrap();
assert_eq!(history.len(), 3);
}
}
mod lru_k_operations {
use std::thread;
use std::time::Duration;
use super::super::*;
#[test]
fn test_pop_lru_k_basic() {
let mut cache = LrukCache::with_k(3, 2);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
let popped = cache.pop_lru_k();
assert_eq!(popped, Some((1, 10)));
assert!(!cache.contains(&1));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_peek_lru_k_basic() {
let mut cache = LrukCache::with_k(3, 2);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
let peeked = cache.peek_lru_k();
assert_eq!(peeked, Some((&1, &10)));
assert!(cache.contains(&1));
assert_eq!(cache.len(), 2);
}
#[test]
fn test_k_value_retrieval() {
let cache = LrukCache::<i32, i32>::with_k(10, 4);
assert_eq!(cache.k_value(), 4);
}
#[test]
fn test_access_history_retrieval() {
let mut cache = LrukCache::with_k(10, 3);
cache.insert(1, 10);
cache.get(&1);
let history = cache.access_history(&1).unwrap();
assert_eq!(history.len(), 2);
}
#[test]
fn test_access_count() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
assert_eq!(cache.access_count(&1), Some(1));
cache.get(&1);
assert_eq!(cache.access_count(&1), Some(2));
}
#[test]
fn test_k_distance() {
let mut cache = LrukCache::with_k(5, 2);
cache.insert(1, 10);
assert_eq!(cache.k_distance(&1), None);
cache.get(&1);
assert!(cache.k_distance(&1).is_some());
}
#[test]
fn test_touch_functionality() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
assert!(cache.touch(&1));
assert_eq!(cache.access_count(&1), Some(2));
assert!(!cache.touch(&999)); }
#[test]
fn test_k_distance_rank() {
let mut cache = LrukCache::with_k(5, 2);
cache.insert(1, 10); thread::sleep(Duration::from_millis(2));
cache.insert(2, 20); thread::sleep(Duration::from_millis(2));
assert_eq!(cache.k_distance_rank(&1), Some(0));
assert_eq!(cache.k_distance_rank(&2), Some(1));
cache.get(&1);
assert_eq!(cache.k_distance_rank(&2), Some(0));
assert_eq!(cache.k_distance_rank(&1), Some(1));
}
#[test]
fn test_pop_lru_k_empty_cache() {
let mut cache = LrukCache::<i32, i32>::new(5);
assert_eq!(cache.pop_lru_k(), None);
}
#[test]
fn test_peek_lru_k_empty_cache() {
let cache = LrukCache::<i32, i32>::new(5);
assert_eq!(cache.peek_lru_k(), None);
}
#[test]
fn test_lru_k_tie_breaking() {
let mut cache = LrukCache::with_k(5, 2);
cache.insert(1, 10);
cache.insert(2, 20);
assert!(cache.peek_lru_k().is_some());
}
#[test]
fn test_access_history_after_removal() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
cache.remove(&1);
assert!(!cache.contains(&1));
assert_eq!(cache.access_count(&1), None);
}
#[test]
fn test_access_history_after_clear() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.access_count(&1), None);
}
}
mod state_consistency {
use super::super::*;
#[test]
fn test_cache_access_history_consistency() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
assert!(cache.access_history(&1).is_some());
cache.remove(&1);
assert!(cache.access_history(&1).is_none());
}
#[test]
fn test_len_consistency() {
let mut cache = LrukCache::new(5);
assert_eq!(cache.len(), 0);
cache.insert(1, 10);
assert_eq!(cache.len(), 1);
cache.insert(2, 20);
assert_eq!(cache.len(), 2);
cache.remove(&1);
assert_eq!(cache.len(), 1);
cache.clear();
assert_eq!(cache.len(), 0);
}
#[test]
fn test_capacity_consistency() {
let mut cache = LrukCache::new(2);
assert_eq!(cache.capacity(), 2);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert_eq!(cache.len(), 2); }
#[test]
fn test_clear_resets_all_state() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
cache.insert(2, 20);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&1));
assert!(!cache.contains(&2));
assert!(cache.access_history(&1).is_none());
}
#[test]
fn test_remove_consistency() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
let removed = cache.remove(&1);
assert_eq!(removed, Some(10));
assert!(!cache.contains(&1));
assert!(cache.access_history(&1).is_none());
let removed_again = cache.remove(&1);
assert_eq!(removed_again, None);
}
#[test]
fn test_eviction_consistency() {
let mut cache = LrukCache::new(1);
cache.insert(1, 10);
cache.insert(2, 20);
assert!(!cache.contains(&1));
assert!(cache.contains(&2));
assert!(cache.access_history(&1).is_none());
assert!(cache.access_history(&2).is_some());
}
#[test]
fn test_access_history_update_on_get() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
let count_before = cache.access_count(&1).unwrap();
cache.get(&1);
let count_after = cache.access_count(&1).unwrap();
assert_eq!(count_after, count_before + 1);
}
#[test]
fn test_invariants_after_operations() {
let mut cache = LrukCache::with_k(2, 2);
cache.insert(1, 10);
cache.insert(2, 20);
assert!(cache.len() <= cache.capacity());
let h1 = cache.access_history(&1).unwrap();
assert!(h1.len() <= 2);
cache.get(&1);
cache.get(&1);
let h1_new = cache.access_history(&1).unwrap();
assert!(h1_new.len() <= 2);
}
#[test]
fn test_k_distance_calculation_consistency() {
let mut cache = LrukCache::with_k(5, 2);
cache.insert(1, 10);
assert_eq!(cache.k_distance(&1), None);
cache.get(&1); assert!(cache.k_distance(&1).is_some());
}
#[test]
fn test_timestamp_consistency() {
let mut cache = LrukCache::new(5);
cache.insert(1, 10);
let history = cache.access_history(&1).unwrap();
let ts1 = history[0];
std::thread::sleep(std::time::Duration::from_millis(1));
cache.get(&1);
let history_new = cache.access_history(&1).unwrap();
let ts2 = history_new[0];
assert!(ts2 > ts1);
}
}
}