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
157impl Default for RiMemoryCache {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163impl RiMemoryCache {
164    /// Creates a new in-memory cache instance.
165    ///
166    /// # Returns
167    ///
168    /// A new RiMemoryCache instance
169    pub fn new() -> Self {
170        RiMemoryCache {
171            store: Arc::new(DashMap::new()),
172            stats: Arc::new(AtomicCacheStats::new()),
173        }
174    }
175}
176
177#[async_trait::async_trait]
178impl RiCache for RiMemoryCache {
179    /// Gets a value from the cache by key.
180    ///
181    /// This method checks if the value exists and is not expired. If the value is expired,
182    /// it is removed from the cache and None is returned. Otherwise, the value is returned
183    /// and its last access time is updated.
184    ///
185    /// # Parameters
186    ///
187    /// - `key`: The key to retrieve
188    ///
189    /// # Returns
190    ///
191    /// An `Option<RiCachedValue>` containing the value if it exists and is not expired, or None otherwise
192    async fn get(&self, key: &str) -> RiResult<Option<String>> {
193        match self.store.get(key) {
194            Some(entry) => {
195                let value = entry.clone();
196                if value.is_expired() {
197                    drop(entry);
198                    self.store.remove(key);
199                    self.stats.increment_misses();
200                    Ok(None)
201                } else {
202                    self.stats.increment_hits();
203                    Ok(Some(value.value))
204                }
205            }
206            None => {
207                self.stats.increment_misses();
208                Ok(None)
209            }
210        }
211    }
212    
213    /// Sets a value in the cache with the given key.
214    ///
215    /// # Parameters
216    ///
217    /// - `key`: The key to set
218    /// - `value`: The cached value to store
219    ///
220    /// # Returns
221    ///
222    /// A `RiResult<()>` indicating success or failure
223    async fn set(&self, key: &str, value: &str, ttl_seconds: Option<u64>) -> crate::core::RiResult<()> {
224        let cached_value = RiCachedValue::new(value.to_string(), ttl_seconds);
225        self.store.insert(key.to_string(), cached_value);
226        Ok(())
227    }
228    
229    /// Deletes a value from the cache by key.
230    ///
231    /// # Parameters
232    ///
233    /// - `key`: The key to delete
234    ///
235    /// # Returns
236    ///
237    /// A `RiResult<bool>` indicating whether the key was found and deleted
238    async fn delete(&self, key: &str) -> crate::core::RiResult<bool> {
239        Ok(self.store.remove(key).is_some())
240    }
241    
242    /// Checks if a key exists in the cache and is not expired.
243    ///
244    /// If the key exists but the value is expired, it is removed from the cache and false is returned.
245    ///
246    /// # Parameters
247    ///
248    /// - `key`: The key to check
249    ///
250    /// # Returns
251    ///
252    /// `true` if the key exists and is not expired, `false` otherwise
253    async fn exists(&self, key: &str) -> bool {
254        if let Some(entry) = self.store.get(key) {
255            if entry.is_expired() {
256                drop(entry);
257                self.store.remove(key);
258                false
259            } else {
260                true
261            }
262        } else {
263            false
264        }
265    }
266    
267    /// Clears all entries from the cache.
268    ///
269    /// # Returns
270    ///
271    /// A `RiResult<()>` indicating success or failure
272    async fn clear(&self) -> crate::core::RiResult<()> {
273        self.store.clear();
274        Ok(())
275    }
276    
277    /// Gets cache statistics.
278    ///
279    /// # Returns
280    ///
281    /// A `RiCacheStats` struct containing cache statistics
282    async fn stats(&self) -> RiCacheStats {
283        let mut stats = self.stats.to_cache_stats();
284        stats.entries = self.store.len();
285        stats
286    }
287    
288    /// Cleans up all expired entries from the cache.
289    ///
290    /// # Returns
291    ///
292    /// A `RiResult<usize>` containing the number of expired entries cleaned up
293    async fn cleanup_expired(&self) -> crate::core::RiResult<usize> {
294        let mut cleaned = 0;
295        let keys: Vec<String> = self.store.iter().map(|entry| entry.key().clone()).collect();
296        
297        for key in keys {
298            if let Some(entry) = self.store.get(&key) {
299                if entry.is_expired() {
300                    drop(entry);
301                    self.store.remove(&key);
302                    cleaned += 1;
303                }
304            }
305        }
306        
307        Ok(cleaned)
308    }
309
310    /// Gets all keys from the cache.
311    ///
312    /// # Returns
313    ///
314    /// A `RiResult<Vec<String>>` containing all cache keys
315    async fn keys(&self) -> crate::core::RiResult<Vec<String>> {
316        let keys: Vec<String> = self.store.iter().map(|entry| entry.key().clone()).collect();
317        Ok(keys)
318    }
319}