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}