#![cfg_attr(coverage_nightly, coverage(off))]
use crate::services::cache::base::{CacheEntry, CacheStats, CacheStrategy};
use lru::LruCache;
use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::num::NonZeroUsize;
use std::sync::Arc;
pub struct ContentCache<T: CacheStrategy> {
cache: Arc<RwLock<LruCache<String, CacheEntry<T::Value>>>>,
hashes: Arc<RwLock<FxHashMap<String, u64>>>,
pub stats: CacheStats,
strategy: Arc<T>,
}
impl<T: CacheStrategy> ContentCache<T> {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new(strategy: T) -> Self {
let max_size = NonZeroUsize::new(strategy.max_size())
.unwrap_or(NonZeroUsize::new(100).expect("internal error"));
Self {
cache: Arc::new(RwLock::new(LruCache::new(max_size))),
hashes: Arc::new(RwLock::new(FxHashMap::default())),
stats: CacheStats::new(),
strategy: Arc::new(strategy),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get(&self, key: &T::Key) -> Option<Arc<T::Value>> {
let cache_key = self.strategy.cache_key(key);
let mut cache = self.cache.write();
if let Some(entry) = cache.get_mut(&cache_key) {
if let Some(ttl) = self.strategy.ttl() {
if entry.age() > ttl {
self.stats.remove_bytes(entry.size_bytes);
cache.pop(&cache_key);
self.stats.record_miss();
return None;
}
}
if self.strategy.validate(key, &entry.value) {
entry.access();
self.stats.record_hit();
Some(entry.value.clone())
} else {
self.stats.remove_bytes(entry.size_bytes);
cache.pop(&cache_key);
self.stats.record_miss();
None
}
} else {
self.stats.record_miss();
None
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn put(&self, key: T::Key, value: T::Value) {
let cache_key = self.strategy.cache_key(&key);
let size_bytes = self.estimate_size(&value);
let entry = CacheEntry::new(value, size_bytes);
let mut cache = self.cache.write();
if let Some((_key, evicted)) = cache.push(cache_key.clone(), entry) {
self.stats.remove_bytes(evicted.size_bytes);
self.stats.record_eviction();
}
self.stats.add_bytes(size_bytes);
let mut hasher = DefaultHasher::new();
cache_key.hash(&mut hasher);
let hash = hasher.finish();
self.hashes.write().insert(cache_key, hash);
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn remove(&self, key: &T::Key) -> Option<Arc<T::Value>> {
let cache_key = self.strategy.cache_key(key);
let mut cache = self.cache.write();
if let Some(entry) = cache.pop(&cache_key) {
self.stats.remove_bytes(entry.size_bytes);
self.hashes.write().remove(&cache_key);
Some(entry.value)
} else {
None
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn clear(&self) {
let mut cache = self.cache.write();
for (_, entry) in cache.iter() {
self.stats.remove_bytes(entry.size_bytes);
}
cache.clear();
self.hashes.write().clear();
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn evict_lru(&self) {
let mut cache = self.cache.write();
if let Some((_, entry)) = cache.pop_lru() {
self.stats.remove_bytes(entry.size_bytes);
self.stats.record_eviction();
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn len(&self) -> usize {
self.cache.read().len()
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn is_empty(&self) -> bool {
self.cache.read().is_empty()
}
fn estimate_size(&self, value: &T::Value) -> usize {
std::mem::size_of_val(value) * 2
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn metrics(&self) -> CacheMetrics {
let cache = self.cache.read();
CacheMetrics {
entries: cache.len(),
memory_bytes: self.stats.memory_usage(),
hit_rate: self.stats.hit_rate(),
total_requests: self.stats.total_requests(),
evictions: self
.stats
.evictions
.load(std::sync::atomic::Ordering::Relaxed),
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn hot_entries(&self, limit: usize) -> Vec<(String, u32)> {
let cache = self.cache.read();
let mut entries: Vec<(String, u32)> = cache
.iter()
.map(|(k, v)| {
(
k.clone(),
v.access_count.load(std::sync::atomic::Ordering::Relaxed),
)
})
.collect();
entries.sort_by(|a, b| b.1.cmp(&a.1));
entries.truncate(limit);
entries
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn invalidate_matching<F>(&self, predicate: F)
where
F: Fn(&str) -> bool,
{
let mut cache = self.cache.write();
let keys_to_remove: Vec<String> = cache
.iter()
.filter(|(k, _)| predicate(k))
.map(|(k, _)| k.clone())
.collect();
for key in keys_to_remove {
if let Some(entry) = cache.pop(&key) {
self.stats.remove_bytes(entry.size_bytes);
self.stats.record_eviction();
}
}
}
}
#[derive(Debug, Clone)]
pub struct CacheMetrics {
pub entries: usize,
pub memory_bytes: usize,
pub hit_rate: f64,
pub total_requests: u64,
pub evictions: u64,
}
impl CacheMetrics {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn memory_mb(&self) -> f64 {
self.memory_bytes as f64 / (1024.0 * 1024.0)
}
}
impl<T: CacheStrategy> Clone for ContentCache<T>
where
T: Clone,
{
fn clone(&self) -> Self {
Self::new((*self.strategy).clone())
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
struct TestStrategy;
impl CacheStrategy for TestStrategy {
type Key = String;
type Value = String;
fn cache_key(&self, input: &Self::Key) -> String {
input.clone()
}
fn validate(&self, _key: &Self::Key, _value: &Self::Value) -> bool {
true
}
fn ttl(&self) -> Option<Duration> {
None
}
fn max_size(&self) -> usize {
100
}
}
#[test]
fn test_content_cache_basic() {
let cache = ContentCache::new(TestStrategy);
assert_eq!(cache.len(), 0);
cache.put("key1".to_string(), "value1".to_string());
assert_eq!(cache.len(), 1);
let value = cache.get(&"key1".to_string());
assert!(value.is_some());
assert_eq!(*value.expect("internal error"), "value1");
}
#[test]
fn test_content_cache_clear() {
let cache = ContentCache::new(TestStrategy);
for i in 0..5 {
cache.put(format!("key{}", i), format!("value{}", i));
}
assert_eq!(cache.len(), 5);
cache.clear();
assert_eq!(cache.len(), 0);
}
#[test]
fn test_content_cache_stats() {
let cache = ContentCache::new(TestStrategy);
assert_eq!(
cache.stats.hits.load(std::sync::atomic::Ordering::Relaxed),
0
);
assert_eq!(
cache
.stats
.misses
.load(std::sync::atomic::Ordering::Relaxed),
0
);
cache.get(&"missing".to_string());
assert_eq!(
cache
.stats
.misses
.load(std::sync::atomic::Ordering::Relaxed),
1
);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}