use std::hash::Hash;
use crate::ds::ClockRing;
use crate::ds::clock_ring::{IntoIter, Iter, IterMut};
use crate::traits::{CoreCache, MutableCache, ReadOnlyCache};
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::ClockMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::ClockMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::MetricsSnapshotProvider;
pub struct ClockCache<K, V> {
ring: ClockRing<K, V>,
#[cfg(feature = "metrics")]
metrics: ClockMetrics,
}
impl<K, V> ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
pub fn new(capacity: usize) -> Self {
Self {
ring: ClockRing::new(capacity),
#[cfg(feature = "metrics")]
metrics: ClockMetrics::default(),
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.ring.is_empty()
}
#[inline]
pub fn iter(&self) -> Iter<'_, K, V> {
self.ring.iter()
}
#[inline]
pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
self.ring.iter_mut()
}
#[inline]
pub fn as_ring(&self) -> &ClockRing<K, V> {
&self.ring
}
#[inline]
pub fn as_ring_mut(&mut self) -> &mut ClockRing<K, V> {
&mut self.ring
}
}
impl<K, V> ReadOnlyCache<K, V> for ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
fn contains(&self, key: &K) -> bool {
self.ring.contains(key)
}
fn len(&self) -> usize {
self.ring.len()
}
fn capacity(&self) -> usize {
self.ring.capacity()
}
}
impl<K, V> CoreCache<K, V> for ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
fn insert(&mut self, key: K, value: V) -> Option<V> {
#[cfg(feature = "metrics")]
{
self.metrics.insert_calls += 1;
}
if self.ring.capacity() == 0 {
return None;
}
if self.ring.contains(&key) {
#[cfg(feature = "metrics")]
{
self.metrics.insert_updates += 1;
}
return self.ring.update(&key, value);
}
#[cfg(feature = "metrics")]
{
self.metrics.insert_new += 1;
}
#[cfg(feature = "metrics")]
let need_evict = self.ring.len() >= self.ring.capacity();
#[cfg(feature = "metrics")]
let ha_before = self.ring.sweep_hand_advances();
#[cfg(feature = "metrics")]
let rb_before = self.ring.sweep_ref_bit_resets();
let evicted = self.ring.insert(key, value);
#[cfg(feature = "metrics")]
if need_evict {
self.metrics.evict_calls += 1;
if evicted.is_some() {
self.metrics.evicted_entries += 1;
}
self.metrics.hand_advances += self.ring.sweep_hand_advances() - ha_before;
self.metrics.ref_bit_resets += self.ring.sweep_ref_bit_resets() - rb_before;
}
#[allow(unused_variables)]
let _ = evicted;
None
}
#[inline]
fn get(&mut self, key: &K) -> Option<&V> {
let result = self.ring.get(key);
#[cfg(feature = "metrics")]
if result.is_some() {
self.metrics.get_calls += 1;
self.metrics.get_hits += 1;
} else {
self.metrics.get_calls += 1;
self.metrics.get_misses += 1;
}
result
}
fn clear(&mut self) {
self.ring.clear();
#[cfg(feature = "metrics")]
{
use crate::metrics::traits::CoreMetricsRecorder;
self.metrics.record_clear();
}
}
}
impl<K, V> MutableCache<K, V> for ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
fn remove(&mut self, key: &K) -> Option<V> {
self.ring.remove(key)
}
}
#[cfg(feature = "metrics")]
impl<K, V> ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
pub fn metrics_snapshot(&self) -> ClockMetricsSnapshot {
ClockMetricsSnapshot {
get_calls: self.metrics.get_calls,
get_hits: self.metrics.get_hits,
get_misses: self.metrics.get_misses,
insert_calls: self.metrics.insert_calls,
insert_updates: self.metrics.insert_updates,
insert_new: self.metrics.insert_new,
evict_calls: self.metrics.evict_calls,
evicted_entries: self.metrics.evicted_entries,
hand_advances: self.metrics.hand_advances,
ref_bit_resets: self.metrics.ref_bit_resets,
cache_len: self.ring.len(),
capacity: self.ring.capacity(),
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<ClockMetricsSnapshot> for ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
fn snapshot(&self) -> ClockMetricsSnapshot {
self.metrics_snapshot()
}
}
impl<K, V> std::fmt::Debug for ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClockCache")
.field("capacity", &self.ring.capacity())
.field("len", &self.ring.len())
.finish_non_exhaustive()
}
}
impl<K, V> Clone for ClockCache<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
fn clone(&self) -> Self {
Self {
ring: self.ring.clone(),
#[cfg(feature = "metrics")]
metrics: self.metrics,
}
}
}
impl<K, V> AsRef<ClockRing<K, V>> for ClockCache<K, V> {
fn as_ref(&self) -> &ClockRing<K, V> {
&self.ring
}
}
impl<K, V> AsMut<ClockRing<K, V>> for ClockCache<K, V> {
fn as_mut(&mut self) -> &mut ClockRing<K, V> {
&mut self.ring
}
}
impl<K, V> IntoIterator for ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
self.ring.into_iter()
}
}
impl<'a, K, V> IntoIterator for &'a ClockCache<K, V> {
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.ring.iter()
}
}
impl<'a, K, V> IntoIterator for &'a mut ClockCache<K, V> {
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.ring.iter_mut()
}
}
impl<K, V> Extend<(K, V)> for ClockCache<K, V>
where
K: Clone + Eq + Hash,
{
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
self.ring.extend(iter);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::MutableCache;
#[allow(dead_code)]
const _: () = {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
fn check() {
assert_send::<ClockCache<String, i32>>();
assert_sync::<ClockCache<String, i32>>();
}
};
mod basic_operations {
use super::*;
#[test]
fn test_new_cache() {
let cache: ClockCache<i32, i32> = ClockCache::new(10);
assert_eq!(cache.capacity(), 10);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_insert_and_get() {
let mut cache = ClockCache::new(10);
cache.insert("a", 1);
cache.insert("b", 2);
assert_eq!(cache.get(&"a"), Some(&1));
assert_eq!(cache.get(&"b"), Some(&2));
assert_eq!(cache.get(&"c"), None);
}
#[test]
fn test_insert_returns_old_value() {
let mut cache = ClockCache::new(10);
assert_eq!(cache.insert("a", 1), None);
assert_eq!(cache.insert("a", 2), Some(1));
assert_eq!(cache.get(&"a"), Some(&2));
}
#[test]
fn test_contains() {
let mut cache = ClockCache::new(10);
cache.insert("a", 1);
assert!(cache.contains(&"a"));
assert!(!cache.contains(&"b"));
}
#[test]
fn test_remove() {
let mut cache = ClockCache::new(10);
cache.insert("a", 1);
cache.insert("b", 2);
assert_eq!(cache.remove(&"a"), Some(1));
assert!(!cache.contains(&"a"));
assert_eq!(cache.len(), 1);
assert_eq!(cache.remove(&"c"), None);
}
#[test]
fn test_clear() {
let mut cache = ClockCache::new(10);
cache.insert("a", 1);
cache.insert("b", 2);
cache.clear();
assert!(cache.is_empty());
assert!(!cache.contains(&"a"));
}
}
mod eviction {
use super::*;
#[test]
fn test_eviction_at_capacity() {
let mut cache = ClockCache::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
assert_eq!(cache.len(), 3);
cache.insert("d", 4);
assert_eq!(cache.len(), 3);
let count = [
cache.contains(&"a"),
cache.contains(&"b"),
cache.contains(&"c"),
]
.iter()
.filter(|&&x| x)
.count();
assert_eq!(count, 2);
assert!(cache.contains(&"d"));
}
#[test]
fn test_second_chance() {
let mut cache = ClockCache::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.get(&"a");
cache.insert("d", 4);
assert!(cache.contains(&"a"));
assert!(cache.contains(&"d"));
assert_eq!(cache.len(), 3);
}
#[test]
fn test_all_referenced_eviction() {
let mut cache = ClockCache::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.get(&"a");
cache.get(&"b");
cache.get(&"c");
cache.insert("d", 4);
assert_eq!(cache.len(), 3);
}
#[test]
fn test_repeated_eviction() {
let mut cache = ClockCache::new(2);
for i in 0..100 {
cache.insert(i, i * 10);
}
assert_eq!(cache.len(), 2);
}
}
mod edge_cases {
use super::*;
#[test]
fn test_capacity_one() {
let mut cache = ClockCache::new(1);
cache.insert("a", 1);
assert_eq!(cache.get(&"a"), Some(&1));
cache.insert("b", 2);
assert!(!cache.contains(&"a"));
assert!(cache.contains(&"b"));
}
#[test]
fn test_zero_capacity_clamped() {
let cache: ClockCache<i32, i32> = ClockCache::new(0);
assert_eq!(cache.capacity(), 0);
}
#[test]
fn test_string_keys() {
let mut cache = ClockCache::new(10);
cache.insert("hello".to_string(), 1);
cache.insert("world".to_string(), 2);
assert_eq!(cache.get(&"hello".to_string()), Some(&1));
}
#[test]
fn test_large_capacity() {
let mut cache = ClockCache::new(10000);
for i in 0..5000 {
cache.insert(i, i * 2);
}
assert_eq!(cache.len(), 5000);
for i in 0..5000 {
assert_eq!(cache.get(&i), Some(&(i * 2)));
}
}
}
mod ring_access {
use super::*;
#[test]
fn test_as_ring() {
let mut cache = ClockCache::new(10);
cache.insert("a", 1);
cache.insert("b", 2);
assert_eq!(cache.as_ring().peek(&"a"), Some(&1));
assert!(cache.as_ring_mut().touch(&"b"));
}
#[test]
fn test_peek_vs_get() {
let mut cache = ClockCache::new(2);
cache.insert("a", 1);
cache.insert("b", 2);
let _ = cache.as_ring().peek(&"a");
cache.insert("c", 3);
assert!(!cache.contains(&"a"));
assert!(cache.contains(&"b"));
assert!(cache.contains(&"c"));
}
}
}