use rustc_hash::FxHashMap;
use std::borrow::Borrow;
use std::hash::Hash;
#[cfg(feature = "concurrency")]
use parking_lot::RwLock;
#[derive(Debug, Clone)]
struct Entry<K, V> {
key: K,
value: V,
}
#[must_use]
#[derive(Debug, Clone)]
pub struct ClockRing<K, V> {
slots: Vec<Option<Entry<K, V>>>,
referenced: Vec<bool>,
index: FxHashMap<K, usize>,
hand: usize,
len: usize,
#[cfg(feature = "metrics")]
sweep_hand_advances: u64,
#[cfg(feature = "metrics")]
sweep_ref_bit_resets: u64,
}
#[cfg(feature = "concurrency")]
#[must_use]
#[derive(Debug)]
pub struct ConcurrentClockRing<K, V> {
inner: RwLock<ClockRing<K, V>>,
}
#[cfg(feature = "concurrency")]
impl<K, V> ConcurrentClockRing<K, V>
where
K: Eq + Hash + Clone,
{
pub fn new(capacity: usize) -> Self {
Self {
inner: RwLock::new(ClockRing::new(capacity)),
}
}
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 mut ring = self.inner.write();
ring.insert(key, value)
}
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 mut ring = self.inner.try_write()?;
Some(ring.insert(key, value))
}
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> ClockRing<K, V> {
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>
where
K: Eq + Hash + Clone,
{
pub fn new(capacity: usize) -> Self {
let mut slots = Vec::with_capacity(capacity);
slots.resize_with(capacity, || None);
Self {
slots,
referenced: vec![false; capacity],
index: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
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 {
std::mem::size_of::<Self>()
+ self.index.capacity() * std::mem::size_of::<(K, usize)>()
+ self.slots.capacity() * std::mem::size_of::<Option<Entry<K, V>>>()
+ self.referenced.capacity() * std::mem::size_of::<bool>()
}
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)?;
self.referenced[idx] = true;
self.slots.get(idx)?.as_ref().map(|entry| &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)?;
self.referenced[idx] = true;
self.slots
.get_mut(idx)?
.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,
};
self.referenced[idx] = true;
true
}
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)> {
if self.capacity() == 0 {
return None;
}
if let Some(&idx) = self.index.get(&key) {
if let Some(entry) = self.slots.get_mut(idx).and_then(|slot| slot.as_mut()) {
entry.value = value;
self.referenced[idx] = true;
}
return None;
}
if self.len < self.capacity() {
let cap = self.capacity();
for offset in 0..cap {
let idx = (self.hand + offset) % 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 += 1;
self.hand = (idx + 1) % cap;
return None;
}
}
debug_assert!(false, "len < capacity but no empty slot found");
}
let cap = self.capacity();
for _ in 0..(2 * cap) {
let idx = self.hand;
if self.referenced[idx] {
self.referenced[idx] = false;
#[cfg(feature = "metrics")]
{
self.sweep_ref_bit_resets += 1;
}
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances += 1;
}
continue;
}
let evicted = self.slots[idx].take().expect("occupied slot missing");
self.index.remove(&evicted.key);
let entry_key = key.clone();
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 += 1;
}
return Some((evicted.key, evicted.value));
}
debug_assert!(
false,
"insert sweep exceeded 2*capacity without finding victim"
);
None
}
#[must_use]
pub fn peek_victim(&self) -> Option<(&K, &V)> {
if self.capacity() == 0 || self.len == 0 {
return None;
}
let cap = self.capacity();
for offset in 0..cap {
let idx = (self.hand + offset) % 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));
}
}
}
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..(2 * cap) {
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 += 1;
}
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances += 1;
}
continue;
}
let evicted = self.slots[idx].take().expect("occupied slot missing");
self.index.remove(&evicted.key);
self.referenced[idx] = false;
self.len -= 1;
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances += 1;
}
return Some((evicted.key, evicted.value));
}
self.advance_hand();
#[cfg(feature = "metrics")]
{
self.sweep_hand_advances += 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 -= 1;
Some(entry.value)
}
fn advance_hand(&mut self) {
self.hand = (self.hand + 1) % 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> Extend<(K, V)> for ClockRing<K, V>
where
K: Eq + Hash + Clone,
{
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
for (key, value) in iter {
self.insert(key, value);
}
}
}
#[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> IntoIterator for ClockRing<K, V> {
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> IntoIterator for &'a ClockRing<K, V> {
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 ClockRing<K, V> {
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> ConcurrentClockRing<K, V> {
pub fn into_inner(self) -> ClockRing<K, V> {
self.inner.into_inner()
}
}
#[cfg(test)]
#[allow(unused_must_use)]
mod tests {
use super::*;
#[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();
}
#[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);
}
}
}