#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::SlruMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::SlruMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{CoreMetricsRecorder, MetricsSnapshotProvider, SlruMetricsRecorder};
use crate::prelude::ReadOnlyCache;
use crate::traits::CoreCache;
use rustc_hash::FxHashMap;
use std::hash::Hash;
use std::iter::FusedIterator;
use std::marker::PhantomData;
use std::ptr::NonNull;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Segment {
Probationary,
Protected,
}
#[repr(C)]
struct Node<K, V> {
prev: Option<NonNull<Node<K, V>>>,
next: Option<NonNull<Node<K, V>>>,
segment: Segment,
key: K,
value: V,
}
pub struct SlruCore<K, V> {
map: FxHashMap<K, NonNull<Node<K, V>>>,
probationary_head: Option<NonNull<Node<K, V>>>,
probationary_tail: Option<NonNull<Node<K, V>>>,
probationary_len: usize,
protected_head: Option<NonNull<Node<K, V>>>,
protected_tail: Option<NonNull<Node<K, V>>>,
protected_len: usize,
probationary_cap: usize,
capacity: usize,
#[cfg(feature = "metrics")]
metrics: SlruMetrics,
}
unsafe impl<K: Send, V: Send> Send for SlruCore<K, V> {}
unsafe impl<K: Sync, V: Sync> Sync for SlruCore<K, V> {}
impl<K, V> SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
#[must_use]
pub fn new(capacity: usize, probationary_frac: f64) -> Self {
assert!(
(0.0..=1.0).contains(&probationary_frac),
"probationary_frac must be in 0.0..=1.0, got {probationary_frac}"
);
let probationary_cap = (capacity as f64 * probationary_frac) as usize;
let total_cap = capacity + probationary_cap;
Self {
map: FxHashMap::with_capacity_and_hasher(total_cap, Default::default()),
probationary_head: None,
probationary_tail: None,
probationary_len: 0,
protected_head: None,
protected_tail: None,
protected_len: 0,
probationary_cap,
capacity,
#[cfg(feature = "metrics")]
metrics: SlruMetrics::default(),
}
}
#[inline(always)]
fn detach(&mut self, node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_ref();
let prev = node.prev;
let next = node.next;
let segment = node.segment;
let (head, tail, len) = match segment {
Segment::Probationary => (
&mut self.probationary_head,
&mut self.probationary_tail,
&mut self.probationary_len,
),
Segment::Protected => (
&mut self.protected_head,
&mut self.protected_tail,
&mut self.protected_len,
),
};
match prev {
Some(mut p) => p.as_mut().next = next,
None => *head = next,
}
match next {
Some(mut n) => n.as_mut().prev = prev,
None => *tail = prev,
}
*len -= 1;
}
}
#[inline(always)]
fn attach_probationary_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_mut();
node.prev = None;
node.next = self.probationary_head;
node.segment = Segment::Probationary;
match self.probationary_head {
Some(mut h) => h.as_mut().prev = Some(node_ptr),
None => self.probationary_tail = Some(node_ptr),
}
self.probationary_head = Some(node_ptr);
self.probationary_len += 1;
}
}
#[inline(always)]
fn attach_protected_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_mut();
node.prev = None;
node.next = self.protected_head;
node.segment = Segment::Protected;
match self.protected_head {
Some(mut h) => h.as_mut().prev = Some(node_ptr),
None => self.protected_tail = Some(node_ptr),
}
self.protected_head = Some(node_ptr);
self.protected_len += 1;
}
}
#[inline(always)]
fn pop_probationary_tail(&mut self) -> Option<Box<Node<K, V>>> {
self.probationary_tail.map(|tail_ptr| unsafe {
let node = Box::from_raw(tail_ptr.as_ptr());
self.probationary_tail = node.prev;
match self.probationary_tail {
Some(mut t) => t.as_mut().next = None,
None => self.probationary_head = None,
}
self.probationary_len -= 1;
node
})
}
#[inline(always)]
fn pop_protected_tail(&mut self) -> Option<Box<Node<K, V>>> {
self.protected_tail.map(|tail_ptr| unsafe {
let node = Box::from_raw(tail_ptr.as_ptr());
self.protected_tail = node.prev;
match self.protected_tail {
Some(mut t) => t.as_mut().next = None,
None => self.protected_head = None,
}
self.protected_len -= 1;
node
})
}
#[inline]
pub fn peek(&self, key: &K) -> Option<&V> {
self.map.get(key).map(|&ptr| unsafe { &ptr.as_ref().value })
}
#[inline]
pub fn get(&mut self, key: &K) -> Option<&V> {
let node_ptr = match self.map.get(key) {
Some(&ptr) => ptr,
None => {
#[cfg(feature = "metrics")]
self.metrics.record_get_miss();
return None;
},
};
#[cfg(feature = "metrics")]
self.metrics.record_get_hit();
let segment = unsafe { node_ptr.as_ref().segment };
match segment {
Segment::Probationary => {
self.detach(node_ptr);
self.attach_protected_head(node_ptr);
#[cfg(feature = "metrics")]
self.metrics.record_probationary_to_protected();
},
Segment::Protected => {
self.detach(node_ptr);
self.attach_protected_head(node_ptr);
},
}
unsafe { Some(&node_ptr.as_ref().value) }
}
#[inline]
pub 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(&node_ptr) = self.map.get(&key) {
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
let old = unsafe { std::mem::replace(&mut (*node_ptr.as_ptr()).value, value) };
return Some(old);
}
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
self.evict_if_needed();
let node = Box::new(Node {
prev: None,
next: None,
segment: Segment::Probationary,
key: key.clone(),
value,
});
let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
self.map.insert(key, node_ptr);
self.attach_probationary_head(node_ptr);
#[cfg(debug_assertions)]
self.validate_invariants();
None
}
#[inline]
fn evict_if_needed(&mut self) {
while self.len() >= self.capacity {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
if self.probationary_len > self.probationary_cap {
if let Some(node) = self.pop_probationary_tail() {
self.map.remove(&node.key);
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
continue;
}
}
if let Some(node) = self.pop_protected_tail() {
self.map.remove(&node.key);
#[cfg(feature = "metrics")]
{
self.metrics.record_evicted_entry();
self.metrics.record_protected_eviction();
}
continue;
}
if let Some(node) = self.pop_probationary_tail() {
self.map.remove(&node.key);
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
continue;
}
break;
}
}
#[inline]
pub fn len(&self) -> usize {
self.map.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
#[inline]
pub fn probationary_len(&self) -> usize {
self.probationary_len
}
#[inline]
pub fn protected_len(&self) -> usize {
self.protected_len
}
pub fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
while self.pop_probationary_tail().is_some() {}
while self.pop_protected_tail().is_some() {}
self.map.clear();
#[cfg(debug_assertions)]
self.validate_invariants();
}
pub fn iter(&self) -> Iter<'_, K, V> {
Iter {
current: self.probationary_head,
protected_head: self.protected_head,
in_protected: false,
remaining: self.probationary_len + self.protected_len,
_marker: PhantomData,
}
}
pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
IterMut {
current: self.probationary_head,
protected_head: self.protected_head,
in_protected: false,
remaining: self.probationary_len + self.protected_len,
_marker: PhantomData,
}
}
#[cfg(debug_assertions)]
fn validate_invariants(&self) {
let mut prob_count = 0;
let mut current = self.probationary_head;
let mut visited = std::collections::HashSet::new();
while let Some(ptr) = current {
prob_count += 1;
assert!(visited.insert(ptr), "Cycle detected in probationary list");
assert!(
prob_count <= self.map.len(),
"Probationary count exceeds total entries"
);
unsafe {
let node = ptr.as_ref();
assert!(
matches!(node.segment, Segment::Probationary),
"Non-probationary node in probationary list"
);
current = node.next;
}
}
debug_assert_eq!(
prob_count, self.probationary_len,
"Probationary count mismatch"
);
let mut prot_count = 0;
let mut current = self.protected_head;
visited.clear();
while let Some(ptr) = current {
prot_count += 1;
assert!(visited.insert(ptr), "Cycle detected in protected list");
assert!(
prot_count <= self.map.len(),
"Protected count exceeds total entries"
);
unsafe {
let node = ptr.as_ref();
assert!(
matches!(node.segment, Segment::Protected),
"Non-protected node in protected list"
);
current = node.next;
}
}
debug_assert_eq!(prot_count, self.protected_len, "Protected count mismatch");
debug_assert_eq!(
prob_count + prot_count,
self.map.len(),
"List counts don't match map size"
);
for &node_ptr in self.map.values() {
unsafe {
let node = node_ptr.as_ref();
match node.segment {
Segment::Probationary => {
debug_assert!(prob_count > 0, "Node marked probationary but list empty");
},
Segment::Protected => {
debug_assert!(prot_count > 0, "Node marked protected but list empty");
},
}
}
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
pub fn metrics_snapshot(&self) -> SlruMetricsSnapshot {
SlruMetricsSnapshot {
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,
probationary_to_protected: self.metrics.probationary_to_protected,
protected_evictions: self.metrics.protected_evictions,
cache_len: self.len(),
capacity: self.capacity(),
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<SlruMetricsSnapshot> for SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
fn snapshot(&self) -> SlruMetricsSnapshot {
self.metrics_snapshot()
}
}
impl<K, V> Drop for SlruCore<K, V> {
fn drop(&mut self) {
unsafe {
let mut current = self.probationary_head.take();
while let Some(ptr) = current {
current = ptr.as_ref().next;
drop(Box::from_raw(ptr.as_ptr()));
}
let mut current = self.protected_head.take();
while let Some(ptr) = current {
current = ptr.as_ref().next;
drop(Box::from_raw(ptr.as_ptr()));
}
}
}
}
impl<K, V> std::fmt::Debug for SlruCore<K, V>
where
K: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SlruCore")
.field("capacity", &self.capacity)
.field("probationary_cap", &self.probationary_cap)
.field("len", &self.map.len())
.field("probationary_len", &self.probationary_len)
.field("protected_len", &self.protected_len)
.finish_non_exhaustive()
}
}
impl<K, V> Default for SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
fn default() -> Self {
Self::new(0, 0.25)
}
}
impl<K, V> Clone for SlruCore<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
fn clone(&self) -> Self {
let mut new_cache = SlruCore::new(
self.capacity,
if self.capacity > 0 {
self.probationary_cap as f64 / self.capacity as f64
} else {
0.25
},
);
let mut prob_entries = Vec::with_capacity(self.probationary_len);
let mut current = self.probationary_tail;
while let Some(ptr) = current {
unsafe {
let node = ptr.as_ref();
prob_entries.push((node.key.clone(), node.value.clone()));
current = node.prev;
}
}
for (key, value) in prob_entries {
let node = Box::new(Node {
prev: None,
next: None,
segment: Segment::Probationary,
key: key.clone(),
value,
});
let ptr = NonNull::new(Box::into_raw(node)).unwrap();
new_cache.map.insert(key, ptr);
new_cache.attach_probationary_head(ptr);
}
let mut prot_entries = Vec::with_capacity(self.protected_len);
let mut current = self.protected_tail;
while let Some(ptr) = current {
unsafe {
let node = ptr.as_ref();
prot_entries.push((node.key.clone(), node.value.clone()));
current = node.prev;
}
}
for (key, value) in prot_entries {
let node = Box::new(Node {
prev: None,
next: None,
segment: Segment::Protected,
key: key.clone(),
value,
});
let ptr = NonNull::new(Box::into_raw(node)).unwrap();
new_cache.map.insert(key, ptr);
new_cache.attach_protected_head(ptr);
}
#[cfg(feature = "metrics")]
{
new_cache.metrics = self.metrics;
}
new_cache
}
}
impl<K, V> ReadOnlyCache<K, V> for SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
#[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
}
}
impl<K, V> CoreCache<K, V> for SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
fn insert(&mut self, key: K, value: V) -> Option<V> {
SlruCore::insert(self, key, value)
}
#[inline]
fn get(&mut self, key: &K) -> Option<&V> {
SlruCore::get(self, key)
}
fn clear(&mut self) {
SlruCore::clear(self);
}
}
pub struct Iter<'a, K, V> {
current: Option<NonNull<Node<K, V>>>,
protected_head: Option<NonNull<Node<K, V>>>,
in_protected: bool,
remaining: usize,
_marker: PhantomData<&'a (K, V)>,
}
impl<'a, K, V> Iterator for Iter<'a, K, V> {
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(node_ptr) = self.current {
unsafe {
let node = node_ptr.as_ref();
self.current = node.next;
self.remaining -= 1;
return Some((&node.key, &node.value));
}
} else if !self.in_protected {
self.in_protected = true;
self.current = self.protected_head;
} else {
return None;
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<K, V> ExactSizeIterator for Iter<'_, K, V> {}
impl<K, V> FusedIterator for Iter<'_, K, V> {}
pub struct IterMut<'a, K, V> {
current: Option<NonNull<Node<K, V>>>,
protected_head: Option<NonNull<Node<K, V>>>,
in_protected: bool,
remaining: usize,
_marker: PhantomData<&'a mut (K, V)>,
}
impl<'a, K, V> Iterator for IterMut<'a, K, V> {
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(mut node_ptr) = self.current {
unsafe {
let node = node_ptr.as_mut();
self.current = node.next;
self.remaining -= 1;
return Some((&node.key, &mut node.value));
}
} else if !self.in_protected {
self.in_protected = true;
self.current = self.protected_head;
} else {
return None;
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<K, V> ExactSizeIterator for IterMut<'_, K, V> {}
impl<K, V> FusedIterator for IterMut<'_, K, V> {}
pub struct IntoIter<K, V> {
current: Option<NonNull<Node<K, V>>>,
protected_head: Option<NonNull<Node<K, V>>>,
in_protected: bool,
remaining: usize,
}
impl<K, V> Iterator for IntoIter<K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(node_ptr) = self.current {
unsafe {
let node = Box::from_raw(node_ptr.as_ptr());
self.current = node.next;
self.remaining -= 1;
return Some((node.key, node.value));
}
} else if !self.in_protected {
self.in_protected = true;
self.current = self.protected_head;
} else {
return None;
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<K, V> ExactSizeIterator for IntoIter<K, V> {}
impl<K, V> FusedIterator for IntoIter<K, V> {}
impl<K, V> Drop for IntoIter<K, V> {
fn drop(&mut self) {
while self.next().is_some() {}
}
}
unsafe impl<K: Send, V: Send> Send for IntoIter<K, V> {}
unsafe impl<K: Sync, V: Sync> Sync for IntoIter<K, V> {}
impl<K, V> IntoIterator for SlruCore<K, V> {
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(mut self) -> IntoIter<K, V> {
let iter = IntoIter {
current: self.probationary_head,
protected_head: self.protected_head,
in_protected: false,
remaining: self.probationary_len + self.protected_len,
};
self.probationary_head = None;
self.probationary_tail = None;
self.probationary_len = 0;
self.protected_head = None;
self.protected_tail = None;
self.protected_len = 0;
iter
}
}
impl<'a, K, V> IntoIterator for &'a SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Iter<'a, K, V> {
self.iter()
}
}
impl<'a, K, V> IntoIterator for &'a mut SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> IterMut<'a, K, V> {
self.iter_mut()
}
}
impl<K, V> FromIterator<(K, V)> for SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut cache = SlruCore::new(lower.max(16), 0.25);
cache.extend(iter);
cache
}
}
impl<K, V> Extend<(K, V)> for SlruCore<K, V>
where
K: Clone + Eq + Hash,
{
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
for (k, v) in iter {
self.insert(k, v);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
mod basic_operations {
use super::*;
#[test]
fn new_cache_is_empty() {
let cache: SlruCore<&str, i32> = SlruCore::new(100, 0.25);
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 100);
}
#[test]
fn insert_and_get() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("key1", "value1");
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&"key1"), Some(&"value1"));
}
#[test]
fn insert_multiple_items() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&"a"), Some(&1));
assert_eq!(cache.get(&"b"), Some(&2));
assert_eq!(cache.get(&"c"), Some(&3));
}
#[test]
fn get_missing_key_returns_none() {
let mut cache: SlruCore<&str, i32> = SlruCore::new(100, 0.25);
cache.insert("exists", 42);
assert_eq!(cache.get(&"missing"), None);
}
#[test]
fn update_existing_key() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("key", "initial");
cache.insert("key", "updated");
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&"key"), Some(&"updated"));
}
#[test]
fn contains_returns_correct_result() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("exists", 1);
assert!(cache.contains(&"exists"));
assert!(!cache.contains(&"missing"));
}
#[test]
fn contains_does_not_promote() {
let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
cache.insert("c".to_string(), 3);
assert!(cache.contains(&"a".to_string()));
assert!(cache.contains(&"b".to_string()));
assert!(cache.contains(&"c".to_string()));
for i in 0..10 {
cache.insert(format!("new{}", i), i);
}
assert!(!cache.contains(&"a".to_string()));
assert!(!cache.contains(&"b".to_string()));
assert!(!cache.contains(&"c".to_string()));
}
#[test]
fn clear_removes_all_entries() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
cache.insert("b", 2);
cache.get(&"a");
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"a"));
assert!(!cache.contains(&"b"));
}
#[test]
fn capacity_returns_correct_value() {
let cache: SlruCore<i32, i32> = SlruCore::new(500, 0.25);
assert_eq!(cache.capacity(), 500);
}
}
mod peek_tests {
use super::*;
#[test]
fn peek_returns_value_without_promotion() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("key", 42);
assert_eq!(cache.peek(&"key"), Some(&42));
assert_eq!(cache.peek(&"missing"), None);
}
#[test]
fn peek_does_not_promote_to_protected() {
let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
cache.insert("key".to_string(), 1);
for _ in 0..10 {
assert_eq!(cache.peek(&"key".to_string()), Some(&1));
}
for i in 0..12 {
cache.insert(format!("filler{}", i), i);
}
assert!(!cache.contains(&"key".to_string()));
}
}
mod segment_behavior {
use super::*;
#[test]
fn new_insert_goes_to_probationary() {
let mut cache = SlruCore::new(10, 0.3);
cache.insert("key", "value");
assert!(cache.contains(&"key"));
assert_eq!(cache.len(), 1);
assert_eq!(cache.probationary_len(), 1);
assert_eq!(cache.protected_len(), 0);
}
#[test]
fn get_promotes_from_probationary_to_protected() {
let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
cache.insert("key".to_string(), 0);
let _ = cache.get(&"key".to_string());
for i in 0..12 {
cache.insert(format!("new{}", i), i);
}
assert!(cache.contains(&"key".to_string()));
}
#[test]
fn item_in_protected_stays_in_protected() {
let mut cache = SlruCore::new(10, 0.3);
cache.insert("key", "value");
cache.get(&"key");
cache.get(&"key");
cache.get(&"key");
assert_eq!(cache.get(&"key"), Some(&"value"));
}
#[test]
fn multiple_accesses_keep_item_alive() {
let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
cache.insert("hot".to_string(), 0);
cache.get(&"hot".to_string());
for i in 0..15 {
cache.insert(format!("cold{}", i), i);
cache.get(&"hot".to_string());
}
assert!(cache.contains(&"hot".to_string()));
}
#[test]
fn segment_len_accessors() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
cache.insert("b", 2);
assert_eq!(cache.probationary_len(), 2);
assert_eq!(cache.protected_len(), 0);
cache.get(&"a");
assert_eq!(cache.probationary_len(), 1);
assert_eq!(cache.protected_len(), 1);
}
}
mod eviction_behavior {
use super::*;
#[test]
fn eviction_occurs_when_over_capacity() {
let mut cache = SlruCore::new(5, 0.2);
for i in 0..10 {
cache.insert(i, i * 10);
}
assert_eq!(cache.len(), 5);
}
#[test]
fn probationary_evicts_lru_order() {
let mut cache = SlruCore::new(5, 0.4);
cache.insert("first", 1);
cache.insert("second", 2);
cache.insert("third", 3);
cache.insert("fourth", 4);
cache.insert("fifth", 5);
cache.insert("sixth", 6);
assert!(!cache.contains(&"first"));
assert_eq!(cache.len(), 5);
}
#[test]
fn protected_evicts_lru_when_probationary_under_cap() {
let mut cache = SlruCore::new(5, 0.4);
cache.insert("p1", 1);
cache.get(&"p1");
cache.insert("p2", 2);
cache.get(&"p2");
cache.insert("p3", 3);
cache.get(&"p3");
cache.insert("new1", 10);
cache.insert("new2", 20);
cache.insert("new3", 30);
assert!(!cache.contains(&"p1"));
assert_eq!(cache.len(), 5);
}
#[test]
fn scan_items_evicted_before_hot_items() {
let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
cache.insert("hot1".to_string(), 1);
cache.get(&"hot1".to_string());
cache.insert("hot2".to_string(), 2);
cache.get(&"hot2".to_string());
for i in 0..20 {
cache.insert(format!("scan{}", i), i);
}
assert!(cache.contains(&"hot1".to_string()));
assert!(cache.contains(&"hot2".to_string()));
assert_eq!(cache.len(), 10);
}
#[test]
fn eviction_removes_from_index() {
let mut cache = SlruCore::new(3, 0.33);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
assert!(cache.contains(&"a"));
cache.insert("d", 4);
assert!(!cache.contains(&"a"));
assert_eq!(cache.get(&"a"), None);
}
}
mod scan_resistance {
use super::*;
#[test]
fn scan_does_not_pollute_protected() {
let mut cache = SlruCore::new(100, 0.25);
for i in 0..50 {
let key = format!("working{}", i);
cache.insert(key.clone(), i);
cache.get(&key);
}
for i in 0..200 {
cache.insert(format!("scan{}", i), i);
}
let mut working_set_hits = 0;
for i in 0..50 {
if cache.contains(&format!("working{}", i)) {
working_set_hits += 1;
}
}
assert!(
working_set_hits >= 40,
"Working set should survive scan, but only {} items remained",
working_set_hits
);
}
#[test]
fn one_time_access_stays_in_probationary() {
let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
cache.insert("once".to_string(), 1);
for i in 0..5 {
cache.insert(format!("other{}", i), i);
}
cache.get(&"once".to_string());
for i in 0..10 {
cache.insert(format!("new{}", i), i);
}
assert!(cache.contains(&"once".to_string()));
}
#[test]
fn repeated_scans_dont_evict_hot_items() {
let mut cache = SlruCore::new(20, 0.25);
for i in 0..10 {
let key = format!("hot{}", i);
cache.insert(key.clone(), i);
cache.get(&key);
cache.get(&key);
cache.get(&key);
}
for scan in 0..3 {
for i in 0..30 {
cache.insert(format!("scan{}_{}", scan, i), i);
}
}
let mut hot_survivors = 0;
for i in 0..10 {
if cache.contains(&format!("hot{}", i)) {
hot_survivors += 1;
}
}
assert!(
hot_survivors >= 8,
"Hot items should survive scans, but only {} survived",
hot_survivors
);
}
}
mod edge_cases {
use super::*;
#[test]
fn single_capacity_cache() {
let mut cache = SlruCore::new(1, 0.5);
cache.insert("a", 1);
assert_eq!(cache.get(&"a"), Some(&1));
cache.insert("b", 2);
assert!(!cache.contains(&"a"));
assert_eq!(cache.get(&"b"), Some(&2));
}
#[test]
fn zero_probationary_fraction() {
let mut cache = SlruCore::new(10, 0.0);
for i in 0..10 {
cache.insert(i, i * 10);
}
assert_eq!(cache.len(), 10);
cache.insert(100, 1000);
assert_eq!(cache.len(), 10);
}
#[test]
fn one_hundred_percent_probationary() {
let mut cache = SlruCore::new(10, 1.0);
for i in 0..10 {
cache.insert(i, i * 10);
}
for i in 0..10 {
cache.get(&i);
}
assert_eq!(cache.len(), 10);
}
#[test]
fn get_after_update() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("key", "v1");
assert_eq!(cache.get(&"key"), Some(&"v1"));
cache.insert("key", "v2");
assert_eq!(cache.get(&"key"), Some(&"v2"));
cache.insert("key", "v3");
cache.insert("key", "v4");
assert_eq!(cache.get(&"key"), Some(&"v4"));
}
#[test]
fn large_capacity() {
let mut cache = SlruCore::new(10000, 0.25);
for i in 0..10000 {
cache.insert(i, i * 2);
}
assert_eq!(cache.len(), 10000);
assert_eq!(cache.get(&5000), Some(&10000));
assert_eq!(cache.get(&9999), Some(&19998));
}
#[test]
fn empty_cache_operations() {
let mut cache: SlruCore<i32, i32> = SlruCore::new(100, 0.25);
assert!(cache.is_empty());
assert_eq!(cache.get(&1), None);
assert!(!cache.contains(&1));
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn small_fractions() {
let mut cache = SlruCore::new(100, 0.01);
for i in 0..10 {
cache.insert(i, i);
}
assert_eq!(cache.len(), 10);
}
#[test]
fn string_keys_and_values() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert(String::from("hello"), String::from("world"));
cache.insert(String::from("foo"), String::from("bar"));
assert_eq!(
cache.get(&String::from("hello")),
Some(&String::from("world"))
);
assert_eq!(cache.get(&String::from("foo")), Some(&String::from("bar")));
}
#[test]
fn integer_keys() {
let mut cache = SlruCore::new(100, 0.25);
for i in 0..50 {
cache.insert(i, format!("value_{}", i));
}
assert_eq!(cache.get(&25), Some(&String::from("value_25")));
assert_eq!(cache.get(&49), Some(&String::from("value_49")));
}
#[test]
#[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
fn negative_fraction_panics() {
let _cache: SlruCore<i32, i32> = SlruCore::new(100, -0.5);
}
#[test]
#[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
fn fraction_above_one_panics() {
let _cache: SlruCore<i32, i32> = SlruCore::new(100, 1.5);
}
#[test]
#[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
fn nan_fraction_panics() {
let _cache: SlruCore<i32, i32> = SlruCore::new(100, f64::NAN);
}
}
mod boundary_tests {
use super::*;
#[test]
fn exact_capacity_no_eviction() {
let mut cache = SlruCore::new(10, 0.3);
for i in 0..10 {
cache.insert(i, i);
}
assert_eq!(cache.len(), 10);
for i in 0..10 {
assert!(cache.contains(&i));
}
}
#[test]
fn one_over_capacity_triggers_eviction() {
let mut cache = SlruCore::new(10, 0.3);
for i in 0..10 {
cache.insert(i, i);
}
cache.insert(10, 10);
assert_eq!(cache.len(), 10);
assert!(!cache.contains(&0));
assert!(cache.contains(&10));
}
#[test]
fn probationary_cap_boundary() {
let mut cache = SlruCore::new(10, 0.3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
assert_eq!(cache.len(), 3);
cache.insert("d", 4);
assert_eq!(cache.len(), 4);
for key in &["a", "b", "c", "d"] {
assert!(cache.contains(key));
}
}
#[test]
fn promotion_fills_protected() {
let mut cache = SlruCore::new(10, 0.3);
for i in 0..5 {
cache.insert(i, i);
}
for i in 0..5 {
cache.get(&i);
}
for i in 5..10 {
cache.insert(i, i);
}
assert_eq!(cache.len(), 10);
cache.insert(10, 10);
assert_eq!(cache.len(), 10);
}
}
mod regression_tests {
use super::*;
#[test]
fn promotion_actually_moves_to_protected_segment() {
let mut cache: SlruCore<String, i32> = SlruCore::new(5, 0.4);
cache.insert("key".to_string(), 0);
cache.get(&"key".to_string());
cache.insert("p1".to_string(), 1);
cache.insert("p2".to_string(), 2);
cache.insert("p3".to_string(), 3);
cache.insert("p4".to_string(), 4);
assert!(
cache.contains(&"key".to_string()),
"Promoted item should be in protected segment and survive probationary eviction"
);
}
#[test]
fn update_preserves_segment_position() {
let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
cache.insert("key".to_string(), 1);
cache.get(&"key".to_string());
cache.insert("key".to_string(), 2);
assert_eq!(cache.get(&"key".to_string()), Some(&2));
for i in 0..15 {
cache.insert(format!("other{}", i), i);
}
assert!(cache.contains(&"key".to_string()));
}
#[test]
fn eviction_order_consistency() {
for _ in 0..10 {
let mut cache = SlruCore::new(5, 0.4);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.insert("d", 4);
cache.insert("e", 5);
cache.insert("f", 6);
assert!(!cache.contains(&"a"), "First item should be evicted");
assert!(cache.contains(&"f"), "New item should exist");
}
}
}
mod workload_simulation {
use super::*;
#[test]
fn database_buffer_pool_workload() {
let mut cache = SlruCore::new(100, 0.25);
for i in 0..10 {
let key = format!("index_page_{}", i);
cache.insert(key.clone(), format!("index_data_{}", i));
cache.get(&key);
cache.get(&key);
}
for i in 0..200 {
cache.insert(format!("table_page_{}", i), format!("row_data_{}", i));
}
let mut index_hits = 0;
for i in 0..10 {
if cache.contains(&format!("index_page_{}", i)) {
index_hits += 1;
}
}
assert!(
index_hits >= 8,
"Index pages should survive table scan, got {} hits",
index_hits
);
}
#[test]
fn web_cache_simulation() {
let mut cache: SlruCore<String, String> = SlruCore::new(50, 0.3);
let popular = vec!["home", "about", "products", "contact"];
for page in &popular {
cache.insert(page.to_string(), format!("{}_content", page));
cache.get(&page.to_string());
cache.get(&page.to_string());
}
for i in 0..100 {
cache.insert(format!("blog_post_{}", i), format!("content_{}", i));
}
for page in &popular {
assert!(
cache.contains(&page.to_string()),
"Popular page '{}' should survive",
page
);
}
}
#[test]
fn mixed_workload() {
let mut cache = SlruCore::new(100, 0.25);
for i in 0..30 {
let key = format!("working_{}", i);
cache.insert(key.clone(), i);
cache.get(&key);
}
for round in 0..5 {
for i in (0..30).step_by(3) {
cache.get(&format!("working_{}", i));
}
for i in 0..20 {
cache.insert(format!("round_{}_{}", round, i), i);
}
}
let mut working_set_hits = 0;
for i in (0..30).step_by(3) {
if cache.contains(&format!("working_{}", i)) {
working_set_hits += 1;
}
}
assert!(
working_set_hits >= 8,
"Frequently accessed working set should survive, got {} hits",
working_set_hits
);
}
}
#[test]
#[cfg(debug_assertions)]
fn validate_invariants_after_operations() {
let mut cache = SlruCore::new(10, 0.3);
for i in 1..=10 {
cache.insert(i, i * 100);
}
cache.validate_invariants();
for _ in 0..3 {
cache.get(&1);
cache.get(&2);
}
cache.validate_invariants();
cache.insert(11, 1100);
cache.validate_invariants();
cache.insert(12, 1200);
cache.validate_invariants();
cache.clear();
cache.validate_invariants();
assert_eq!(cache.len(), 0);
}
#[test]
#[cfg(debug_assertions)]
fn validate_invariants_with_segment_transitions() {
let mut cache = SlruCore::new(5, 0.4);
cache.insert(1, 100);
cache.insert(2, 200);
cache.insert(3, 300);
cache.get(&1);
cache.validate_invariants();
cache.get(&2);
cache.validate_invariants();
cache.insert(4, 400);
cache.insert(5, 500);
cache.validate_invariants();
cache.insert(6, 600);
cache.validate_invariants();
assert_eq!(cache.len(), 5);
}
#[test]
fn zero_capacity_rejects_inserts() {
let mut cache: SlruCore<&str, i32> = SlruCore::new(0, 0.25);
assert_eq!(cache.capacity(), 0);
cache.insert("key", 42);
assert_eq!(
cache.len(),
0,
"SlruCore with capacity=0 should reject inserts"
);
}
#[test]
fn trait_insert_returns_old_value() {
let mut cache: SlruCore<&str, i32> = SlruCore::new(10, 0.25);
let first = CoreCache::insert(&mut cache, "key", 1);
assert_eq!(first, None);
let second = CoreCache::insert(&mut cache, "key", 2);
assert_eq!(
second,
Some(1),
"Second insert via trait should return old value"
);
}
#[test]
fn inherent_insert_updates_value() {
let mut cache: SlruCore<&str, i32> = SlruCore::new(10, 0.25);
cache.insert("key", 1);
cache.insert("key", 2);
assert_eq!(cache.get(&"key"), Some(&2));
}
#[test]
fn default_creates_zero_capacity() {
let cache: SlruCore<String, i32> = SlruCore::default();
assert_eq!(cache.capacity(), 0);
assert!(cache.is_empty());
}
#[test]
fn clone_preserves_entries() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
cache.insert("b", 2);
cache.get(&"a");
let cloned = cache.clone();
assert_eq!(cloned.len(), 2);
assert_eq!(cloned.peek(&"a"), Some(&1));
assert_eq!(cloned.peek(&"b"), Some(&2));
assert_eq!(cloned.capacity(), 100);
}
#[test]
fn clone_is_independent() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
let mut cloned = cache.clone();
cloned.insert("a", 999);
cloned.insert("b", 2);
assert_eq!(cache.peek(&"a"), Some(&1));
assert!(!cache.contains(&"b"));
}
#[test]
fn from_iterator() {
let data = vec![("a", 1), ("b", 2), ("c", 3)];
let cache: SlruCore<&str, i32> = data.into_iter().collect();
assert_eq!(cache.len(), 3);
assert_eq!(cache.peek(&"a"), Some(&1));
assert_eq!(cache.peek(&"b"), Some(&2));
assert_eq!(cache.peek(&"c"), Some(&3));
}
#[test]
fn into_iterator_owned() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
let mut items: Vec<_> = cache.into_iter().collect();
items.sort_by_key(|(k, _)| *k);
assert_eq!(items, vec![("a", 1), ("b", 2), ("c", 3)]);
}
#[test]
fn into_iterator_ref() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
cache.insert("b", 2);
let mut items: Vec<_> = (&cache).into_iter().collect();
items.sort_by_key(|(k, _)| **k);
assert_eq!(items, vec![(&"a", &1), (&"b", &2)]);
}
#[test]
fn iter_exact_size() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
cache.insert("b", 2);
cache.get(&"a");
let iter = cache.iter();
assert_eq!(iter.len(), 2);
}
#[test]
fn extend_adds_entries() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
cache.extend(vec![("b", 2), ("c", 3)]);
assert_eq!(cache.len(), 3);
assert_eq!(cache.peek(&"b"), Some(&2));
}
#[test]
fn debug_impl_works() {
let mut cache = SlruCore::new(100, 0.25);
cache.insert("a", 1);
let debug = format!("{:?}", cache);
assert!(debug.contains("SlruCore"));
assert!(debug.contains("capacity"));
}
}