ri/cache/backends/memory_backend.rs
1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # In-memory Cache Backend
21//!
22//! This module provides an in-memory cache implementation using DashMap for high performance
23//! and thread safety. It implements the RiCache trait, providing all standard cache operations
24//! with automatic expiration handling and comprehensive statistics.
25//!
26//! ## Key Features
27//!
28//! - **High Performance**: Uses DashMap for concurrent access without blocking
29//! - **Automatic Expiration**: Automatically removes expired entries on access
30//! - **Comprehensive Statistics**: Tracks hit count, miss count, and eviction count
31//! - **Thread Safe**: Safe for concurrent access from multiple threads
32//! - **LRU-like Behavior**: Touches entries on access to support LRU eviction (if implemented)
33//! - **Expired Entry Cleanup**: Provides a method to explicitly cleanup all expired entries
34//!
35//! ## Design Principles
36//!
37//! 1. **Non-blocking**: Uses DashMap for lock-free concurrent access
38//! 2. **Automatic Expiration**: Expired entries are removed when accessed
39//! 3. **Statistics-driven**: Comprehensive cache statistics for monitoring
40//! 4. **Simple API**: Implements the standard RiCache trait
41//! 5. **Memory Efficient**: Automatically cleans up expired entries
42//! 6. **Thread-safe**: Safe for use in multi-threaded applications
43//! 7. **Fast Access**: In-memory storage for minimal latency
44//! 8. **Easy to Use**: Simple constructor with no configuration required
45//!
46//! ## Usage
47//!
48//! ```rust
49//! use ri::prelude::*;
50//! use std::time::Duration;
51//!
52//! async fn example() -> RiResult<()> {
53//! // Create a new in-memory cache
54//! let cache = RiMemoryCache::new();
55//!
56//! // Create a cached value with 1-hour expiration
57//! let value = RiCachedValue::new(b"test_value".to_vec(), Duration::from_secs(3600));
58//!
59//! // Set the value in the cache
60//! cache.set("test_key", value).await?;
61//!
62//! // Get the value from the cache
63//! if let Some(retrieved_value) = cache.get("test_key").await {
64//! println!("Retrieved value: {:?}", retrieved_value.payload);
65//! }
66//!
67//! // Check if a key exists
68//! if cache.exists("test_key").await {
69//! println!("Key exists in cache");
70//! }
71//!
72//! // Get cache statistics
73//! let stats = cache.stats().await;
74//! println!("Cache hit rate: {:.2}%", stats.avg_hit_rate * 100.0);
75//!
76//! // Cleanup expired entries
77//! let cleaned = cache.cleanup_expired().await?;
78//! println!("Cleaned up {} expired entries", cleaned);
79//!
80//! Ok(())
81//! }
82//! ```
83
84use dashmap::DashMap;
85use std::sync::Arc;
86use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
87use crate::cache::{RiCache, RiCachedValue, RiCacheStats};
88use crate::core::RiResult;
89
90/// Atomic cache statistics for lock-free performance tracking.
91struct AtomicCacheStats {
92 hits: AtomicU64,
93 misses: AtomicU64,
94 entries: AtomicUsize,
95 memory_usage_bytes: AtomicUsize,
96 eviction_count: AtomicU64,
97}
98
99impl AtomicCacheStats {
100 fn new() -> Self {
101 Self {
102 hits: AtomicU64::new(0),
103 misses: AtomicU64::new(0),
104 entries: AtomicUsize::new(0),
105 memory_usage_bytes: AtomicUsize::new(0),
106 eviction_count: AtomicU64::new(0),
107 }
108 }
109
110 fn increment_hits(&self) {
111 self.hits.fetch_add(1, Ordering::Relaxed);
112 }
113
114 fn increment_misses(&self) {
115 self.misses.fetch_add(1, Ordering::Relaxed);
116 }
117
118 #[allow(dead_code)]
119 fn increment_evictions(&self) {
120 self.eviction_count.fetch_add(1, Ordering::Relaxed);
121 }
122
123 fn to_cache_stats(&self) -> RiCacheStats {
124 let hits = self.hits.load(Ordering::Relaxed);
125 let misses = self.misses.load(Ordering::Relaxed);
126 let total = hits + misses;
127 let avg_hit_rate = if total > 0 {
128 hits as f64 / total as f64
129 } else {
130 0.0
131 };
132
133 RiCacheStats {
134 hits,
135 misses,
136 entries: self.entries.load(Ordering::Relaxed),
137 memory_usage_bytes: self.memory_usage_bytes.load(Ordering::Relaxed),
138 avg_hit_rate,
139 hit_count: hits,
140 miss_count: misses,
141 eviction_count: self.eviction_count.load(Ordering::Relaxed),
142 }
143 }
144}
145
146/// In-memory cache implementation using DashMap for high performance and thread safety.
147///
148/// This struct provides an in-memory cache with automatic expiration handling, comprehensive
149/// statistics, and thread-safe concurrent access.
150pub struct RiMemoryCache {
151 /// Underlying storage using DashMap for concurrent access
152 store: Arc<DashMap<String, RiCachedValue>>,
153 /// Cache statistics using atomic operations for lock-free performance
154 stats: Arc<AtomicCacheStats>,
155}
156
157/// Maximum key length in bytes (1 KB)
158const MAX_KEY_LENGTH: usize = 1024;
159/// Maximum value length in bytes (10 MB)
160const MAX_VALUE_LENGTH: usize = 10 * 1024 * 1024;
161/// Maximum number of entries in cache (100,000)
162const MAX_ENTRIES: usize = 100_000;
163
164impl Default for RiMemoryCache {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170impl RiMemoryCache {
171 /// Creates a new in-memory cache instance.
172 ///
173 /// # Returns
174 ///
175 /// A new RiMemoryCache instance
176 pub fn new() -> Self {
177 RiMemoryCache {
178 store: Arc::new(DashMap::new()),
179 stats: Arc::new(AtomicCacheStats::new()),
180 }
181 }
182
183 /// Validates a cache key to prevent injection and memory exhaustion attacks.
184 ///
185 /// # Security
186 ///
187 /// Keys must:
188 /// - Not be empty
189 /// - Not exceed MAX_KEY_LENGTH (1 KB)
190 /// - Not contain control characters
191 /// - Not contain null bytes
192 fn validate_key(key: &str) -> crate::core::RiResult<()> {
193 if key.is_empty() {
194 return Err(crate::core::RiError::Other(
195 "Cache key cannot be empty".to_string()
196 ));
197 }
198
199 if key.len() > MAX_KEY_LENGTH {
200 return Err(crate::core::RiError::Other(format!(
201 "Cache key too long: {} bytes (max {} bytes)",
202 key.len(), MAX_KEY_LENGTH
203 )));
204 }
205
206 // Check for control characters and null bytes
207 for c in key.chars() {
208 if c.is_control() || c == '\0' {
209 return Err(crate::core::RiError::Other(
210 "Cache key contains invalid characters (control characters or null bytes)".to_string()
211 ));
212 }
213 }
214
215 Ok(())
216 }
217
218 /// Validates a cache value to prevent memory exhaustion attacks.
219 ///
220 /// # Security
221 ///
222 /// Values must not exceed MAX_VALUE_LENGTH (10 MB)
223 fn validate_value(value: &str) -> crate::core::RiResult<()> {
224 if value.len() > MAX_VALUE_LENGTH {
225 return Err(crate::core::RiError::Other(format!(
226 "Cache value too large: {} bytes (max {} bytes)",
227 value.len(), MAX_VALUE_LENGTH
228 )));
229 }
230
231 Ok(())
232 }
233
234 /// Checks if adding a new entry would exceed the maximum entry count.
235 fn check_entry_limit(&self) -> crate::core::RiResult<()> {
236 let current_entries = self.store.len();
237 if current_entries >= MAX_ENTRIES {
238 return Err(crate::core::RiError::Other(format!(
239 "Cache entry limit reached: {} entries (max {} entries)",
240 current_entries, MAX_ENTRIES
241 )));
242 }
243
244 Ok(())
245 }
246}
247
248#[async_trait::async_trait]
249impl RiCache for RiMemoryCache {
250 /// Gets a value from the cache by key.
251 ///
252 /// This method checks if the value exists and is not expired. If the value is expired,
253 /// it is removed from the cache and None is returned. Otherwise, the value is returned
254 /// and its last access time is updated.
255 ///
256 /// # Parameters
257 ///
258 /// - `key`: The key to retrieve
259 ///
260 /// # Returns
261 ///
262 /// An `Option<RiCachedValue>` containing the value if it exists and is not expired, or None otherwise
263 async fn get(&self, key: &str) -> RiResult<Option<String>> {
264 match self.store.get(key) {
265 Some(entry) => {
266 let value = entry.clone();
267 if value.is_expired() {
268 drop(entry);
269 self.store.remove(key);
270 self.stats.increment_misses();
271 Ok(None)
272 } else {
273 self.stats.increment_hits();
274 Ok(Some(value.value))
275 }
276 }
277 None => {
278 self.stats.increment_misses();
279 Ok(None)
280 }
281 }
282 }
283
284 /// Sets a value in the cache with the given key.
285 ///
286 /// # Security
287 ///
288 /// This method validates:
289 /// - Key length (max 1 KB)
290 /// - Value length (max 10 MB)
291 /// - Entry count (max 100,000 entries)
292 /// - Key format (no control characters or null bytes)
293 ///
294 /// # Parameters
295 ///
296 /// - `key`: The key to set
297 /// - `value`: The cached value to store
298 ///
299 /// # Returns
300 ///
301 /// A `RiResult<()>` indicating success or failure
302 async fn set(&self, key: &str, value: &str, ttl_seconds: Option<u64>) -> crate::core::RiResult<()> {
303 // Security: Validate key and value
304 Self::validate_key(key)?;
305 Self::validate_value(value)?;
306 self.check_entry_limit()?;
307
308 let cached_value = RiCachedValue::new(value.to_string(), ttl_seconds);
309 self.store.insert(key.to_string(), cached_value);
310 Ok(())
311 }
312
313 /// Deletes a value from the cache by key.
314 ///
315 /// # Parameters
316 ///
317 /// - `key`: The key to delete
318 ///
319 /// # Returns
320 ///
321 /// A `RiResult<bool>` indicating whether the key was found and deleted
322 async fn delete(&self, key: &str) -> crate::core::RiResult<bool> {
323 Ok(self.store.remove(key).is_some())
324 }
325
326 /// Checks if a key exists in the cache and is not expired.
327 ///
328 /// If the key exists but the value is expired, it is removed from the cache and false is returned.
329 ///
330 /// # Parameters
331 ///
332 /// - `key`: The key to check
333 ///
334 /// # Returns
335 ///
336 /// `true` if the key exists and is not expired, `false` otherwise
337 async fn exists(&self, key: &str) -> bool {
338 if let Some(entry) = self.store.get(key) {
339 if entry.is_expired() {
340 drop(entry);
341 self.store.remove(key);
342 false
343 } else {
344 true
345 }
346 } else {
347 false
348 }
349 }
350
351 /// Clears all entries from the cache.
352 ///
353 /// # Returns
354 ///
355 /// A `RiResult<()>` indicating success or failure
356 async fn clear(&self) -> crate::core::RiResult<()> {
357 self.store.clear();
358 Ok(())
359 }
360
361 /// Gets cache statistics.
362 ///
363 /// # Returns
364 ///
365 /// A `RiCacheStats` struct containing cache statistics
366 async fn stats(&self) -> RiCacheStats {
367 let mut stats = self.stats.to_cache_stats();
368 stats.entries = self.store.len();
369 stats
370 }
371
372 /// Cleans up all expired entries from the cache.
373 ///
374 /// # Returns
375 ///
376 /// A `RiResult<usize>` containing the number of expired entries cleaned up
377 async fn cleanup_expired(&self) -> crate::core::RiResult<usize> {
378 let mut cleaned = 0;
379 let keys: Vec<String> = self.store.iter().map(|entry| entry.key().clone()).collect();
380
381 for key in keys {
382 if let Some(entry) = self.store.get(&key) {
383 if entry.is_expired() {
384 drop(entry);
385 self.store.remove(&key);
386 cleaned += 1;
387 }
388 }
389 }
390
391 Ok(cleaned)
392 }
393
394 /// Gets all keys from the cache.
395 ///
396 /// # Returns
397 ///
398 /// A `RiResult<Vec<String>>` containing all cache keys
399 async fn keys(&self) -> crate::core::RiResult<Vec<String>> {
400 let keys: Vec<String> = self.store.iter().map(|entry| entry.key().clone()).collect();
401 Ok(keys)
402 }
403}