use crate::error::CacheResult;
use bytes::Bytes;
use futures_util::future::BoxFuture;
use parking_lot::RwLock;
use quick_cache::sync::Cache;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use tracing::{debug, info};
#[derive(Debug, Clone)]
struct CacheEntry {
value: Bytes,
expires_at: Instant,
}
impl CacheEntry {
fn new(value: Bytes, ttl: Duration) -> Self {
Self {
value,
expires_at: Instant::now() + ttl,
}
}
fn is_expired(&self) -> bool {
Instant::now() > self.expires_at
}
}
pub struct QuickCacheBackend {
cache: Cache<String, Arc<RwLock<CacheEntry>>>,
hits: Arc<AtomicU64>,
misses: Arc<AtomicU64>,
sets: Arc<AtomicU64>,
}
impl QuickCacheBackend {
pub fn new(max_capacity: u64) -> CacheResult<Self> {
info!(capacity = max_capacity, "Initializing QuickCache");
let cache = Cache::new(usize::try_from(max_capacity)?);
Ok(Self {
cache,
hits: Arc::new(AtomicU64::new(0)),
misses: Arc::new(AtomicU64::new(0)),
sets: Arc::new(AtomicU64::new(0)),
})
}
#[must_use]
pub const fn size(&self) -> usize {
0 }
}
use crate::traits::CacheBackend;
impl CacheBackend for QuickCacheBackend {
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<Bytes>> {
Box::pin(async move {
if let Some(entry_lock) = self.cache.get(key) {
let entry: parking_lot::RwLockReadGuard<'_, CacheEntry> = entry_lock.read();
if entry.is_expired() {
drop(entry); self.cache.remove(key);
self.misses.fetch_add(1, Ordering::Relaxed);
None
} else {
self.hits.fetch_add(1, Ordering::Relaxed);
Some(entry.value.clone())
}
} else {
self.misses.fetch_add(1, Ordering::Relaxed);
None
}
})
}
fn set_with_ttl<'a>(
&'a self,
key: &'a str,
value: Bytes,
ttl: Duration,
) -> BoxFuture<'a, CacheResult<()>> {
Box::pin(async move {
let entry = Arc::new(RwLock::new(CacheEntry::new(value, ttl)));
self.cache.insert(key.to_string(), entry);
self.sets.fetch_add(1, Ordering::Relaxed);
debug!(key = %key, ttl_secs = %ttl.as_secs(), "[QuickCache] Cached key with TTL");
Ok(())
})
}
fn remove<'a>(&'a self, key: &'a str) -> BoxFuture<'a, CacheResult<()>> {
Box::pin(async move {
self.cache.remove(key);
Ok(())
})
}
fn health_check(&self) -> BoxFuture<'_, bool> {
Box::pin(async move {
let test_key = "health_check_quickcache";
let test_value = Bytes::from_static(b"health_check");
match self
.set_with_ttl(test_key, test_value.clone(), Duration::from_secs(60))
.await
{
Ok(()) => match self.get(test_key).await {
Some(retrieved) => {
let _ = self.remove(test_key).await;
retrieved == test_value
}
None => false,
},
Err(_) => false,
}
})
}
fn name(&self) -> &'static str {
"QuickCache"
}
}