Something went wrong. Try again.
A fork of @slices.network/slices forked from slices.network/slices
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185//! In-memory cache implementation with TTL support.//!//! Provides a thread-safe, in-memory cache with automatic expiration.//! Used as a fallback when Redis is unavailable or for local development.
use super::Cache;use anyhow::Result;use async_trait::async_trait;use serde::{Deserialize, Serialize};use std::collections::HashMap;use std::sync::Arc;use std::time::{Duration, Instant};use tokio::sync::RwLock;use tracing::{debug, warn};
/// Cache entry: (serialized_value, optional_expiry)type CacheEntry = (String, Option<Instant>);
/// In-memory cache implementation with TTL support.////// Uses Arc<RwLock<HashMap>> for thread-safe concurrent access./// Expired entries are checked on read but not automatically cleaned up.pub struct InMemoryCache { data: Arc<RwLock<HashMap<String, CacheEntry>>>, default_ttl_seconds: u64,}
impl InMemoryCache { /// Create a new in-memory cache with optional default TTL. /// /// # Arguments /// * `default_ttl_seconds` - Default expiration time in seconds (default: 3600) pub fn new(default_ttl_seconds: Option<u64>) -> Self { Self { data: Arc::new(RwLock::new(HashMap::new())), default_ttl_seconds: default_ttl_seconds.unwrap_or(3600), } }}
#[async_trait]impl Cache for InMemoryCache { async fn get<T>(&mut self, key: &str) -> Result<Option<T>> where T: for<'de> Deserialize<'de> + Send, { let data = self.data.read().await;
if let Some((serialized, expiry)) = data.get(key) { if let Some(exp) = expiry && *exp <= Instant::now() { debug!(cache_key = %key, "Cache entry expired"); return Ok(None); }
match serde_json::from_str::<T>(serialized) { Ok(value) => Ok(Some(value)), Err(e) => { warn!( error = ?e, cache_key = %key, "Failed to deserialize cached value" ); Ok(None) } } } else { Ok(None) } }
async fn set<T>(&mut self, key: &str, value: &T, ttl_seconds: Option<u64>) -> Result<()> where T: Serialize + Send + Sync, { let ttl = ttl_seconds.unwrap_or(self.default_ttl_seconds);
match serde_json::to_string(value) { Ok(serialized) => { let expiry = if ttl > 0 { Some(Instant::now() + Duration::from_secs(ttl)) } else { None };
let mut data = self.data.write().await; data.insert(key.to_string(), (serialized, expiry));
debug!( cache_key = %key, ttl_seconds = ttl, "Cached value in memory" ); Ok(()) } Err(e) => { warn!( error = ?e, cache_key = %key, "Failed to serialize value for caching" ); Ok(()) } } }
async fn delete(&mut self, key: &str) -> Result<()> { let mut data = self.data.write().await; data.remove(key); debug!(cache_key = %key, "Deleted key from in-memory cache"); Ok(()) }
async fn set_multiple<T>(&mut self, items: Vec<(&str, &T, Option<u64>)>) -> Result<()> where T: Serialize + Send + Sync, { if items.is_empty() { return Ok(()); }
let mut data = self.data.write().await; let mut success_count = 0;
for (key, value, ttl) in &items { match serde_json::to_string(value) { Ok(serialized) => { let ttl_to_use = ttl.unwrap_or(self.default_ttl_seconds); let expiry = if ttl_to_use > 0 { Some(Instant::now() + Duration::from_secs(ttl_to_use)) } else { None };
data.insert(key.to_string(), (serialized, expiry)); success_count += 1; } Err(e) => { warn!( error = ?e, cache_key = %key, "Failed to serialize value for bulk caching" ); } } }
debug!( items_count = success_count, total_items = items.len(), "Successfully bulk cached items in memory" ); Ok(()) }
async fn ping(&mut self) -> Result<bool> { Ok(true) }
async fn get_info(&mut self) -> Result<String> { let data = self.data.read().await; let now = Instant::now();
let mut total_entries = 0; let mut expired_entries = 0;
for (_, expiry) in data.values() { total_entries += 1; if let Some(exp) = expiry && *exp <= now { expired_entries += 1; } }
Ok(format!( "InMemoryCache: {} total entries, {} expired, {} active", total_entries, expired_entries, total_entries - expired_entries )) }}