use crate::ds::{IntrusiveList, SlotId};
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::TwoQMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::TwoQMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{CoreMetricsRecorder, MetricsSnapshotProvider, TwoQMetricsRecorder};
use crate::prelude::ReadOnlyCache;
use crate::traits::CoreCache;
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
use std::hash::Hash;
use std::ptr::NonNull;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum QueueKind {
Probation,
Protected,
}
#[repr(C)]
struct Node<K, V> {
prev: Option<NonNull<Node<K, V>>>,
next: Option<NonNull<Node<K, V>>>,
queue: QueueKind,
key: K,
value: V,
}
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct LruQueue<T> {
list: IntrusiveList<T>,
}
#[allow(dead_code)]
pub(crate) struct TwoQWithGhost<K, V> {
core: TwoQCore<K, V>,
ghost_list: VecDeque<K>,
ghost_list_cap: usize,
}
impl<K, V> std::fmt::Debug for TwoQWithGhost<K, V>
where
K: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TwoQWithGhost")
.field("core", &self.core)
.field("ghost_list_len", &self.ghost_list.len())
.field("ghost_list_cap", &self.ghost_list_cap)
.finish()
}
}
pub struct TwoQCore<K, V> {
map: FxHashMap<K, NonNull<Node<K, V>>>,
probation_head: Option<NonNull<Node<K, V>>>,
probation_tail: Option<NonNull<Node<K, V>>>,
probation_len: usize,
protected_head: Option<NonNull<Node<K, V>>>,
protected_tail: Option<NonNull<Node<K, V>>>,
protected_len: usize,
probation_cap: usize,
protected_cap: usize,
#[cfg(feature = "metrics")]
metrics: TwoQMetrics,
}
unsafe impl<K, V> Send for TwoQCore<K, V>
where
K: Send,
V: Send,
{
}
unsafe impl<K, V> Sync for TwoQCore<K, V>
where
K: Sync,
V: Sync,
{
}
impl<T> Default for LruQueue<T> {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
impl<T> LruQueue<T> {
#[must_use]
pub fn new() -> Self {
Self {
list: IntrusiveList::new(),
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
list: IntrusiveList::with_capacity(capacity),
}
}
pub fn is_empty(&self) -> bool {
self.list.len() == 0
}
pub fn insert(&mut self, id: T) -> SlotId {
self.list.push_front(id)
}
pub fn touch(&mut self, id: SlotId) -> bool {
self.list.move_to_front(id)
}
pub fn evict(&mut self) -> Option<T> {
self.list.pop_back()
}
pub fn remove(&mut self, id: SlotId) -> Option<T> {
self.list.remove(id)
}
pub fn len(&self) -> usize {
self.list.len()
}
pub fn push_front(&mut self, id: T) -> SlotId {
self.list.push_front(id)
}
pub fn move_to_front(&mut self, id: SlotId) -> bool {
self.list.move_to_front(id)
}
pub fn pop_back(&mut self) -> Option<T> {
self.list.pop_back()
}
}
impl<K, V> TwoQCore<K, V> {
#[inline(always)]
fn pop_probation_tail(&mut self) -> Option<Box<Node<K, V>>> {
self.probation_tail.map(|tail_ptr| unsafe {
let node = Box::from_raw(tail_ptr.as_ptr());
self.probation_tail = node.prev;
match self.probation_tail {
Some(mut t) => t.as_mut().next = None,
None => self.probation_head = None,
}
self.probation_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
})
}
}
impl<K, V> TwoQCore<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
#[must_use]
pub fn new(protected_cap: usize, a1_frac: f64) -> Self {
assert!(
(0.0..=1.0).contains(&a1_frac),
"a1_frac must be between 0.0 and 1.0, got {a1_frac}"
);
let probation_cap = (protected_cap as f64 * a1_frac) as usize;
let total_cap = protected_cap + probation_cap;
Self {
map: FxHashMap::with_capacity_and_hasher(total_cap, Default::default()),
probation_head: None,
probation_tail: None,
probation_len: 0,
protected_head: None,
protected_tail: None,
protected_len: 0,
probation_cap,
protected_cap,
#[cfg(feature = "metrics")]
metrics: TwoQMetrics::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 queue = node.queue;
let (head, tail, len) = match queue {
QueueKind::Probation => (
&mut self.probation_head,
&mut self.probation_tail,
&mut self.probation_len,
),
QueueKind::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_probation_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_mut();
node.prev = None;
node.next = self.probation_head;
node.queue = QueueKind::Probation;
match self.probation_head {
Some(mut h) => h.as_mut().prev = Some(node_ptr),
None => self.probation_tail = Some(node_ptr),
}
self.probation_head = Some(node_ptr);
self.probation_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.queue = QueueKind::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]
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 queue = unsafe { node_ptr.as_ref().queue };
match queue {
QueueKind::Probation => {
#[cfg(feature = "metrics")]
self.metrics.record_a1in_to_am_promotion();
self.detach(node_ptr);
self.attach_protected_head(node_ptr);
},
QueueKind::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.protected_cap == 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,
queue: QueueKind::Probation,
key: key.clone(),
value,
});
let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
self.map.insert(key, node_ptr);
self.attach_probation_head(node_ptr);
None
}
#[inline]
fn evict_if_needed(&mut self) {
if self.len() >= self.protected_cap {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
}
while self.len() >= self.protected_cap {
if self.probation_len > self.probation_cap {
if let Some(node) = self.pop_probation_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();
continue;
}
if let Some(node) = self.pop_probation_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.protected_cap
}
#[inline]
pub fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
pub fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
while self.pop_probation_tail().is_some() {}
while self.pop_protected_tail().is_some() {}
self.map.clear();
}
}
impl<K, V> Drop for TwoQCore<K, V> {
fn drop(&mut self) {
while self.pop_probation_tail().is_some() {}
while self.pop_protected_tail().is_some() {}
}
}
impl<K, V> std::fmt::Debug for TwoQCore<K, V>
where
K: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TwoQCore")
.field("capacity", &self.protected_cap)
.field("probation_cap", &self.probation_cap)
.field("len", &self.map.len())
.field("probation_len", &self.probation_len)
.field("protected_len", &self.protected_len)
.finish_non_exhaustive()
}
}
impl<K, V> ReadOnlyCache<K, V> for TwoQCore<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.protected_cap
}
}
impl<K, V> CoreCache<K, V> for TwoQCore<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
fn insert(&mut self, key: K, value: V) -> Option<V> {
TwoQCore::insert(self, key, value)
}
#[inline]
fn get(&mut self, key: &K) -> Option<&V> {
TwoQCore::get(self, key)
}
fn clear(&mut self) {
TwoQCore::clear(self);
}
}
#[cfg(feature = "metrics")]
impl<K, V> TwoQCore<K, V>
where
K: Clone + Eq + Hash,
{
pub fn metrics_snapshot(&self) -> TwoQMetricsSnapshot {
TwoQMetricsSnapshot {
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,
a1in_to_am_promotions: self.metrics.a1in_to_am_promotions,
a1out_ghost_hits: self.metrics.a1out_ghost_hits,
cache_len: self.len(),
capacity: self.protected_cap,
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<TwoQMetricsSnapshot> for TwoQCore<K, V>
where
K: Clone + Eq + Hash,
{
fn snapshot(&self) -> TwoQMetricsSnapshot {
self.metrics_snapshot()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod lru_queue_tests {
use super::*;
#[test]
fn new_queue_is_empty() {
let lru: LruQueue<i32> = LruQueue::new();
assert!(lru.is_empty());
assert_eq!(lru.len(), 0);
}
#[test]
fn default_creates_empty_queue() {
let lru: LruQueue<&str> = LruQueue::default();
assert!(lru.is_empty());
}
#[test]
fn insert_increases_length() {
let mut lru = LruQueue::new();
lru.insert(1);
assert_eq!(lru.len(), 1);
assert!(!lru.is_empty());
lru.insert(2);
lru.insert(3);
assert_eq!(lru.len(), 3);
}
#[test]
fn evict_returns_lru_item() {
let mut lru = LruQueue::new();
lru.insert("first");
lru.insert("second");
lru.insert("third");
assert_eq!(lru.evict(), Some("first"));
assert_eq!(lru.evict(), Some("second"));
assert_eq!(lru.evict(), Some("third"));
assert_eq!(lru.evict(), None);
}
#[test]
fn touch_moves_to_mru() {
let mut lru = LruQueue::new();
let first = lru.insert("first");
lru.insert("second");
lru.insert("third");
assert!(lru.touch(first));
assert_eq!(lru.evict(), Some("second"));
assert_eq!(lru.evict(), Some("third"));
assert_eq!(lru.evict(), Some("first")); }
#[test]
fn remove_returns_item() {
let mut lru = LruQueue::new();
let id = lru.insert("item");
assert_eq!(lru.len(), 1);
assert_eq!(lru.remove(id), Some("item"));
assert!(lru.is_empty());
}
#[test]
fn remove_from_middle() {
let mut lru = LruQueue::new();
lru.insert("first");
let middle = lru.insert("middle");
lru.insert("last");
assert_eq!(lru.remove(middle), Some("middle"));
assert_eq!(lru.len(), 2);
assert_eq!(lru.evict(), Some("first"));
assert_eq!(lru.evict(), Some("last"));
}
#[test]
fn evict_from_empty_returns_none() {
let mut lru: LruQueue<i32> = LruQueue::new();
assert_eq!(lru.evict(), None);
}
#[test]
fn push_front_alias_works() {
let mut lru = LruQueue::new();
lru.push_front("a");
lru.push_front("b");
assert_eq!(lru.len(), 2);
assert_eq!(lru.pop_back(), Some("a"));
}
#[test]
fn move_to_front_alias_works() {
let mut lru = LruQueue::new();
let a = lru.insert("a");
lru.insert("b");
assert!(lru.move_to_front(a));
assert_eq!(lru.pop_back(), Some("b"));
}
}
mod basic_operations {
use super::*;
#[test]
fn new_cache_is_empty() {
let cache: TwoQCore<&str, i32> = TwoQCore::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 = TwoQCore::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 = TwoQCore::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: TwoQCore<&str, i32> = TwoQCore::new(100, 0.25);
cache.insert("exists", 42);
assert_eq!(cache.get(&"missing"), None);
}
#[test]
fn update_existing_key() {
let mut cache = TwoQCore::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 = TwoQCore::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: TwoQCore<String, i32> = TwoQCore::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 = TwoQCore::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: TwoQCore<i32, i32> = TwoQCore::new(500, 0.25);
assert_eq!(cache.capacity(), 500);
}
}
mod queue_behavior {
use super::*;
#[test]
fn new_insert_goes_to_probation() {
let mut cache = TwoQCore::new(10, 0.3);
cache.insert("key", "value");
assert!(cache.contains(&"key"));
assert_eq!(cache.len(), 1);
}
#[test]
fn get_promotes_from_probation_to_protected() {
let mut cache: TwoQCore<String, i32> = TwoQCore::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 = TwoQCore::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: TwoQCore<String, i32> = TwoQCore::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()));
}
}
mod eviction_behavior {
use super::*;
#[test]
fn eviction_occurs_when_over_capacity() {
let mut cache = TwoQCore::new(5, 0.2);
for i in 0..10 {
cache.insert(i, i * 10);
}
assert_eq!(cache.len(), 5);
}
#[test]
fn probation_evicts_fifo_order() {
let mut cache = TwoQCore::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_probation_under_cap() {
let mut cache = TwoQCore::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: TwoQCore<String, i32> = TwoQCore::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 = TwoQCore::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 = TwoQCore::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_probation() {
let mut cache: TwoQCore<String, i32> = TwoQCore::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 = TwoQCore::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 = TwoQCore::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_probation_fraction() {
let mut cache = TwoQCore::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_probation() {
let mut cache = TwoQCore::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 = TwoQCore::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 = TwoQCore::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: TwoQCore<i32, i32> = TwoQCore::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 = TwoQCore::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 = TwoQCore::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 = TwoQCore::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")));
}
}
mod boundary_tests {
use super::*;
#[test]
fn exact_capacity_no_eviction() {
let mut cache = TwoQCore::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 = TwoQCore::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 probation_cap_boundary() {
let mut cache = TwoQCore::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 = TwoQCore::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_queue() {
let mut cache: TwoQCore<String, i32> = TwoQCore::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 queue and survive probation eviction"
);
}
#[test]
fn update_preserves_queue_position() {
let mut cache: TwoQCore<String, i32> = TwoQCore::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 = TwoQCore::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 = TwoQCore::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: TwoQCore<String, String> = TwoQCore::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 = TwoQCore::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]
fn zero_capacity_rejects_inserts() {
let mut cache: TwoQCore<&str, i32> = TwoQCore::new(0, 0.25);
assert_eq!(cache.capacity(), 0);
cache.insert("key", 42);
assert_eq!(
cache.len(),
0,
"TwoQCore with capacity=0 should reject inserts"
);
}
#[test]
fn trait_insert_returns_old_value() {
let mut cache: TwoQCore<&str, i32> = TwoQCore::new(10, 0.25);
let first = CoreCache::insert(&mut cache, "key", 1);
assert_eq!(first, None, "First insert of new key should return None");
let second = CoreCache::insert(&mut cache, "key", 2);
assert_eq!(second, Some(1), "Second insert should return old value");
}
#[test]
fn inherent_insert_updates_value() {
let mut cache: TwoQCore<&str, i32> = TwoQCore::new(10, 0.25);
cache.insert("key", 1);
cache.insert("key", 2);
assert_eq!(cache.get(&"key"), Some(&2), "Value should be updated to 2");
}
}