Highly ambitious ATProtocol AppView service and sdks
at main 184 lines 5.5 kB view raw
1//! In-memory cache implementation with TTL support. 2//! 3//! Provides a thread-safe, in-memory cache with automatic expiration. 4//! Used as a fallback when Redis is unavailable or for local development. 5 6use super::Cache; 7use anyhow::Result; 8use async_trait::async_trait; 9use serde::{Deserialize, Serialize}; 10use std::collections::HashMap; 11use std::sync::Arc; 12use std::time::{Duration, Instant}; 13use tokio::sync::RwLock; 14use tracing::{debug, warn}; 15 16/// Cache entry: (serialized_value, optional_expiry) 17type CacheEntry = (String, Option<Instant>); 18 19/// In-memory cache implementation with TTL support. 20/// 21/// Uses Arc<RwLock<HashMap>> for thread-safe concurrent access. 22/// Expired entries are checked on read but not automatically cleaned up. 23pub struct InMemoryCache { 24 data: Arc<RwLock<HashMap<String, CacheEntry>>>, 25 default_ttl_seconds: u64, 26} 27 28impl InMemoryCache { 29 /// Create a new in-memory cache with optional default TTL. 30 /// 31 /// # Arguments 32 /// * `default_ttl_seconds` - Default expiration time in seconds (default: 3600) 33 pub fn new(default_ttl_seconds: Option<u64>) -> Self { 34 Self { 35 data: Arc::new(RwLock::new(HashMap::new())), 36 default_ttl_seconds: default_ttl_seconds.unwrap_or(3600), 37 } 38 } 39} 40 41#[async_trait] 42impl Cache for InMemoryCache { 43 async fn get<T>(&mut self, key: &str) -> Result<Option<T>> 44 where 45 T: for<'de> Deserialize<'de> + Send, 46 { 47 let data = self.data.read().await; 48 49 if let Some((serialized, expiry)) = data.get(key) { 50 if let Some(exp) = expiry 51 && *exp <= Instant::now() 52 { 53 debug!(cache_key = %key, "Cache entry expired"); 54 return Ok(None); 55 } 56 57 match serde_json::from_str::<T>(serialized) { 58 Ok(value) => Ok(Some(value)), 59 Err(e) => { 60 warn!( 61 error = ?e, 62 cache_key = %key, 63 "Failed to deserialize cached value" 64 ); 65 Ok(None) 66 } 67 } 68 } else { 69 Ok(None) 70 } 71 } 72 73 async fn set<T>(&mut self, key: &str, value: &T, ttl_seconds: Option<u64>) -> Result<()> 74 where 75 T: Serialize + Send + Sync, 76 { 77 let ttl = ttl_seconds.unwrap_or(self.default_ttl_seconds); 78 79 match serde_json::to_string(value) { 80 Ok(serialized) => { 81 let expiry = if ttl > 0 { 82 Some(Instant::now() + Duration::from_secs(ttl)) 83 } else { 84 None 85 }; 86 87 let mut data = self.data.write().await; 88 data.insert(key.to_string(), (serialized, expiry)); 89 90 debug!( 91 cache_key = %key, 92 ttl_seconds = ttl, 93 "Cached value in memory" 94 ); 95 Ok(()) 96 } 97 Err(e) => { 98 warn!( 99 error = ?e, 100 cache_key = %key, 101 "Failed to serialize value for caching" 102 ); 103 Ok(()) 104 } 105 } 106 } 107 108 async fn delete(&mut self, key: &str) -> Result<()> { 109 let mut data = self.data.write().await; 110 data.remove(key); 111 debug!(cache_key = %key, "Deleted key from in-memory cache"); 112 Ok(()) 113 } 114 115 async fn set_multiple<T>(&mut self, items: Vec<(&str, &T, Option<u64>)>) -> Result<()> 116 where 117 T: Serialize + Send + Sync, 118 { 119 if items.is_empty() { 120 return Ok(()); 121 } 122 123 let mut data = self.data.write().await; 124 let mut success_count = 0; 125 126 for (key, value, ttl) in &items { 127 match serde_json::to_string(value) { 128 Ok(serialized) => { 129 let ttl_to_use = ttl.unwrap_or(self.default_ttl_seconds); 130 let expiry = if ttl_to_use > 0 { 131 Some(Instant::now() + Duration::from_secs(ttl_to_use)) 132 } else { 133 None 134 }; 135 136 data.insert(key.to_string(), (serialized, expiry)); 137 success_count += 1; 138 } 139 Err(e) => { 140 warn!( 141 error = ?e, 142 cache_key = %key, 143 "Failed to serialize value for bulk caching" 144 ); 145 } 146 } 147 } 148 149 debug!( 150 items_count = success_count, 151 total_items = items.len(), 152 "Successfully bulk cached items in memory" 153 ); 154 Ok(()) 155 } 156 157 async fn ping(&mut self) -> Result<bool> { 158 Ok(true) 159 } 160 161 async fn get_info(&mut self) -> Result<String> { 162 let data = self.data.read().await; 163 let now = Instant::now(); 164 165 let mut total_entries = 0; 166 let mut expired_entries = 0; 167 168 for (_, expiry) in data.values() { 169 total_entries += 1; 170 if let Some(exp) = expiry 171 && *exp <= now 172 { 173 expired_entries += 1; 174 } 175 } 176 177 Ok(format!( 178 "InMemoryCache: {} total entries, {} expired, {} active", 179 total_entries, 180 expired_entries, 181 total_entries - expired_entries 182 )) 183 } 184}