use std::borrow::Borrow;
use std::collections::HashMap;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hash};
#[cfg(feature = "concurrency")]
use parking_lot::RwLock;
pub const MAX_CAPACITY: usize = isize::MAX as usize / 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClockRingError {
CapacityTooLarge {
requested: usize,
max: usize,
},
AllocationFailed {
requested: usize,
},
}
impl std::fmt::Display for ClockRingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClockRingError::CapacityTooLarge { requested, max } => {
write!(f, "ClockRing capacity {requested} exceeds maximum {max}")
},
ClockRingError::AllocationFailed { requested } => {
write!(
f,
"ClockRing failed to allocate backing storage for capacity {requested}"
)
},
}
}
}
impl std::error::Error for ClockRingError {}
#[derive(Debug, Clone, Copy, Default)]
pub struct KeysAreTrusted(());
impl KeysAreTrusted {
#[inline]
pub const fn new() -> Self {
Self(())
}
}
#[inline]
fn step(idx: usize, cap: usize) -> usize {
if cap == 0 {
return 0;
}
match idx.checked_add(1) {
Some(next) => next % cap,
None => 0,
}
}
#[derive(Debug, Clone)]
struct Entry<K, V> {
key: K,
value: V,
}
#[must_use]
pub struct ClockRing<K, V, S = RandomState> {
slots: Vec<Option<Entry<K, V>>>,
referenced: Vec<bool>,
index: HashMap<K, usize, S>,
hand: usize,
len: usize,
#[cfg(feature = "metrics")]
sweep_hand_advances: u64,
#[cfg(feature = "metrics")]
sweep_ref_bit_resets: u64,
}
impl<K, V, S> std::fmt::Debug for ClockRing<K, V, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_struct("ClockRing");
dbg.field("len", &self.len)
.field("capacity", &self.slots.len())
.field("hand", &self.hand);
#[cfg(feature = "metrics")]
{
dbg.field("sweep_hand_advances", &self.sweep_hand_advances)
.field("sweep_ref_bit_resets", &self.sweep_ref_bit_resets);
}
dbg.finish_non_exhaustive()
}
}
#[cfg(feature = "concurrency")]
#[must_use]
pub struct ConcurrentClockRing<K, V, S = RandomState> {
inner: RwLock<ClockRing<K, V, S>>,
}
#[cfg(feature = "concurrency")]
impl<K, V, S> std::fmt::Debug for ConcurrentClockRing<K, V, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let inner = self.inner.read();
let mut dbg = f.debug_struct("ConcurrentClockRing");
dbg.field("len", &inner.len)
.field("capacity", &inner.slots.len())
.field("hand", &inner.hand);
#[cfg(feature = "metrics")]
{
dbg.field("sweep_hand_advances", &inner.sweep_hand_advances)
.field("sweep_ref_bit_resets", &inner.sweep_ref_bit_resets);
}
dbg.finish_non_exhaustive()
}
}
#[cfg(feature = "concurrency")]
impl<K, V> ConcurrentClockRing<K, V, RandomState>
where
K: Eq + Hash + Clone,
{
pub fn new(capacity: usize) -> Self {
Self {
inner: RwLock::new(ClockRing::new(capacity)),
}
}
pub fn try_new(capacity: usize) -> Result<Self, ClockRingError> {
Ok(Self {
inner: RwLock::new(ClockRing::try_new(capacity)?),
})
}
}
#[cfg(feature = "concurrency")]
impl<K, V, S> ConcurrentClockRing<K, V, S>
where
K: Eq + Hash + Clone,
S: BuildHasher,
{
pub fn with_hasher(capacity: usize, hash_builder: S, ack: KeysAreTrusted) -> Self {
Self {
inner: RwLock::new(ClockRing::with_hasher(capacity, hash_builder, ack)),
}
}
pub fn try_with_hasher(
capacity: usize,
hash_builder: S,
ack: KeysAreTrusted,
) -> Result<Self, ClockRingError> {
Ok(Self {
inner: RwLock::new(ClockRing::try_with_hasher(capacity, hash_builder, ack)?),
})
}
pub fn capacity(&self) -> usize {
let ring = self.inner.read();
ring.capacity()
}
pub fn len(&self) -> usize {
let ring = self.inner.read();
ring.len()
}
pub fn is_empty(&self) -> bool {
let ring = self.inner.read();
ring.is_empty()
}
pub fn contains<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let ring = self.inner.read();
ring.contains(key)
}
pub fn peek_with<Q, R>(&self, key: &Q, f: impl FnOnce(&V) -> R) -> Option<R>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let ring = self.inner.read();
ring.peek(key).map(f)
}
pub fn get_with<Q, R>(&self, key: &Q, f: impl FnOnce(&V) -> R) -> Option<R>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.write();
ring.get(key).map(f)
}
pub fn get_mut_with<Q, R>(&self, key: &Q, f: impl FnOnce(&mut V) -> R) -> Option<R>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.write();
ring.get_mut(key).map(f)
}
pub fn touch<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.write();
ring.touch(key)
}
pub fn update<Q>(&self, key: &Q, value: V) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.write();
ring.update(key, value)
}
pub fn insert(&self, key: K, value: V) -> Option<(K, V)> {
let (replaced, evicted) = {
let mut ring = self.inner.write();
ring.insert_swap(key, value)
};
drop(replaced);
evicted
}
pub fn remove<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.write();
ring.remove(key)
}
pub fn peek_victim_with<R>(&self, f: impl FnOnce(&K, &V) -> R) -> Option<R> {
let ring = self.inner.read();
ring.peek_victim().map(|(key, value)| f(key, value))
}
pub fn pop_victim(&self) -> Option<(K, V)> {
let mut ring = self.inner.write();
ring.pop_victim()
}
pub fn clear(&self) {
let mut ring = self.inner.write();
ring.clear();
}
pub fn clear_shrink(&self) {
let mut ring = self.inner.write();
ring.clear_shrink();
}
#[must_use]
pub fn approx_bytes(&self) -> usize {
let ring = self.inner.read();
ring.approx_bytes()
}
pub fn reserve_index(&self, additional: usize) {
let mut ring = self.inner.write();
ring.reserve_index(additional);
}
pub fn shrink_to_fit(&self) {
let mut ring = self.inner.write();
ring.shrink_to_fit();
}
pub fn try_update<Q>(&self, key: &Q, value: V) -> Option<Option<V>>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.try_write()?;
Some(ring.update(key, value))
}
pub fn try_insert(&self, key: K, value: V) -> Option<Option<(K, V)>> {
let (replaced, evicted) = {
let mut ring = self.inner.try_write()?;
ring.insert_swap(key, value)
};
drop(replaced);
Some(evicted)
}
pub fn try_remove<Q>(&self, key: &Q) -> Option<Option<V>>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.try_write()?;
Some(ring.remove(key))
}
pub fn try_touch<Q>(&self, key: &Q) -> Option<bool>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.try_write()?;
Some(ring.touch(key))
}
pub fn try_peek_with<Q, R>(&self, key: &Q, f: impl FnOnce(&V) -> R) -> Option<Option<R>>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let ring = self.inner.try_read()?;
Some(ring.peek(key).map(f))
}
pub fn try_get_with<Q, R>(&self, key: &Q, f: impl FnOnce(&V) -> R) -> Option<Option<R>>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.try_write()?;
Some(ring.get(key).map(f))
}
pub fn try_get_mut_with<Q, R>(&self, key: &Q, f: impl FnOnce(&mut V) -> R) -> Option<Option<R>>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut ring = self.inner.try_write()?;
Some(ring.get_mut(key).map(f))
}
pub fn try_peek_victim_with<R>(&self, f: impl FnOnce(&K, &V) -> R) -> Option<Option<R>> {
let ring = self.inner.try_read()?;
Some(ring.peek_victim().map(|(key, value)| f(key, value)))
}
pub fn try_pop_victim(&self) -> Option<Option<(K, V)>> {
let mut ring = self.inner.try_write()?;
Some(ring.pop_victim())
}
pub fn try_clear(&self) -> bool {
if let Some(mut ring) = self.inner.try_write() {
ring.clear();
true
} else {
false
}
}
pub fn try_clear_shrink(&self) -> bool {
if let Some(mut ring) = self.inner.try_write() {
ring.clear_shrink();
true
} else {
false
}
}
}
impl<K, V, S> ClockRing<K, V, S> {
pub fn iter(&self) -> Iter<'_, K, V> {
Iter {
inner: self.slots.iter(),
}
}
pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
IterMut {
inner: self.slots.iter_mut(),
}
}
pub fn keys(&self) -> Keys<'_, K, V> {
Keys { inner: self.iter() }
}
pub fn values(&self) -> Values<'_, K, V> {
Values { inner: self.iter() }
}
pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> {
ValuesMut {
inner: self.iter_mut(),
}
}
}
impl<K, V> ClockRing<K, V, RandomState>
where
K: Eq + Hash + Clone,
{
pub fn new(capacity: usize) -> Self {
Self::try_new(capacity).expect("ClockRing::new: capacity exceeds MAX_CAPACITY")
}
pub fn try_new(capacity: usize) -> Result<Self, ClockRingError> {
Self::try_with_hasher(capacity, RandomState::default(), KeysAreTrusted::new())
}
}
impl<K, V, S> ClockRing<K, V, S>
where
K: Eq + Hash + Clone,
S: BuildHasher,
{
#[track_caller]
pub fn with_hasher(capacity: usize, hash_builder: S, ack: KeysAreTrusted) -> Self {
Self::try_with_hasher(capacity, hash_builder, ack)
.expect("ClockRing::with_hasher: capacity exceeds MAX_CAPACITY")
}
pub fn try_with_hasher(
capacity: usize,
hash_builder: S,
_ack: KeysAreTrusted,
) -> Result<Self, ClockRingError> {
if capacity > MAX_CAPACITY {
return Err(ClockRingError::CapacityTooLarge {
requested: capacity,
max: MAX_CAPACITY,
});
}
let alloc_err = || ClockRingError::AllocationFailed {
requested: capacity,
};
let mut slots: Vec<Option<Entry<K, V>>> = Vec::new();
slots.try_reserve_exact(capacity).map_err(|_| alloc_err())?;
slots.resize_with(capacity, || None);
let mut referenced: Vec<bool> = Vec::new();
referenced
.try_reserve_exact(capacity)
.map_err(|_| alloc_err())?;
referenced.resize(capacity, false);
let mut index = HashMap::with_hasher(hash_builder);
index.try_reserve(capacity).map_err(|_| alloc_err())?;
Ok(Self {
slots,
referenced,
index,
hand: 0,
len: 0,
#[cfg(feature = "metrics")]
sweep_hand_advances: 0,
#[cfg(feature = "metrics")]
sweep_ref_bit_resets: 0,
})
}
pub fn capacity(&self) -> usize {
self.slots.len()
}
pub fn reserve_index(&mut self, additional: usize) {
self.index.reserve(additional);
}
pub fn shrink_to_fit(&mut self) {
self.index.shrink_to_fit();
self.slots.shrink_to_fit();
self.referenced.shrink_to_fit();
}
pub fn clear(&mut self) {
self.index.clear();
for slot in &mut self.slots {
*slot = None;
}
self.referenced.fill(false);
self.len = 0;
self.hand = 0;
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances = 0;
self.sweep_ref_bit_resets = 0;
}
}
pub fn clear_shrink(&mut self) {
self.clear();
self.index.shrink_to_fit();
self.slots.shrink_to_fit();
self.referenced.shrink_to_fit();
}
#[cfg(feature = "metrics")]
#[inline]
pub fn sweep_hand_advances(&self) -> u64 {
self.sweep_hand_advances
}
#[cfg(feature = "metrics")]
#[inline]
pub fn sweep_ref_bit_resets(&self) -> u64 {
self.sweep_ref_bit_resets
}
#[must_use]
pub fn approx_bytes(&self) -> usize {
let index_bytes = self
.index
.capacity()
.saturating_mul(std::mem::size_of::<(K, usize)>());
let slots_bytes = self
.slots
.capacity()
.saturating_mul(std::mem::size_of::<Option<Entry<K, V>>>());
let ref_bytes = self
.referenced
.capacity()
.saturating_mul(std::mem::size_of::<bool>());
std::mem::size_of::<Self>()
.saturating_add(index_bytes)
.saturating_add(slots_bytes)
.saturating_add(ref_bytes)
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn contains<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.index.contains_key(key)
}
pub fn peek<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let idx = *self.index.get(key)?;
self.slots.get(idx)?.as_ref().map(|entry| &entry.value)
}
#[must_use]
pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let idx = *self.index.get(key)?;
let entry = self.slots.get(idx)?.as_ref()?;
self.referenced[idx] = true;
Some(&entry.value)
}
#[must_use]
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let idx = *self.index.get(key)?;
let slot = self.slots.get_mut(idx)?;
if slot.is_none() {
return None;
}
self.referenced[idx] = true;
slot.as_mut().map(|entry| &mut entry.value)
}
pub fn touch<Q>(&mut self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let idx = match self.index.get(key) {
Some(idx) => *idx,
None => return false,
};
match self.slots.get(idx) {
Some(Some(_)) => {
self.referenced[idx] = true;
true
},
_ => false,
}
}
pub fn update<Q>(&mut self, key: &Q, value: V) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let idx = *self.index.get(key)?;
let entry = self.slots.get_mut(idx)?.as_mut()?;
let old = std::mem::replace(&mut entry.value, value);
self.referenced[idx] = true;
Some(old)
}
pub fn insert(&mut self, key: K, value: V) -> Option<(K, V)> {
let (replaced, evicted) = self.insert_swap(key, value);
drop(replaced);
evicted
}
pub fn insert_swap(&mut self, key: K, value: V) -> (Option<V>, Option<(K, V)>) {
if self.capacity() == 0 {
return (None, None);
}
if let Some(&idx) = self.index.get(&key) {
if let Some(entry) = self.slots.get_mut(idx).and_then(|slot| slot.as_mut()) {
let old = std::mem::replace(&mut entry.value, value);
self.referenced[idx] = true;
return (Some(old), None);
}
debug_assert!(false, "index entry points to empty slot in insert_swap");
self.index.remove(&key);
if idx < self.slots.len() {
let entry_key = key.clone();
self.slots[idx] = Some(Entry {
key: entry_key,
value,
});
if let Some(referenced) = self.referenced.get_mut(idx) {
*referenced = false;
}
self.index.insert(key, idx);
self.len = self.slots.iter().filter(|slot| slot.is_some()).count();
let cap = self.slots.len();
self.hand = step(idx, cap);
return (None, None);
}
return (Some(value), None);
}
if self.len < self.capacity() {
let cap = self.capacity();
let mut idx = self.hand % cap;
for _ in 0..cap {
if self.slots[idx].is_none() {
let entry_key = key.clone();
self.slots[idx] = Some(Entry {
key: entry_key,
value,
});
self.referenced[idx] = false;
self.index.insert(key, idx);
self.len = self.len.saturating_add(1);
self.hand = step(idx, cap);
return (None, None);
}
idx = step(idx, cap);
}
unreachable!("len < capacity but no empty slot found");
}
let cap = self.capacity();
for _ in 0..cap.saturating_mul(2) {
let idx = self.hand;
if self.referenced[idx] {
self.referenced[idx] = false;
#[cfg(feature = "metrics")]
{
self.sweep_ref_bit_resets = self.sweep_ref_bit_resets.saturating_add(1);
}
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances = self.sweep_hand_advances.saturating_add(1);
}
continue;
}
let entry_key = key.clone();
let victim_key_ref = match &self.slots[idx] {
Some(entry) => &entry.key,
None => unreachable!("occupied slot missing under full ring"),
};
let removed = self.index.remove(victim_key_ref);
debug_assert_eq!(
removed,
Some(idx),
"index/slot disagreement in insert_swap eviction"
);
let evicted = match self.slots[idx].take() {
Some(entry) => entry,
None => unreachable!("slot vanished between borrow and take"),
};
self.slots[idx] = Some(Entry {
key: entry_key,
value,
});
self.referenced[idx] = false;
self.index.insert(key, idx);
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances = self.sweep_hand_advances.saturating_add(1);
}
return (None, Some((evicted.key, evicted.value)));
}
unreachable!("insert sweep exceeded 2*capacity without finding victim");
}
#[must_use]
pub fn peek_victim(&self) -> Option<(&K, &V)> {
if self.capacity() == 0 || self.len == 0 {
return None;
}
let cap = self.capacity();
let mut idx = self.hand % cap;
for _ in 0..cap {
if let Some(entry) = self.slots.get(idx).and_then(|slot| slot.as_ref()) {
if !self.referenced[idx] {
return Some((&entry.key, &entry.value));
}
}
idx = step(idx, cap);
}
None
}
pub fn pop_victim(&mut self) -> Option<(K, V)> {
if self.capacity() == 0 || self.len == 0 {
return None;
}
let cap = self.capacity();
for _ in 0..cap.saturating_mul(2) {
let idx = self.hand;
if self.slots[idx].is_some() {
if self.referenced[idx] {
self.referenced[idx] = false;
#[cfg(feature = "metrics")]
{
self.sweep_ref_bit_resets = self.sweep_ref_bit_resets.saturating_add(1);
}
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances = self.sweep_hand_advances.saturating_add(1);
}
continue;
}
let victim_key_ref = match &self.slots[idx] {
Some(entry) => &entry.key,
None => unreachable!("slot reported occupied but is empty"),
};
let removed = self.index.remove(victim_key_ref);
debug_assert_eq!(removed, Some(idx), "index/slot disagreement in pop_victim");
let evicted = match self.slots[idx].take() {
Some(entry) => entry,
None => unreachable!("slot reported occupied but take() returned None"),
};
self.referenced[idx] = false;
self.len = self
.len
.checked_sub(1)
.unwrap_or_else(|| unreachable!("len underflow in pop_victim"));
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances = self.sweep_hand_advances.saturating_add(1);
}
return Some((evicted.key, evicted.value));
}
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances = self.sweep_hand_advances.saturating_add(1);
}
}
None
}
#[cfg(any(test, debug_assertions))]
pub fn debug_snapshot_slots(&self) -> Vec<Option<(&K, bool)>> {
self.slots
.iter()
.enumerate()
.map(|(idx, slot)| {
slot.as_ref()
.map(|entry| (&entry.key, self.referenced[idx]))
})
.collect()
}
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let idx = self.index.remove(key)?;
let entry = self.slots.get_mut(idx)?.take()?;
self.referenced[idx] = false;
self.len = self
.len
.checked_sub(1)
.unwrap_or_else(|| unreachable!("len underflow in remove"));
Some(entry.value)
}
fn advance_hand(&mut self) {
self.hand = step(self.hand, self.slots.len());
}
#[cfg(any(test, debug_assertions))]
pub fn debug_validate_invariants(&self) {
let slot_count = self.slots.iter().filter(|slot| slot.is_some()).count();
assert_eq!(self.len, slot_count);
assert_eq!(self.len, self.index.len());
assert_eq!(self.referenced.len(), self.slots.len());
if self.capacity() == 0 {
assert_eq!(self.hand, 0);
} else {
assert!(self.hand < self.capacity());
}
for (key, &idx) in &self.index {
assert!(idx < self.slots.len());
let entry = self.slots[idx]
.as_ref()
.expect("index points to empty slot");
assert!(&entry.key == key);
}
for idx in 0..self.slots.len() {
if self.slots[idx].is_none() {
assert!(!self.referenced[idx], "empty slot has referenced bit set");
}
}
}
}
impl<K, V, S> Extend<(K, V)> for ClockRing<K, V, S>
where
K: Eq + Hash + Clone,
S: BuildHasher,
{
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
for (key, value) in iter {
let _ = self.insert(key, value);
}
}
}
impl<K, V, S> Clone for ClockRing<K, V, S>
where
K: Clone,
V: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
slots: self.slots.clone(),
referenced: self.referenced.clone(),
index: self.index.clone(),
hand: self.hand,
len: self.len,
#[cfg(feature = "metrics")]
sweep_hand_advances: self.sweep_hand_advances,
#[cfg(feature = "metrics")]
sweep_ref_bit_resets: self.sweep_ref_bit_resets,
}
}
}
impl<K, V, S> ClockRing<K, V, S>
where
K: Eq + Hash + Clone,
V: Clone,
S: Clone + BuildHasher,
{
pub fn try_clone(&self) -> Result<Self, ClockRingError> {
let cap = self.capacity();
let alloc_err = || ClockRingError::AllocationFailed { requested: cap };
let mut slots: Vec<Option<Entry<K, V>>> = Vec::new();
slots.try_reserve_exact(cap).map_err(|_| alloc_err())?;
for slot in &self.slots {
slots.push(slot.clone());
}
let mut referenced: Vec<bool> = Vec::new();
referenced.try_reserve_exact(cap).map_err(|_| alloc_err())?;
referenced.extend_from_slice(&self.referenced);
let mut index = HashMap::with_hasher(self.index.hasher().clone());
index.try_reserve(cap).map_err(|_| alloc_err())?;
for (k, &v) in &self.index {
index.insert(k.clone(), v);
}
Ok(Self {
slots,
referenced,
index,
hand: self.hand,
len: self.len,
#[cfg(feature = "metrics")]
sweep_hand_advances: self.sweep_hand_advances,
#[cfg(feature = "metrics")]
sweep_ref_bit_resets: self.sweep_ref_bit_resets,
})
}
}
#[derive(Debug)]
pub struct Iter<'a, K, V> {
inner: std::slice::Iter<'a, Option<Entry<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 {
match self.inner.next() {
Some(Some(entry)) => return Some((&entry.key, &entry.value)),
Some(None) => continue,
None => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.inner.size_hint().1)
}
}
#[derive(Debug)]
pub struct IterMut<'a, K, V> {
inner: std::slice::IterMut<'a, Option<Entry<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 {
match self.inner.next() {
Some(Some(entry)) => return Some((&entry.key, &mut entry.value)),
Some(None) => continue,
None => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.inner.size_hint().1)
}
}
#[derive(Debug)]
pub struct IntoIter<K, V> {
inner: std::vec::IntoIter<Option<Entry<K, V>>>,
}
impl<K, V> Iterator for IntoIter<K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.inner.next() {
Some(Some(entry)) => return Some((entry.key, entry.value)),
Some(None) => continue,
None => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.inner.size_hint().1)
}
}
#[derive(Debug)]
pub struct Keys<'a, K, V> {
inner: Iter<'a, K, V>,
}
impl<'a, K, V> Iterator for Keys<'a, K, V> {
type Item = &'a K;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(k, _)| k)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
#[derive(Debug)]
pub struct Values<'a, K, V> {
inner: Iter<'a, K, V>,
}
impl<'a, K, V> Iterator for Values<'a, K, V> {
type Item = &'a V;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(_, v)| v)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
#[derive(Debug)]
pub struct ValuesMut<'a, K, V> {
inner: IterMut<'a, K, V>,
}
impl<'a, K, V> Iterator for ValuesMut<'a, K, V> {
type Item = &'a mut V;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(_, v)| v)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<K, V, S> IntoIterator for ClockRing<K, V, S> {
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
inner: self.slots.into_iter(),
}
}
}
impl<'a, K, V, S> IntoIterator for &'a ClockRing<K, V, S> {
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, K, V, S> IntoIterator for &'a mut ClockRing<K, V, S> {
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
#[cfg(feature = "concurrency")]
impl<K, V, S> ConcurrentClockRing<K, V, S> {
pub fn into_inner(self) -> ClockRing<K, V, S> {
self.inner.into_inner()
}
}
#[cfg(test)]
#[allow(unused_must_use)]
mod tests {
use super::*;
#[test]
fn step_helper_wraps_and_handles_usize_max() {
assert_eq!(step(0, 4), 1);
assert_eq!(step(3, 4), 0);
assert_eq!(step(usize::MAX, 4), 0);
}
#[test]
fn step_helper_is_total_for_zero_capacity() {
assert_eq!(step(0, 0), 0);
assert_eq!(step(42, 0), 0);
assert_eq!(step(usize::MAX, 0), 0);
}
#[test]
fn try_new_rejects_capacity_above_max() {
let err = ClockRing::<u32, u32>::try_new(MAX_CAPACITY + 1).unwrap_err();
match err {
ClockRingError::CapacityTooLarge { requested, max } => {
assert_eq!(requested, MAX_CAPACITY + 1);
assert_eq!(max, MAX_CAPACITY);
},
other => panic!("expected CapacityTooLarge, got {other:?}"),
}
let ring = ClockRing::<u32, u32>::try_new(8).unwrap();
assert_eq!(ring.capacity(), 8);
}
#[test]
fn try_new_returns_allocation_failed_on_hostile_capacity() {
let err = ClockRing::<u32, [u8; 1024]>::try_new(MAX_CAPACITY).unwrap_err();
match err {
ClockRingError::AllocationFailed { requested } => {
assert_eq!(requested, MAX_CAPACITY);
},
other => panic!("expected AllocationFailed, got {other:?}"),
}
}
#[test]
#[cfg(not(debug_assertions))]
fn insert_swap_repairs_stale_index_instead_of_dropping_value() {
let mut ring: ClockRing<&str, i32> = ClockRing::new(4);
ring.insert("a", 1);
let idx = *ring.index.get("a").unwrap();
ring.slots[idx] = None;
let (replaced, evicted) = ring.insert_swap("a", 99);
assert_eq!(replaced, None);
assert_eq!(evicted, None);
assert_eq!(ring.peek(&"a"), Some(&99));
ring.debug_validate_invariants();
}
#[test]
#[cfg(not(debug_assertions))]
fn insert_swap_returns_value_when_index_points_out_of_bounds() {
let mut ring: ClockRing<&str, i32> = ClockRing::new(4);
ring.insert("a", 1);
ring.index.insert("a", ring.slots.len() + 5);
ring.slots[0] = None;
ring.len = 0;
let (replaced, evicted) = ring.insert_swap("a", 99);
assert_eq!(
replaced,
Some(99),
"value must be handed back via the replaced position \
rather than silently dropped on invariant violation",
);
assert_eq!(evicted, None);
assert!(!ring.contains(&"a"));
}
#[test]
fn debug_impl_does_not_leak_keys_or_values() {
let mut ring: ClockRing<&str, &str> = ClockRing::new(4);
ring.insert("session-token-key-do-not-leak", "bearer-secret-do-not-leak");
ring.insert("api-key-do-not-leak", "plaintext-password-do-not-leak");
let rendered = format!("{ring:?}");
assert!(
!rendered.contains("do-not-leak"),
"Debug impl leaked key or value into output: {rendered}",
);
assert!(rendered.contains("ClockRing"));
assert!(rendered.contains("len"));
assert!(rendered.contains("capacity"));
assert!(rendered.contains("hand"));
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_debug_impl_does_not_leak_keys_or_values() {
let cache: ConcurrentClockRing<&str, &str> = ConcurrentClockRing::new(4);
cache.insert("session-token-key-do-not-leak", "bearer-secret-do-not-leak");
cache.insert("api-key-do-not-leak", "plaintext-password-do-not-leak");
let rendered = format!("{cache:?}");
assert!(
!rendered.contains("do-not-leak"),
"Debug impl leaked key or value into output: {rendered}",
);
assert!(rendered.contains("ConcurrentClockRing"));
assert!(rendered.contains("len"));
assert!(rendered.contains("capacity"));
}
#[test]
fn get_does_not_set_ref_bit_on_missing_key() {
let mut ring = ClockRing::<&str, i32>::new(4);
ring.insert("a", 1);
assert!(ring.get(&"missing").is_none());
assert!(!ring.touch(&"missing"));
assert!(ring.get_mut(&"missing").is_none());
assert_eq!(ring.peek_victim(), Some((&"a", &1)));
}
#[test]
#[should_panic(expected = "capacity exceeds MAX_CAPACITY")]
fn new_panics_on_oversized_capacity() {
let _ = ClockRing::<u32, u32>::new(MAX_CAPACITY + 1);
}
#[test]
fn approx_bytes_saturates_without_panicking() {
let ring: ClockRing<u64, u64> = ClockRing::new(128);
let bytes = ring.approx_bytes();
assert!(bytes > 0);
}
#[test]
fn with_hasher_uses_custom_hasher() {
use std::collections::hash_map::RandomState;
let mut ring: ClockRing<&str, i32, RandomState> =
ClockRing::with_hasher(4, RandomState::new(), KeysAreTrusted::new());
assert_eq!(ring.insert("a", 1), None);
assert_eq!(ring.insert("b", 2), None);
assert_eq!(ring.peek(&"a"), Some(&1));
assert_eq!(ring.peek(&"b"), Some(&2));
}
#[test]
fn try_with_hasher_requires_ack_and_builds() {
use std::collections::hash_map::RandomState;
let ring: ClockRing<u32, u32, RandomState> =
ClockRing::try_with_hasher(8, RandomState::new(), KeysAreTrusted::new()).unwrap();
assert_eq!(ring.capacity(), 8);
let ring2: ClockRing<u32, u32, RandomState> =
ClockRing::try_with_hasher(8, RandomState::new(), KeysAreTrusted::default()).unwrap();
assert_eq!(ring2.capacity(), 8);
}
#[test]
fn clock_ring_eviction_prefers_unreferenced() {
let mut ring = ClockRing::new(2);
ring.insert("a", 1);
ring.insert("b", 2);
ring.touch(&"a");
let evicted = ring.insert("c", 3);
assert_eq!(evicted, Some(("b", 2)));
assert!(ring.contains(&"a"));
assert!(ring.contains(&"c"));
}
#[test]
fn clock_ring_zero_capacity_is_noop() {
let mut ring = ClockRing::<&str, i32>::new(0);
assert!(ring.is_empty());
assert_eq!(ring.capacity(), 0);
assert_eq!(ring.insert("a", 1), None);
assert!(ring.is_empty());
assert!(ring.peek(&"a").is_none());
assert!(ring.get(&"a").is_none());
assert!(!ring.contains(&"a"));
}
#[test]
fn clock_ring_insert_and_peek_no_eviction() {
let mut ring = ClockRing::new(3);
assert_eq!(ring.insert("a", 1), None);
assert_eq!(ring.insert("b", 2), None);
assert_eq!(ring.insert("c", 3), None);
assert_eq!(ring.len(), 3);
assert!(ring.contains(&"a"));
assert!(ring.contains(&"b"));
assert!(ring.contains(&"c"));
assert_eq!(ring.peek(&"a"), Some(&1));
assert_eq!(ring.peek(&"b"), Some(&2));
assert_eq!(ring.peek(&"c"), Some(&3));
}
#[test]
fn clock_ring_update_existing_key_does_not_grow() {
let mut ring = ClockRing::new(2);
assert_eq!(ring.insert("a", 1), None);
assert_eq!(ring.insert("b", 2), None);
assert_eq!(ring.len(), 2);
assert_eq!(ring.insert("a", 10), None);
assert_eq!(ring.len(), 2);
assert_eq!(ring.peek(&"a"), Some(&10));
}
#[test]
fn clock_ring_update_returns_old_value() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
assert_eq!(ring.update(&"a", 10), Some(1));
assert_eq!(ring.peek(&"a"), Some(&10));
assert_eq!(ring.len(), 2);
assert_eq!(ring.update(&"missing", 99), None);
assert_eq!(ring.len(), 2);
let mut ring2 = ClockRing::new(2);
ring2.insert("x", 1);
ring2.insert("y", 2);
ring2.update(&"x", 10); let evicted = ring2.insert("z", 3);
assert_eq!(evicted, Some(("y", 2))); }
#[test]
fn clock_ring_get_sets_referenced_and_eviction_skips_it() {
let mut ring = ClockRing::new(2);
ring.insert("a", 1);
ring.insert("b", 2);
assert_eq!(ring.get(&"a"), Some(&1));
let evicted = ring.insert("c", 3);
assert_eq!(evicted, Some(("b", 2)));
assert!(ring.contains(&"a"));
assert!(ring.contains(&"c"));
assert!(!ring.contains(&"b"));
}
#[test]
fn clock_ring_touch_marks_referenced() {
let mut ring = ClockRing::new(2);
ring.insert("a", 1);
ring.insert("b", 2);
assert!(ring.touch(&"b"));
let evicted = ring.insert("c", 3);
assert_eq!(evicted, Some(("a", 1)));
assert!(ring.contains(&"b"));
assert!(ring.contains(&"c"));
}
#[test]
fn clock_ring_remove_clears_slot_and_updates_len() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
ring.insert("c", 3);
assert_eq!(ring.len(), 3);
assert_eq!(ring.remove(&"b"), Some(2));
assert_eq!(ring.len(), 2);
assert!(!ring.contains(&"b"));
assert!(ring.peek(&"b").is_none());
let evicted = ring.insert("d", 4);
assert_eq!(evicted, None, "ring has free slot, must not evict");
assert!(ring.contains(&"d"));
assert!(!ring.contains(&"b"));
assert_eq!(ring.len(), 3);
}
#[test]
fn clock_ring_eviction_cycles_with_hand_wrap() {
let mut ring = ClockRing::new(2);
ring.insert("a", 1);
ring.insert("b", 2);
let evicted1 = ring.insert("c", 3);
assert!(matches!(evicted1, Some(("a", 1)) | Some(("b", 2))));
assert_eq!(ring.len(), 2);
let evicted2 = ring.insert("d", 4);
assert!(matches!(
evicted2,
Some(("a", 1)) | Some(("b", 2)) | Some(("c", 3))
));
assert_eq!(ring.len(), 2);
assert!(ring.contains(&"d"));
}
#[test]
fn clock_ring_debug_invariants_hold() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
ring.get(&"a");
ring.insert("c", 3);
ring.remove(&"b");
ring.debug_validate_invariants();
}
#[test]
fn clock_ring_peek_and_pop_victim() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
ring.insert("c", 3);
let peeked = ring.peek_victim();
assert!(matches!(
peeked,
Some((&"a", &1)) | Some((&"b", &2)) | Some((&"c", &3))
));
let evicted = ring.pop_victim();
assert!(matches!(
evicted,
Some(("a", 1)) | Some(("b", 2)) | Some(("c", 3))
));
assert_eq!(ring.len(), 2);
ring.debug_validate_invariants();
}
#[test]
fn clock_ring_peek_skips_referenced_entries() {
let mut ring = ClockRing::new(2);
ring.insert("a", 1);
ring.insert("b", 2);
ring.touch(&"a");
let peeked = ring.peek_victim();
assert_eq!(peeked, Some((&"b", &2)));
}
#[test]
fn clock_ring_pop_victim_clears_referenced_then_eviction() {
let mut ring = ClockRing::new(2);
ring.insert("a", 1);
ring.insert("b", 2);
ring.touch(&"a");
ring.touch(&"b");
let evicted = ring.pop_victim();
assert!(matches!(evicted, Some(("a", 1)) | Some(("b", 2))));
assert_eq!(ring.len(), 1);
}
#[test]
fn clock_ring_debug_snapshot_slots() {
let mut ring = ClockRing::new(2);
ring.insert("a", 1);
ring.insert("b", 2);
ring.touch(&"a");
let snapshot = ring.debug_snapshot_slots();
assert_eq!(snapshot.len(), 2);
assert!(
snapshot
.iter()
.any(|slot| matches!(slot, &Some((&"a", true))))
);
}
#[test]
fn clock_ring_pop_victim_all_referenced_evicts_in_one_call() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
ring.insert("c", 3);
ring.touch(&"a");
ring.touch(&"b");
ring.touch(&"c");
let evicted = ring.pop_victim();
assert!(evicted.is_some());
assert_eq!(ring.len(), 2);
ring.debug_validate_invariants();
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_clock_ring_try_ops() {
let ring = ConcurrentClockRing::new(2);
assert_eq!(ring.try_insert("a", 1), Some(None));
assert_eq!(ring.try_peek_with(&"a", |v| *v), Some(Some(1)));
assert_eq!(ring.try_get_with(&"a", |v| *v), Some(Some(1)));
assert_eq!(ring.try_touch(&"a"), Some(true));
assert!(ring.try_clear());
assert!(ring.is_empty());
}
#[test]
fn iter_yields_all_occupied_entries() {
let mut ring = ClockRing::new(5);
ring.insert("a", 1);
ring.insert("b", 2);
ring.insert("c", 3);
let mut pairs: Vec<_> = ring.iter().collect();
pairs.sort_by_key(|&(k, _)| *k);
assert_eq!(pairs, vec![(&"a", &1), (&"b", &2), (&"c", &3)]);
}
#[test]
fn iter_skips_empty_slots_after_removal() {
let mut ring = ClockRing::new(5);
ring.insert("a", 1);
ring.insert("b", 2);
ring.insert("c", 3);
ring.remove(&"b");
let mut pairs: Vec<_> = ring.iter().collect();
pairs.sort_by_key(|&(k, _)| *k);
assert_eq!(pairs, vec![(&"a", &1), (&"c", &3)]);
}
#[test]
fn iter_on_empty_ring() {
let ring = ClockRing::<&str, i32>::new(5);
assert_eq!(ring.iter().count(), 0);
}
#[test]
fn iter_on_zero_capacity() {
let ring = ClockRing::<&str, i32>::new(0);
assert_eq!(ring.iter().count(), 0);
}
#[test]
fn iter_mut_modifies_values() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
for (_, v) in ring.iter_mut() {
*v += 100;
}
assert_eq!(ring.peek(&"a"), Some(&101));
assert_eq!(ring.peek(&"b"), Some(&102));
}
#[test]
fn iter_mut_does_not_affect_reference_bits() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
for (_, v) in ring.iter_mut() {
*v += 1;
}
ring.debug_validate_invariants();
}
#[test]
fn keys_yields_all_keys() {
let mut ring = ClockRing::new(4);
ring.insert("x", 10);
ring.insert("y", 20);
ring.insert("z", 30);
let mut keys: Vec<_> = ring.keys().collect();
keys.sort();
assert_eq!(keys, vec![&"x", &"y", &"z"]);
}
#[test]
fn values_yields_all_values() {
let mut ring = ClockRing::new(4);
ring.insert("x", 10);
ring.insert("y", 20);
ring.insert("z", 30);
let mut vals: Vec<_> = ring.values().collect();
vals.sort();
assert_eq!(vals, vec![&10, &20, &30]);
}
#[test]
fn values_mut_modifies_all_values() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
for v in ring.values_mut() {
*v *= 10;
}
let mut vals: Vec<_> = ring.values().copied().collect();
vals.sort();
assert_eq!(vals, vec![10, 20]);
}
#[test]
fn into_iter_consumes_ring() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
let mut pairs: Vec<_> = ring.into_iter().collect();
pairs.sort_by_key(|(k, _)| *k);
assert_eq!(pairs, vec![("a", 1), ("b", 2)]);
}
#[test]
fn into_iter_empty() {
let ring = ClockRing::<&str, i32>::new(5);
assert_eq!(ring.into_iter().count(), 0);
}
#[test]
fn into_iter_after_evictions() {
let mut ring = ClockRing::new(2);
ring.insert("a", 1);
ring.insert("b", 2);
ring.insert("c", 3);
let pairs: Vec<_> = ring.into_iter().collect();
assert_eq!(pairs.len(), 2);
}
#[test]
fn into_iter_for_loop() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
let mut sum = 0;
for (_, v) in ring {
sum += v;
}
assert_eq!(sum, 3);
}
#[test]
fn ref_into_iter_for_loop() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
let mut sum = 0;
for (_, v) in &ring {
sum += v;
}
assert_eq!(sum, 3);
assert_eq!(ring.len(), 2); }
#[test]
fn mut_ref_into_iter_for_loop() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
for (_, v) in &mut ring {
*v += 10;
}
assert_eq!(ring.peek(&"a"), Some(&11));
assert_eq!(ring.peek(&"b"), Some(&12));
}
#[test]
fn iter_count_matches_len() {
let mut ring = ClockRing::new(10);
for i in 0..7 {
ring.insert(i, i * 10);
}
ring.remove(&3);
ring.remove(&5);
assert_eq!(ring.iter().count(), ring.len());
assert_eq!(ring.keys().count(), ring.len());
assert_eq!(ring.values().count(), ring.len());
}
#[test]
fn iter_after_clear() {
let mut ring = ClockRing::new(5);
ring.insert("a", 1);
ring.insert("b", 2);
ring.clear();
assert_eq!(ring.iter().count(), 0);
assert_eq!(ring.keys().count(), 0);
assert_eq!(ring.values().count(), 0);
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_into_inner_allows_iteration() {
let cache = ConcurrentClockRing::new(5);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
let ring = cache.into_inner();
let mut pairs: Vec<_> = ring.iter().collect();
pairs.sort_by_key(|&(k, _)| *k);
assert_eq!(pairs, vec![(&"a", &1), (&"b", &2), (&"c", &3)]);
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_into_inner_into_iter() {
let cache = ConcurrentClockRing::new(3);
cache.insert("x", 10);
cache.insert("y", 20);
let pairs: Vec<_> = cache.into_inner().into_iter().collect();
assert_eq!(pairs.len(), 2);
}
#[test]
fn insert_uses_empty_slot_after_remove_no_eviction() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1); ring.insert("b", 2); ring.insert("c", 3);
ring.remove(&"b"); assert_eq!(ring.len(), 2);
let evicted = ring.insert("d", 4);
assert_eq!(evicted, None, "must not evict when ring has free slots");
assert_eq!(ring.len(), 3);
assert!(ring.contains(&"a"), "\"a\" should still be present");
assert!(ring.contains(&"c"), "\"c\" should still be present");
assert!(ring.contains(&"d"), "\"d\" should have been inserted");
ring.debug_validate_invariants();
}
#[test]
fn insert_uses_empty_slot_after_pop_victim_no_eviction() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
ring.insert("c", 3);
let victim = ring.pop_victim();
assert!(victim.is_some());
assert_eq!(ring.len(), 2);
let evicted = ring.insert("d", 4);
assert_eq!(evicted, None, "must not evict when ring has free slots");
assert_eq!(ring.len(), 3);
assert!(ring.contains(&"d"));
ring.debug_validate_invariants();
}
thread_local! {
static CLONE_BUDGET: std::cell::Cell<usize> =
const { std::cell::Cell::new(usize::MAX) };
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct PanicKey(u32);
impl Clone for PanicKey {
fn clone(&self) -> Self {
CLONE_BUDGET.with(|b| {
let remaining = b.get();
if remaining == 0 {
panic!("PanicKey: clone budget exhausted");
}
b.set(remaining - 1);
});
Self(self.0)
}
}
#[test]
fn insert_swap_eviction_panic_in_clone_preserves_invariants() {
let mut ring: ClockRing<PanicKey, i32> = ClockRing::new(2);
CLONE_BUDGET.with(|b| b.set(usize::MAX));
ring.insert(PanicKey(1), 10);
ring.insert(PanicKey(2), 20);
assert_eq!(ring.len(), 2);
assert_eq!(ring.capacity(), 2);
let slots_before: Vec<Option<(u32, bool)>> = ring
.debug_snapshot_slots()
.into_iter()
.map(|slot| slot.map(|(k, bit)| (k.0, bit)))
.collect();
CLONE_BUDGET.with(|b| b.set(0));
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ring.insert(PanicKey(3), 30);
}))
.is_err();
assert!(panicked, "PanicKey::clone must have fired");
CLONE_BUDGET.with(|b| b.set(usize::MAX));
ring.debug_validate_invariants();
assert_eq!(ring.len(), 2, "len must not drift on clone panic");
assert_eq!(ring.capacity(), 2);
let slots_after: Vec<Option<(u32, bool)>> = ring
.debug_snapshot_slots()
.into_iter()
.map(|slot| slot.map(|(k, bit)| (k.0, bit)))
.collect();
assert_eq!(slots_before.len(), slots_after.len());
for (before, after) in slots_before.iter().zip(slots_after.iter()) {
assert_eq!(
before.map(|(k, _)| k),
after.map(|(k, _)| k),
"slot contents changed after panicking clone"
);
}
let evicted = ring.insert(PanicKey(3), 30);
assert!(evicted.is_some(), "next insert must evict normally");
ring.debug_validate_invariants();
assert_eq!(ring.len(), 2);
}
#[test]
fn insert_swap_eviction_panic_in_clone_does_not_leak_evictee() {
let mut ring: ClockRing<PanicKey, i32> = ClockRing::new(2);
CLONE_BUDGET.with(|b| b.set(usize::MAX));
ring.insert(PanicKey(1), 10);
ring.insert(PanicKey(2), 20);
CLONE_BUDGET.with(|b| b.set(0));
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ring.insert(PanicKey(3), 30);
}));
CLONE_BUDGET.with(|b| b.set(usize::MAX));
ring.debug_validate_invariants();
assert!(ring.contains(&PanicKey(1)));
assert!(ring.contains(&PanicKey(2)));
assert!(!ring.contains(&PanicKey(3)));
}
thread_local! {
static HASH_BUDGET: std::cell::Cell<usize> =
const { std::cell::Cell::new(usize::MAX) };
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct HashPanicKey(u32);
impl std::hash::Hash for HashPanicKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
HASH_BUDGET.with(|b| {
let remaining = b.get();
if remaining == 0 {
panic!("HashPanicKey: hash budget exhausted");
}
b.set(remaining - 1);
});
self.0.hash(state);
}
}
#[test]
fn insert_swap_eviction_panic_in_hash_preserves_invariants() {
let mut ring: ClockRing<HashPanicKey, i32> = ClockRing::new(2);
HASH_BUDGET.with(|b| b.set(usize::MAX));
ring.insert(HashPanicKey(1), 10);
ring.insert(HashPanicKey(2), 20);
assert_eq!(ring.len(), 2);
let slots_before: Vec<Option<u32>> = ring
.debug_snapshot_slots()
.into_iter()
.map(|slot| slot.map(|(k, _)| k.0))
.collect();
HASH_BUDGET.with(|b| b.set(1));
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ring.insert(HashPanicKey(3), 30);
}))
.is_err();
assert!(panicked, "HashPanicKey::hash must have fired");
HASH_BUDGET.with(|b| b.set(usize::MAX));
ring.debug_validate_invariants();
assert_eq!(ring.len(), 2, "len must not drift on hash panic");
assert_eq!(ring.capacity(), 2);
let slots_after: Vec<Option<u32>> = ring
.debug_snapshot_slots()
.into_iter()
.map(|slot| slot.map(|(k, _)| k.0))
.collect();
assert_eq!(slots_before, slots_after);
let evicted = ring.insert(HashPanicKey(3), 30);
assert!(evicted.is_some(), "next insert must evict normally");
ring.debug_validate_invariants();
}
#[test]
fn pop_victim_panic_in_hash_preserves_invariants() {
let mut ring: ClockRing<HashPanicKey, i32> = ClockRing::new(3);
HASH_BUDGET.with(|b| b.set(usize::MAX));
ring.insert(HashPanicKey(1), 10);
ring.insert(HashPanicKey(2), 20);
ring.insert(HashPanicKey(3), 30);
assert_eq!(ring.len(), 3);
let snapshot_before = ring.debug_snapshot_slots().len();
HASH_BUDGET.with(|b| b.set(0));
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ring.pop_victim();
}))
.is_err();
assert!(panicked, "HashPanicKey::hash must have fired");
HASH_BUDGET.with(|b| b.set(usize::MAX));
ring.debug_validate_invariants();
assert_eq!(ring.len(), 3, "len must not drift on hash panic");
assert_eq!(ring.debug_snapshot_slots().len(), snapshot_before);
let evicted = ring.pop_victim();
assert!(evicted.is_some(), "pop_victim must recover after unwind");
assert_eq!(ring.len(), 2);
ring.debug_validate_invariants();
}
#[test]
fn try_clone_round_trips_contents() {
let mut ring: ClockRing<&str, i32> = ClockRing::new(4);
ring.insert("a", 1);
ring.insert("b", 2);
ring.insert("c", 3);
ring.touch(&"a");
let clone = ring
.try_clone()
.expect("try_clone must succeed on sane capacity");
assert_eq!(clone.capacity(), ring.capacity());
assert_eq!(clone.len(), ring.len());
assert_eq!(clone.peek(&"a"), Some(&1));
assert_eq!(clone.peek(&"b"), Some(&2));
assert_eq!(clone.peek(&"c"), Some(&3));
clone.debug_validate_invariants();
assert_eq!(ring.debug_snapshot_slots(), clone.debug_snapshot_slots());
}
#[test]
fn try_clone_is_independent_of_source() {
let mut ring: ClockRing<&str, i32> = ClockRing::new(3);
ring.insert("a", 1);
ring.insert("b", 2);
let mut clone = ring.try_clone().unwrap();
clone.insert("c", 3);
clone.remove(&"a");
assert!(ring.contains(&"a"));
assert!(!ring.contains(&"c"));
assert_eq!(ring.len(), 2);
}
#[test]
fn clone_trait_matches_try_clone() {
let mut ring: ClockRing<&str, i32> = ClockRing::new(4);
ring.insert("x", 10);
ring.insert("y", 20);
let via_clone = ring.clone();
let via_try_clone = ring.try_clone().unwrap();
assert_eq!(
via_clone.debug_snapshot_slots(),
via_try_clone.debug_snapshot_slots()
);
assert_eq!(via_clone.len(), via_try_clone.len());
assert_eq!(via_clone.capacity(), via_try_clone.capacity());
}
#[test]
fn insert_no_eviction_preserves_ref_bits() {
let mut ring = ClockRing::new(3);
ring.insert("a", 1); ring.insert("b", 2); ring.insert("c", 3);
ring.touch(&"a");
ring.remove(&"c");
let evicted = ring.insert("d", 4);
assert_eq!(evicted, None, "must not evict when ring has free slots");
assert_eq!(ring.len(), 3);
let evicted = ring.insert("e", 5);
assert!(evicted.is_some());
let (evicted_key, _) = evicted.unwrap();
assert!(
evicted_key == "b" || evicted_key == "d",
"expected unreferenced victim, got {:?}",
evicted_key
);
assert!(
ring.contains(&"a"),
"\"a\" should survive: ref bit preserved"
);
ring.debug_validate_invariants();
}
}
#[cfg(test)]
#[allow(unused_must_use)]
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 ring = ClockRing::new(capacity);
for (key, value) in ops {
ring.insert(key, value);
prop_assert!(ring.len() <= ring.capacity());
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_index_slot_consistency(
capacity in 1usize..50,
ops in prop::collection::vec((0u32..100, 0u32..100), 0..100)
) {
let mut ring = ClockRing::new(capacity);
for (key, value) in ops {
ring.insert(key, value);
ring.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 ring = ClockRing::new(capacity);
ring.insert(key, value);
if ring.contains(&key) {
prop_assert_eq!(ring.peek(&key), Some(&value));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_insert_eviction_behavior(
capacity in 1usize..20,
keys in prop::collection::vec(0u32..50, 0..100)
) {
let mut ring = ClockRing::new(capacity);
for key in keys {
let len_before = ring.len();
let evicted = ring.insert(key, key * 10);
if len_before < capacity {
if evicted.is_some() {
prop_assert!(len_before == ring.len());
}
} else {
prop_assert!(ring.len() <= capacity);
}
ring.debug_validate_invariants();
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_behavior(
capacity in 1usize..50,
keys in prop::collection::vec(0u32..100, 1..50)
) {
let mut ring = ClockRing::new(capacity);
for &key in &keys {
ring.insert(key, key * 10);
}
for &key in &keys[0..keys.len()/2] {
let len_before = ring.len();
let removed = ring.remove(&key);
if removed.is_some() {
prop_assert_eq!(ring.len(), len_before - 1);
prop_assert!(!ring.contains(&key));
}
ring.debug_validate_invariants();
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_update_preserves_len(
capacity in 1usize..50,
ops in prop::collection::vec((0u32..50, 0u32..100), 1..50)
) {
let mut ring = ClockRing::new(capacity);
for (key, value) in &ops {
ring.insert(*key, *value);
}
let len_before = ring.len();
for (key, new_value) in ops {
if ring.contains(&key) {
ring.update(&key, new_value + 1000);
prop_assert_eq!(ring.len(), len_before);
}
}
ring.debug_validate_invariants();
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_second_chance_behavior(
capacity in 2usize..10
) {
let mut ring = ClockRing::new(capacity);
for i in 0..capacity {
ring.insert(i as u32, i as u32);
}
ring.touch(&0);
ring.insert(capacity as u32, capacity as u32);
prop_assert!(ring.contains(&0) || ring.len() < capacity);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_peek_idempotent(
capacity in 1usize..50,
keys in prop::collection::vec(0u32..100, 1..50)
) {
let mut ring = ClockRing::new(capacity);
for key in keys {
ring.insert(key, key * 10);
}
let snapshot_before = ring.debug_snapshot_slots();
for i in 0..100 {
ring.peek(&i);
}
let snapshot_after = ring.debug_snapshot_slots();
prop_assert_eq!(snapshot_before, snapshot_after);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_hand_within_bounds(
capacity in 1usize..50,
ops in prop::collection::vec((0u32..100, 0u32..100), 0..200)
) {
let mut ring = ClockRing::new(capacity);
for (key, value) in ops {
ring.insert(key, value);
ring.debug_validate_invariants();
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_pop_victim_decreases_len(
capacity in 1usize..50,
keys in prop::collection::vec(0u32..100, 1..50)
) {
let mut ring = ClockRing::new(capacity);
for key in keys {
ring.insert(key, key * 10);
}
while !ring.is_empty() {
let len_before = ring.len();
let evicted = ring.pop_victim();
if evicted.is_some() {
prop_assert_eq!(ring.len(), len_before - 1);
let (evicted_key, _) = evicted.unwrap();
prop_assert!(!ring.contains(&evicted_key));
}
ring.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 ring = ClockRing::new(capacity);
for key in keys {
ring.insert(key, key * 10);
}
ring.clear();
prop_assert!(ring.is_empty());
prop_assert_eq!(ring.len(), 0);
ring.debug_validate_invariants();
}
}
#[derive(Debug, Clone)]
enum Operation {
Insert(u32, u32),
Get(u32),
Peek(u32),
Touch(u32),
Update(u32, u32),
Remove(u32),
PopVictim,
}
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::Peek),
(0u32..50).prop_map(Operation::Touch),
(0u32..50, 0u32..100).prop_map(|(k, v)| Operation::Update(k, v)),
(0u32..50).prop_map(Operation::Remove),
Just(Operation::PopVictim),
]
}
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 ring = ClockRing::new(capacity);
for op in ops {
match op {
Operation::Insert(k, v) => {
ring.insert(k, v);
}
Operation::Get(k) => {
ring.get(&k);
}
Operation::Peek(k) => {
ring.peek(&k);
}
Operation::Touch(k) => {
ring.touch(&k);
}
Operation::Update(k, v) => {
ring.update(&k, v);
}
Operation::Remove(k) => {
ring.remove(&k);
}
Operation::PopVictim => {
ring.pop_victim();
}
}
ring.debug_validate_invariants();
prop_assert!(ring.len() <= ring.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 ring = ClockRing::new(capacity);
for (key, should_insert) in ops {
if should_insert {
ring.insert(key, key * 10);
} else {
ring.remove(&key);
}
ring.debug_validate_invariants();
prop_assert!(ring.len() <= ring.capacity());
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_zero_capacity_noop(
ops in prop::collection::vec((0u32..100, 0u32..100), 0..50)
) {
let mut ring = ClockRing::<u32, u32>::new(0);
for (key, value) in ops {
ring.insert(key, value);
prop_assert!(ring.is_empty());
prop_assert_eq!(ring.len(), 0);
prop_assert!(!ring.contains(&key));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_capacity_one_single_entry(
keys in prop::collection::vec(0u32..100, 1..50)
) {
let mut ring = ClockRing::new(1);
for key in keys {
ring.insert(key, key * 10);
prop_assert!(ring.len() <= 1);
ring.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 ring = ClockRing::new(capacity);
ring.insert(key, 0);
let len_after_first = ring.len();
for value in values {
ring.insert(key, value);
prop_assert_eq!(ring.len(), len_after_first);
}
}
}
#[cfg(feature = "concurrency")]
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_concurrent_maintains_invariants(
capacity in 1usize..30,
ops in prop::collection::vec((0u32..50, 0u32..100), 0..100)
) {
let ring = ConcurrentClockRing::new(capacity);
for (key, value) in ops {
ring.insert(key, value);
prop_assert!(ring.len() <= ring.capacity());
}
}
}
}
#[cfg(test)]
#[allow(unused_must_use)]
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 ring = ClockRing::new(capacity);
let mut idx = 1;
while idx < data.len() {
if idx + 2 >= data.len() {
break;
}
let op = data[idx] % 7;
let key = data[idx + 1] as u32;
let value = data[idx + 2] as u32;
match op {
0 => {
ring.insert(key, value);
},
1 => {
ring.get(&key);
},
2 => {
ring.peek(&key);
},
3 => {
ring.touch(&key);
},
4 => {
ring.update(&key, value);
},
5 => {
ring.remove(&key);
},
6 => {
ring.pop_victim();
},
_ => unreachable!(),
}
ring.debug_validate_invariants();
assert!(ring.len() <= ring.capacity());
idx += 3;
}
}
pub fn fuzz_insert_stress(data: &[u8]) {
if data.len() < 4 {
return;
}
let capacity = (data[0] as usize % 100).max(1);
let mut ring = ClockRing::new(capacity);
for chunk in data[1..].chunks(2) {
if chunk.len() < 2 {
break;
}
let key = chunk[0] as u32;
let value = chunk[1] as u32;
ring.insert(key, value);
assert!(ring.len() <= ring.capacity());
}
ring.debug_validate_invariants();
}
pub fn fuzz_eviction_patterns(data: &[u8]) {
if data.len() < 3 {
return;
}
let capacity = (data[0] as usize % 20).max(2);
let mut ring = ClockRing::new(capacity);
for i in 0..capacity {
ring.insert(i as u32, i as u32);
}
let mut idx = 1;
while idx < data.len() {
if idx + 1 >= data.len() {
break;
}
let key = data[idx] as u32 % capacity as u32;
let should_touch = data[idx + 1] % 2 == 0;
if should_touch {
ring.touch(&key);
}
let new_key = capacity as u32 + (idx as u32);
ring.insert(new_key, new_key);
ring.debug_validate_invariants();
assert!(ring.len() <= ring.capacity());
idx += 2;
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn test_fuzz_arbitrary_operations_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],
];
for input in inputs {
fuzz_arbitrary_operations(&input);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn test_fuzz_insert_stress_smoke() {
let inputs = vec![
vec![5, 1, 2, 3, 4, 5, 6, 7, 8],
vec![1, 0, 0, 0, 0, 0, 0],
vec![20; 100],
];
for input in inputs {
fuzz_insert_stress(&input);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn test_fuzz_eviction_patterns_smoke() {
let inputs = vec![
vec![5, 0, 1, 1, 0, 2, 1, 3, 0],
vec![3, 0, 0, 1, 1, 2, 0, 0, 1],
vec![10, 1, 0, 2, 1, 3, 0, 4, 1],
];
for input in inputs {
fuzz_eviction_patterns(&input);
}
}
}