use rustc_hash::FxHashMap;
use std::hash::Hash;
use parking_lot::RwLock;
#[derive(Debug)]
#[repr(C)]
struct Entry<K, V> {
referenced: bool,
key: K,
value: V,
}
#[derive(Debug)]
pub struct ClockRing<K, V> {
slots: Vec<Option<Entry<K, V>>>,
index: FxHashMap<K, usize>,
hand: usize,
len: usize,
}
#[derive(Debug)]
pub struct ConcurrentClockRing<K, V> {
inner: RwLock<ClockRing<K, V>>,
}
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(&self, key: &K) -> bool {
let ring = self.inner.read();
ring.contains(key)
}
pub fn peek_with<R>(&self, key: &K, f: impl FnOnce(&V) -> R) -> Option<R> {
let ring = self.inner.read();
ring.peek(key).map(f)
}
pub fn get_with<R>(&self, key: &K, f: impl FnOnce(&V) -> R) -> Option<R> {
let mut ring = self.inner.write();
ring.get(key).map(f)
}
pub fn touch(&self, key: &K) -> bool {
let mut ring = self.inner.write();
ring.touch(key)
}
pub fn update(&self, key: &K, value: V) -> Option<V> {
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(&self, key: &K) -> Option<V> {
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 try_update(&self, key: &K, value: V) -> Option<Option<V>> {
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(&self, key: &K) -> Option<Option<V>> {
let mut ring = self.inner.try_write()?;
Some(ring.remove(key))
}
pub fn try_touch(&self, key: &K) -> Option<bool> {
let mut ring = self.inner.try_write()?;
Some(ring.touch(key))
}
pub fn try_peek_with<R>(&self, key: &K, f: impl FnOnce(&V) -> R) -> Option<Option<R>> {
let ring = self.inner.try_read()?;
Some(ring.peek(key).map(f))
}
pub fn try_get_with<R>(&self, key: &K, f: impl FnOnce(&V) -> R) -> Option<Option<R>> {
let mut ring = self.inner.try_write()?;
Some(ring.get(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>
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,
index: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
hand: 0,
len: 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();
}
pub fn clear(&mut self) {
self.index.clear();
for slot in &mut self.slots {
*slot = None;
}
self.len = 0;
self.hand = 0;
}
pub fn clear_shrink(&mut self) {
self.clear();
self.index.shrink_to_fit();
self.slots.shrink_to_fit();
}
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>>>()
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn contains(&self, key: &K) -> bool {
self.index.contains_key(key)
}
pub fn peek(&self, key: &K) -> Option<&V> {
let idx = *self.index.get(key)?;
self.slots.get(idx)?.as_ref().map(|entry| &entry.value)
}
pub fn get(&mut self, key: &K) -> Option<&V> {
let idx = *self.index.get(key)?;
let entry = self.slots.get_mut(idx)?.as_mut()?;
entry.referenced = true;
Some(&entry.value)
}
pub fn touch(&mut self, key: &K) -> bool {
let idx = match self.index.get(key) {
Some(idx) => *idx,
None => return false,
};
if let Some(entry) = self.slots.get_mut(idx).and_then(|slot| slot.as_mut()) {
entry.referenced = true;
return true;
}
false
}
pub fn update(&mut self, key: &K, value: V) -> Option<V> {
let idx = *self.index.get(key)?;
let entry = self.slots.get_mut(idx)?.as_mut()?;
let old = std::mem::replace(&mut entry.value, value);
entry.referenced = 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;
entry.referenced = true;
}
return None;
}
loop {
let idx = self.hand;
if let Some(entry) = self.slots.get_mut(idx).and_then(|slot| slot.as_mut()) {
if entry.referenced {
entry.referenced = false;
self.advance_hand();
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,
referenced: false,
});
self.index.insert(key, idx);
self.advance_hand();
return Some((evicted.key, evicted.value));
}
let entry_key = key.clone();
self.slots[idx] = Some(Entry {
key: entry_key,
value,
referenced: false,
});
self.index.insert(key, idx);
self.len += 1;
self.advance_hand();
return None;
}
}
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 !entry.referenced {
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..cap {
let idx = self.hand;
if let Some(entry) = self.slots.get_mut(idx).and_then(|slot| slot.as_mut()) {
if entry.referenced {
entry.referenced = false;
self.advance_hand();
continue;
}
let evicted = self.slots[idx].take().expect("occupied slot missing");
self.index.remove(&evicted.key);
self.len -= 1;
self.advance_hand();
return Some((evicted.key, evicted.value));
}
self.advance_hand();
}
None
}
#[cfg(any(test, debug_assertions))]
pub fn debug_snapshot_slots(&self) -> Vec<Option<(&K, bool)>> {
self.slots
.iter()
.map(|slot| slot.as_ref().map(|entry| (&entry.key, entry.referenced)))
.collect()
}
pub fn remove(&mut self, key: &K) -> Option<V> {
let idx = self.index.remove(key)?;
let entry = self.slots.get_mut(idx)?.take()?;
self.len -= 1;
Some(entry.value)
}
fn advance_hand(&mut self) {
let cap = self.capacity();
if cap == 0 {
self.hand = 0;
} else {
self.hand = (self.hand + 1) % cap;
}
}
#[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());
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);
}
}
}
#[cfg(test)]
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!(ring.contains(&"d"));
assert!(!ring.contains(&"b"));
if evicted.is_some() {
assert_eq!(ring.len(), 2);
} else {
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 first = ring.pop_victim();
if first.is_none() {
let second = ring.pop_victim();
assert!(matches!(second, Some(("a", 1)) | Some(("b", 2))));
assert_eq!(ring.len(), 1);
} else {
assert!(matches!(first, 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 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());
}
}