use crate::ds::GhostList;
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::CarMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::CarMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{CarMetricsRecorder, CoreMetricsRecorder, MetricsSnapshotProvider};
use crate::prelude::ReadOnlyCache;
use crate::traits::{CoreCache, MutableCache};
use rustc_hash::FxHashMap;
use std::hash::Hash;
use std::iter::FusedIterator;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Ring {
Recent,
Frequent,
}
struct SlotPayload<K, V> {
key: K,
value: V,
}
impl<K: Clone, V: Clone> Clone for SlotPayload<K, V> {
fn clone(&self) -> Self {
Self {
key: self.key.clone(),
value: self.value.clone(),
}
}
}
#[must_use]
pub struct CarCore<K, V> {
index: FxHashMap<K, usize>,
slots: Vec<Option<SlotPayload<K, V>>>,
referenced: Vec<bool>,
ring_kind: Vec<Ring>,
ring_next: Vec<usize>,
ring_prev: Vec<usize>,
hand_recent: Option<usize>,
hand_frequent: Option<usize>,
free: Vec<usize>,
ghost_recent: GhostList<K>,
ghost_frequent: GhostList<K>,
target_recent_size: usize,
recent_len: usize,
frequent_len: usize,
capacity: usize,
#[cfg(feature = "metrics")]
metrics: CarMetrics,
}
impl<K, V> CarCore<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
pub fn new(capacity: usize) -> Self {
let mut slots = Vec::with_capacity(capacity);
slots.resize_with(capacity, || None);
let mut free = Vec::with_capacity(capacity);
for i in (0..capacity).rev() {
free.push(i);
}
Self {
index: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
slots,
referenced: vec![false; capacity],
ring_kind: vec![Ring::Recent; capacity],
ring_next: vec![0; capacity],
ring_prev: vec![0; capacity],
hand_recent: None,
hand_frequent: None,
free,
ghost_recent: GhostList::new(capacity),
ghost_frequent: GhostList::new(capacity),
target_recent_size: capacity / 2,
recent_len: 0,
frequent_len: 0,
capacity,
#[cfg(feature = "metrics")]
metrics: CarMetrics::default(),
}
}
#[inline(always)]
fn alloc_slot(&mut self) -> Option<usize> {
self.free.pop()
}
#[inline(always)]
fn free_slot(&mut self, idx: usize) {
self.slots[idx] = None;
self.referenced[idx] = false;
self.free.push(idx);
}
#[inline(always)]
fn link_before_hand(&mut self, idx: usize, list: Ring) {
self.ring_kind[idx] = list;
let hand_ref = match list {
Ring::Recent => &mut self.hand_recent,
Ring::Frequent => &mut self.hand_frequent,
};
match *hand_ref {
None => {
self.ring_next[idx] = idx;
self.ring_prev[idx] = idx;
*hand_ref = Some(idx);
},
Some(h) => {
let h_prev = self.ring_prev[h];
self.ring_next[idx] = h;
self.ring_prev[idx] = h_prev;
self.ring_next[h_prev] = idx;
self.ring_prev[h] = idx;
},
}
}
#[inline(always)]
fn unlink(&mut self, idx: usize) {
let list = self.ring_kind[idx];
let hand_ref = match list {
Ring::Recent => &mut self.hand_recent,
Ring::Frequent => &mut self.hand_frequent,
};
let next = self.ring_next[idx];
let prev = self.ring_prev[idx];
if next == idx {
*hand_ref = None;
} else {
self.ring_next[prev] = next;
self.ring_prev[next] = prev;
if *hand_ref == Some(idx) {
*hand_ref = Some(next);
}
}
}
fn replace(&mut self, ghost_frequent_hit: bool) -> bool {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
for _ in 0..(2 * self.capacity + 1) {
#[cfg(feature = "metrics")]
self.metrics.record_hand_sweep();
let evict_from_recent = if self.recent_len > 0
&& (self.recent_len > self.target_recent_size
|| (self.recent_len == self.target_recent_size && ghost_frequent_hit))
{
true
} else if self.frequent_len > 0 {
false
} else {
self.recent_len > 0
};
if evict_from_recent {
let h = match self.hand_recent {
Some(h) => h,
None => continue,
};
if self.referenced[h] {
self.referenced[h] = false;
self.unlink(h);
self.recent_len -= 1;
self.link_before_hand(h, Ring::Frequent);
self.frequent_len += 1;
#[cfg(feature = "metrics")]
self.metrics.record_recent_to_frequent_promotion();
} else {
let key = self.slots[h].as_ref().unwrap().key.clone();
self.unlink(h);
self.index.remove(&key);
self.recent_len -= 1;
self.free_slot(h);
self.ghost_recent.record(key);
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
return true;
}
} else {
let h = match self.hand_frequent {
Some(h) => h,
None => continue,
};
if self.referenced[h] {
self.referenced[h] = false;
self.hand_frequent = Some(self.ring_next[h]);
} else {
let key = self.slots[h].as_ref().unwrap().key.clone();
self.unlink(h);
self.index.remove(&key);
self.frequent_len -= 1;
self.free_slot(h);
self.ghost_frequent.record(key);
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
return true;
}
}
}
false
}
fn adapt(&mut self, ghost_recent_hit: bool, ghost_frequent_hit: bool) {
if ghost_recent_hit {
let delta = if !self.ghost_recent.is_empty() {
(self.ghost_frequent.len() / self.ghost_recent.len()).max(1)
} else {
1
};
self.target_recent_size = (self.target_recent_size + delta).min(self.capacity);
#[cfg(feature = "metrics")]
self.metrics.record_target_increase();
} else if ghost_frequent_hit {
let delta = if !self.ghost_frequent.is_empty() {
(self.ghost_recent.len() / self.ghost_frequent.len()).max(1)
} else {
1
};
self.target_recent_size = self.target_recent_size.saturating_sub(delta);
#[cfg(feature = "metrics")]
self.metrics.record_target_decrease();
}
}
fn insert_into_ring(&mut self, key: K, value: V, list: Ring) -> bool {
let idx = match self.alloc_slot() {
Some(idx) => idx,
None => return false,
};
self.slots[idx] = Some(SlotPayload {
key: key.clone(),
value,
});
self.referenced[idx] = false;
self.link_before_hand(idx, list);
self.index.insert(key, idx);
match list {
Ring::Recent => self.recent_len += 1,
Ring::Frequent => self.frequent_len += 1,
}
true
}
pub fn target_recent_size(&self) -> usize {
self.target_recent_size
}
pub fn recent_len(&self) -> usize {
self.recent_len
}
pub fn frequent_len(&self) -> usize {
self.frequent_len
}
pub fn ghost_recent_len(&self) -> usize {
self.ghost_recent.len()
}
pub fn ghost_frequent_len(&self) -> usize {
self.ghost_frequent.len()
}
pub fn iter(&self) -> Iter<'_, K, V> {
Iter {
slots: self.slots.iter(),
remaining: self.recent_len + self.frequent_len,
}
}
pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
IterMut {
slots: self.slots.iter_mut(),
remaining: self.recent_len + self.frequent_len,
}
}
#[cfg(any(test, debug_assertions))]
pub fn debug_validate_invariants(&self)
where
K: std::fmt::Debug,
{
assert_eq!(
self.recent_len + self.frequent_len,
self.index.len(),
"recent_len({}) + frequent_len({}) != index.len({})",
self.recent_len,
self.frequent_len,
self.index.len()
);
assert!(
self.recent_len + self.frequent_len <= self.capacity,
"total({}) > capacity({})",
self.recent_len + self.frequent_len,
self.capacity
);
assert!(
self.target_recent_size <= self.capacity,
"p({}) > capacity({})",
self.target_recent_size,
self.capacity
);
assert!(self.ghost_recent.len() <= self.capacity);
assert!(self.ghost_frequent.len() <= self.capacity);
assert_eq!(
self.free.len(),
self.capacity - (self.recent_len + self.frequent_len),
"free.len({}) != capacity({}) - total({})",
self.free.len(),
self.capacity,
self.recent_len + self.frequent_len
);
let mut recent_count = 0;
if let Some(start) = self.hand_recent {
let mut cur = start;
loop {
assert!(
self.slots[cur].is_some(),
"recent ring slot {} is empty",
cur
);
assert_eq!(
self.ring_kind[cur],
Ring::Recent,
"recent ring slot {} has ring_kind {:?}",
cur,
self.ring_kind[cur]
);
let slot = self.slots[cur].as_ref().unwrap();
assert!(
self.index.contains_key(&slot.key),
"recent ring slot {} key not in index",
cur
);
assert_eq!(
self.ring_next[self.ring_prev[cur]], cur,
"recent ring broken at slot {}",
cur
);
assert_eq!(
self.ring_prev[self.ring_next[cur]], cur,
"recent ring broken at slot {}",
cur
);
recent_count += 1;
cur = self.ring_next[cur];
if cur == start {
break;
}
assert!(recent_count <= self.capacity, "recent ring cycle detected");
}
}
assert_eq!(
recent_count, self.recent_len,
"recent ring walk count mismatch"
);
let mut frequent_count = 0;
if let Some(start) = self.hand_frequent {
let mut cur = start;
loop {
assert!(self.slots[cur].is_some());
assert_eq!(self.ring_kind[cur], Ring::Frequent);
let slot = self.slots[cur].as_ref().unwrap();
assert!(self.index.contains_key(&slot.key));
assert_eq!(self.ring_next[self.ring_prev[cur]], cur);
assert_eq!(self.ring_prev[self.ring_next[cur]], cur);
frequent_count += 1;
cur = self.ring_next[cur];
if cur == start {
break;
}
assert!(frequent_count <= self.capacity);
}
}
assert_eq!(
frequent_count, self.frequent_len,
"frequent ring walk count mismatch"
);
for key in self.index.keys() {
assert!(
!self.ghost_recent.contains(key),
"Key {:?} in both cache and ghost_recent",
key
);
assert!(
!self.ghost_frequent.contains(key),
"Key {:?} in both cache and ghost_frequent",
key
);
}
for (key, &idx) in &self.index {
assert!(idx < self.capacity);
let slot = self.slots[idx]
.as_ref()
.expect("index points to empty slot");
assert_eq!(&slot.key, key);
}
for &idx in &self.free {
assert!(idx < self.capacity);
assert!(self.slots[idx].is_none(), "Free slot {} is occupied", idx);
}
}
}
impl<K, V> std::fmt::Debug for CarCore<K, V>
where
K: Clone + Eq + Hash + std::fmt::Debug,
V: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CarCore")
.field("capacity", &self.capacity)
.field("recent_len", &self.recent_len)
.field("frequent_len", &self.frequent_len)
.field("ghost_recent_len", &self.ghost_recent.len())
.field("ghost_frequent_len", &self.ghost_frequent.len())
.field("target_recent_size", &self.target_recent_size)
.field("total_len", &(self.recent_len + self.frequent_len))
.finish()
}
}
impl<K, V> Clone for CarCore<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
fn clone(&self) -> Self {
Self {
index: self.index.clone(),
slots: self.slots.clone(),
referenced: self.referenced.clone(),
ring_kind: self.ring_kind.clone(),
ring_next: self.ring_next.clone(),
ring_prev: self.ring_prev.clone(),
hand_recent: self.hand_recent,
hand_frequent: self.hand_frequent,
free: self.free.clone(),
ghost_recent: self.ghost_recent.clone(),
ghost_frequent: self.ghost_frequent.clone(),
target_recent_size: self.target_recent_size,
recent_len: self.recent_len,
frequent_len: self.frequent_len,
capacity: self.capacity,
#[cfg(feature = "metrics")]
metrics: self.metrics,
}
}
}
impl<K, V> ReadOnlyCache<K, V> for CarCore<K, V>
where
K: Clone + Eq + Hash,
{
fn contains(&self, key: &K) -> bool {
self.index.contains_key(key)
}
fn len(&self) -> usize {
self.recent_len + self.frequent_len
}
fn capacity(&self) -> usize {
self.capacity
}
}
impl<K, V> CoreCache<K, V> for CarCore<K, V>
where
K: Clone + Eq + Hash,
{
fn get(&mut self, key: &K) -> Option<&V> {
let &idx = match self.index.get(key) {
Some(idx) => {
#[cfg(feature = "metrics")]
self.metrics.record_get_hit();
idx
},
None => {
#[cfg(feature = "metrics")]
self.metrics.record_get_miss();
return None;
},
};
self.referenced[idx] = true;
self.slots[idx].as_ref().map(|s| &s.value)
}
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(&idx) = self.index.get(&key) {
if let Some(slot) = self.slots[idx].as_mut() {
let old = std::mem::replace(&mut slot.value, value);
self.referenced[idx] = true;
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
return Some(old);
}
}
let ghost_recent_hit = self.ghost_recent.contains(&key);
let ghost_frequent_hit = self.ghost_frequent.contains(&key);
if ghost_recent_hit {
#[cfg(feature = "metrics")]
self.metrics.record_ghost_recent_hit();
self.adapt(true, false);
self.ghost_recent.remove(&key);
if self.recent_len + self.frequent_len >= self.capacity {
self.replace(false);
}
self.insert_into_ring(key, value, Ring::Frequent);
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
return None;
}
if ghost_frequent_hit {
#[cfg(feature = "metrics")]
self.metrics.record_ghost_frequent_hit();
self.adapt(false, true);
self.ghost_frequent.remove(&key);
if self.recent_len + self.frequent_len >= self.capacity {
self.replace(true);
}
self.insert_into_ring(key, value, Ring::Frequent);
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
return None;
}
if self.recent_len + self.frequent_len >= self.capacity && !self.replace(false) {
return None;
}
self.insert_into_ring(key, value, Ring::Recent);
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
None
}
fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
self.index.clear();
for slot in &mut self.slots {
*slot = None;
}
self.referenced.fill(false);
self.free.clear();
for i in (0..self.capacity).rev() {
self.free.push(i);
}
self.hand_recent = None;
self.hand_frequent = None;
self.ghost_recent.clear();
self.ghost_frequent.clear();
self.target_recent_size = self.capacity / 2;
self.recent_len = 0;
self.frequent_len = 0;
}
}
impl<K, V> MutableCache<K, V> for CarCore<K, V>
where
K: Clone + Eq + Hash,
{
fn remove(&mut self, key: &K) -> Option<V> {
let idx = self.index.remove(key)?;
let list = self.ring_kind[idx];
self.unlink(idx);
match list {
Ring::Recent => self.recent_len -= 1,
Ring::Frequent => self.frequent_len -= 1,
}
let slot = self.slots[idx].take()?;
self.referenced[idx] = false;
self.free.push(idx);
Some(slot.value)
}
}
#[cfg(feature = "metrics")]
impl<K, V> CarCore<K, V>
where
K: Clone + Eq + Hash,
{
pub fn metrics_snapshot(&self) -> CarMetricsSnapshot {
CarMetricsSnapshot {
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,
recent_to_frequent_promotions: self.metrics.recent_to_frequent_promotions,
ghost_recent_hits: self.metrics.ghost_recent_hits,
ghost_frequent_hits: self.metrics.ghost_frequent_hits,
target_increases: self.metrics.target_increases,
target_decreases: self.metrics.target_decreases,
hand_sweeps: self.metrics.hand_sweeps,
cache_len: self.len(),
capacity: self.capacity,
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<CarMetricsSnapshot> for CarCore<K, V>
where
K: Clone + Eq + Hash,
{
fn snapshot(&self) -> CarMetricsSnapshot {
self.metrics_snapshot()
}
}
pub struct Iter<'a, K, V> {
slots: std::slice::Iter<'a, Option<SlotPayload<K, V>>>,
remaining: usize,
}
impl<'a, K, V> Iterator for Iter<'a, K, V> {
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
if let Some(payload) = self.slots.by_ref().flatten().next() {
self.remaining -= 1;
return Some((&payload.key, &payload.value));
}
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> {
slots: std::slice::IterMut<'a, Option<SlotPayload<K, V>>>,
remaining: usize,
}
impl<'a, K, V> Iterator for IterMut<'a, K, V> {
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
if let Some(payload) = self.slots.by_ref().flatten().next() {
self.remaining -= 1;
return Some((&payload.key, &mut payload.value));
}
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> {
slots: std::vec::IntoIter<Option<SlotPayload<K, V>>>,
remaining: usize,
}
impl<K, V> Iterator for IntoIter<K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
if let Some(payload) = self.slots.by_ref().flatten().next() {
self.remaining -= 1;
return Some((payload.key, payload.value));
}
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> IntoIterator for CarCore<K, V> {
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
remaining: self.recent_len + self.frequent_len,
slots: self.slots.into_iter(),
}
}
}
impl<'a, K, V> IntoIterator for &'a CarCore<K, V>
where
K: Clone + Eq + Hash,
{
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, K, V> IntoIterator for &'a mut CarCore<K, V>
where
K: Clone + Eq + Hash,
{
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn car_new_cache() {
let cache: CarCore<String, i32> = CarCore::new(100);
assert_eq!(cache.capacity(), 100);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert_eq!(cache.recent_len(), 0);
assert_eq!(cache.frequent_len(), 0);
assert_eq!(cache.target_recent_size(), 50);
cache.debug_validate_invariants();
}
#[test]
fn car_zero_capacity() {
let mut cache: CarCore<&str, i32> = CarCore::new(0);
assert_eq!(cache.capacity(), 0);
assert_eq!(cache.len(), 0);
cache.insert("key", 1);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"key"));
}
#[test]
fn car_insert_and_get() {
let mut cache = CarCore::new(10);
cache.insert("key1", "value1");
assert_eq!(cache.len(), 1);
assert_eq!(cache.recent_len(), 1);
assert_eq!(cache.frequent_len(), 0);
cache.debug_validate_invariants();
assert_eq!(cache.get(&"key1"), Some(&"value1"));
assert_eq!(cache.recent_len(), 1);
assert_eq!(cache.frequent_len(), 0);
cache.debug_validate_invariants();
}
#[test]
fn car_update_existing() {
let mut cache = CarCore::new(10);
cache.insert("key1", "value1");
let old = cache.insert("key1", "new_value");
assert_eq!(old, Some("value1"));
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&"key1"), Some(&"new_value"));
cache.debug_validate_invariants();
}
#[test]
fn car_eviction_fills_ghost() {
let mut cache = CarCore::new(2);
cache.insert("a", 1);
cache.insert("b", 2);
assert_eq!(cache.len(), 2);
cache.debug_validate_invariants();
cache.insert("c", 3);
assert_eq!(cache.len(), 2);
assert!(cache.ghost_recent_len() + cache.ghost_frequent_len() >= 1);
cache.debug_validate_invariants();
}
#[test]
fn car_ghost_hit_ghost_recent() {
let mut cache = CarCore::new(2);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3); assert!(!cache.contains(&"a"));
cache.debug_validate_invariants();
cache.insert("a", 10);
assert!(cache.contains(&"a"));
assert_eq!(cache.get(&"a"), Some(&10));
cache.debug_validate_invariants();
}
#[test]
fn car_ghost_hit_ghost_frequent() {
let mut cache = CarCore::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.get(&"a");
cache.get(&"b");
cache.insert("d", 4);
cache.debug_validate_invariants();
cache.insert("e", 5);
cache.insert("f", 6);
cache.debug_validate_invariants();
let ghost_frequent_has_entries = cache.ghost_frequent_len() > 0;
if ghost_frequent_has_entries {
cache.debug_validate_invariants();
}
}
#[test]
fn car_remove() {
let mut cache = CarCore::new(10);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
assert_eq!(cache.remove(&"key1"), Some("value1"));
assert_eq!(cache.len(), 1);
assert!(!cache.contains(&"key1"));
assert!(cache.contains(&"key2"));
cache.debug_validate_invariants();
}
#[test]
fn car_remove_nonexistent() {
let mut cache = CarCore::new(10);
cache.insert("key1", "value1");
assert_eq!(cache.remove(&"missing"), None);
assert_eq!(cache.len(), 1);
cache.debug_validate_invariants();
}
#[test]
fn car_clear() {
let mut cache = CarCore::new(10);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
cache.get(&"key1");
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.recent_len(), 0);
assert_eq!(cache.frequent_len(), 0);
assert_eq!(cache.ghost_recent_len(), 0);
assert_eq!(cache.ghost_frequent_len(), 0);
assert!(cache.is_empty());
cache.debug_validate_invariants();
}
#[test]
fn car_ref_bit_protects_from_eviction() {
let mut cache = CarCore::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.get(&"b");
cache.get(&"c");
cache.insert("d", 4);
assert_eq!(cache.len(), 3);
assert!(!cache.contains(&"a"));
assert!(cache.contains(&"b"));
assert!(cache.contains(&"c"));
assert!(cache.contains(&"d"));
cache.debug_validate_invariants();
}
#[test]
fn car_demotion_recent_to_frequent() {
let mut cache = CarCore::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.get(&"a");
cache.get(&"b");
cache.get(&"c");
cache.insert("d", 4);
assert_eq!(cache.len(), 3);
assert!(cache.frequent_len() > 0);
cache.debug_validate_invariants();
}
#[test]
fn car_adaptation_increases_target_on_ghost_recent_hit() {
let mut cache = CarCore::new(4);
let initial_p = cache.target_recent_size();
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.insert("d", 4);
cache.insert("e", 5); cache.debug_validate_invariants();
cache.insert("a", 10);
cache.debug_validate_invariants();
assert!(
cache.target_recent_size() >= initial_p,
"target_recent_size should not decrease on ghost_recent hit: was {}, now {}",
initial_p,
cache.target_recent_size()
);
}
#[test]
fn car_multiple_entries() {
let mut cache = CarCore::new(5);
for i in 0..5 {
cache.insert(i, i * 10);
}
assert_eq!(cache.len(), 5);
cache.debug_validate_invariants();
for i in 0..5 {
assert_eq!(cache.get(&i), Some(&(i * 10)));
}
cache.debug_validate_invariants();
}
#[test]
fn car_capacity_one() {
let mut cache = CarCore::new(1);
cache.insert("a", 1);
assert_eq!(cache.len(), 1);
cache.debug_validate_invariants();
cache.insert("b", 2);
assert_eq!(cache.len(), 1);
assert!(cache.contains(&"b"));
assert!(!cache.contains(&"a"));
cache.debug_validate_invariants();
}
#[test]
fn car_heavy_churn() {
let mut cache = CarCore::new(10);
for i in 0..1000 {
cache.insert(i, i * 10);
if i % 3 == 0 {
cache.get(&(i / 2));
}
if i % 7 == 0 {
cache.remove(&(i / 3));
}
}
assert!(cache.len() <= 10);
cache.debug_validate_invariants();
}
#[test]
fn car_contains_after_eviction() {
let mut cache = CarCore::new(2);
cache.insert("a", 1);
cache.insert("b", 2);
assert!(cache.contains(&"a"));
assert!(cache.contains(&"b"));
cache.insert("c", 3);
assert_eq!(cache.len(), 2);
assert!(cache.contains(&"c"));
cache.debug_validate_invariants();
}
#[test]
fn car_insert_after_remove_reuses_slot() {
let mut cache = CarCore::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.debug_validate_invariants();
cache.remove(&"b");
assert_eq!(cache.len(), 2);
cache.debug_validate_invariants();
cache.insert("d", 4);
assert_eq!(cache.len(), 3);
assert!(cache.contains(&"d"));
cache.debug_validate_invariants();
}
#[test]
fn car_clear_then_reuse() {
let mut cache = CarCore::new(5);
for i in 0..5 {
cache.insert(i, i);
}
cache.clear();
cache.debug_validate_invariants();
for i in 10..15 {
cache.insert(i, i);
}
assert_eq!(cache.len(), 5);
for i in 10..15 {
assert!(cache.contains(&i));
}
cache.debug_validate_invariants();
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_len_within_capacity(
capacity in 1usize..100,
ops in prop::collection::vec((0u32..1000, 0u32..100), 0..200)
) {
let mut cache = CarCore::new(capacity);
for (key, value) in ops {
cache.insert(key, value);
prop_assert!(cache.len() <= cache.capacity());
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_invariants_after_inserts(
capacity in 1usize..50,
ops in prop::collection::vec((0u32..100, 0u32..100), 0..100)
) {
let mut cache = CarCore::new(capacity);
for (key, value) in ops {
cache.insert(key, value);
cache.debug_validate_invariants();
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_get_after_insert(
capacity in 1usize..50,
key in 0u32..100,
value in 0u32..1000
) {
let mut cache = CarCore::new(capacity);
cache.insert(key, value);
if cache.contains(&key) {
prop_assert_eq!(cache.get(&key), Some(&value));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_behavior(
capacity in 1usize..50,
keys in prop::collection::vec(0u32..100, 1..50)
) {
let mut cache = CarCore::new(capacity);
for &key in &keys {
cache.insert(key, key * 10);
}
for &key in &keys[0..keys.len()/2] {
let len_before = cache.len();
let removed = cache.remove(&key);
if removed.is_some() {
prop_assert_eq!(cache.len(), len_before - 1);
prop_assert!(!cache.contains(&key));
}
cache.debug_validate_invariants();
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_empties(
capacity in 1usize..50,
keys in prop::collection::vec(0u32..100, 1..50)
) {
let mut cache = CarCore::new(capacity);
for key in keys {
cache.insert(key, key * 10);
}
cache.clear();
prop_assert!(cache.is_empty());
prop_assert_eq!(cache.len(), 0);
cache.debug_validate_invariants();
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_second_chance_behavior(
capacity in 3usize..10
) {
let mut cache = CarCore::new(capacity);
for i in 0..capacity {
cache.insert(i as u32, i as u32);
}
cache.get(&0);
cache.insert(capacity as u32, capacity as u32);
prop_assert!(cache.contains(&0), "referenced entry 0 should survive");
cache.debug_validate_invariants();
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_duplicate_inserts_no_growth(
capacity in 1usize..30,
key in 0u32..50,
values in prop::collection::vec(0u32..100, 1..50)
) {
let mut cache = CarCore::new(capacity);
cache.insert(key, 0);
let len_after_first = cache.len();
for value in values {
cache.insert(key, value);
prop_assert_eq!(cache.len(), len_after_first);
}
cache.debug_validate_invariants();
}
}
#[derive(Debug, Clone)]
enum Operation {
Insert(u32, u32),
Get(u32),
Remove(u32),
}
fn operation_strategy() -> impl Strategy<Value = Operation> {
prop_oneof![
(0u32..50, 0u32..100).prop_map(|(k, v)| Operation::Insert(k, v)),
(0u32..50).prop_map(Operation::Get),
(0u32..50).prop_map(Operation::Remove),
]
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_arbitrary_ops_maintain_invariants(
capacity in 1usize..30,
ops in prop::collection::vec(operation_strategy(), 0..200)
) {
let mut cache = CarCore::new(capacity);
for op in ops {
match op {
Operation::Insert(k, v) => { cache.insert(k, v); }
Operation::Get(k) => { cache.get(&k); }
Operation::Remove(k) => { cache.remove(&k); }
}
cache.debug_validate_invariants();
prop_assert!(cache.len() <= cache.capacity());
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_interleaved_insert_remove(
capacity in 1usize..30,
ops in prop::collection::vec((0u32..50, any::<bool>()), 0..200)
) {
let mut cache = CarCore::new(capacity);
for (key, should_insert) in ops {
if should_insert {
cache.insert(key, key * 10);
} else {
cache.remove(&key);
}
cache.debug_validate_invariants();
prop_assert!(cache.len() <= cache.capacity());
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_zero_capacity_noop(
ops in prop::collection::vec((0u32..100, 0u32..100), 0..50)
) {
let mut cache = CarCore::<u32, u32>::new(0);
for (key, value) in ops {
cache.insert(key, value);
prop_assert!(cache.is_empty());
prop_assert_eq!(cache.len(), 0);
prop_assert!(!cache.contains(&key));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_capacity_one_single_entry(
keys in prop::collection::vec(0u32..100, 1..50)
) {
let mut cache = CarCore::new(1);
for key in keys {
cache.insert(key, key * 10);
prop_assert!(cache.len() <= 1);
cache.debug_validate_invariants();
}
}
}
}
#[cfg(test)]
mod fuzz_tests {
use super::*;
pub fn fuzz_arbitrary_operations(data: &[u8]) {
if data.len() < 2 {
return;
}
let capacity = (data[0] as usize % 50).max(1);
let mut cache = CarCore::new(capacity);
let mut idx = 1;
while idx + 2 < data.len() {
let op = data[idx] % 4;
let key = data[idx + 1] as u32;
let value = data[idx + 2] as u32;
match op {
0 => {
cache.insert(key, value);
},
1 => {
cache.get(&key);
},
2 => {
cache.remove(&key);
},
3 => {
cache.insert(key, value);
cache.get(&key);
},
_ => unreachable!(),
}
cache.debug_validate_invariants();
assert!(cache.len() <= cache.capacity());
idx += 3;
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn test_fuzz_smoke() {
let inputs = vec![
vec![5, 0, 1, 2, 1, 3, 4, 2, 5, 6],
vec![10, 6, 7, 8, 5, 9, 10, 0, 1, 2],
vec![1, 0, 0, 0, 1, 1, 1, 2, 2, 2],
vec![3, 0, 10, 20, 0, 10, 30, 0, 20, 40, 0, 30, 50],
];
for input in inputs {
fuzz_arbitrary_operations(&input);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn test_fuzz_eviction_patterns() {
let inputs = vec![
vec![
5, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 1, 1, 0, 1, 3, 0,
],
vec![2, 0, 1, 2, 0, 3, 4, 0, 5, 6, 0, 1, 7, 0, 3, 8],
];
for input in inputs {
fuzz_arbitrary_operations(&input);
}
}
}