Skip to main content

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}