ri/cache/mod.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//! # Cache Module
19//!
20//! This module provides a comprehensive caching abstraction for Ri, offering a unified interface
21//! with support for multiple backend implementations. It enables efficient data caching with
22//! configurable policies and backend selection.
23//!
24//! ## Key Components
25//!
26//! - **RiCacheModule**: Main cache module implementing both sync and async service module traits
27//! - **RiCacheManager**: Central cache management component
28//! - **RiCache**: Unified cache interface implemented by all backends
29//! - **RiCacheConfig**: Configuration for cache behavior
30//! - **Backend Implementations**:
31//! - **RiMemoryCache**: In-memory cache implementation (internal)
32//! - **RiRedisCache**: Redis-based distributed cache (internal)
33//! - **RiHybridCache**: Combined memory and Redis cache for optimal performance (internal)
34//!
35//! ## Design Principles
36//!
37//! 1. **Unified Interface**: Consistent API across all backend implementations
38//! 2. **Multiple Backends**: Support for different cache storage options
39//! 3. **Async Support**: Full async/await compatibility
40//! 4. **Configurable**: Highly configurable cache behavior
41//! 5. **Non-critical**: Cache failures should not break the application
42//! 6. **Stats Collection**: Built-in cache statistics for monitoring
43//! 7. **Service Module Integration**: Implements both sync and async service module traits
44//! 8. **Thread-safe**: Safe for concurrent use across multiple threads
45//!
46//! ## Usage
47//!
48//! ```rust,ignore
49//! use ri::prelude::*;
50//!
51//! async fn example() -> RiResult<()> {
52//! // Create cache configuration
53//! let cache_config = RiCacheConfig {
54//! enabled: true,
55//! default_ttl_secs: 3600,
56//! max_memory_mb: 512,
57//! cleanup_interval_secs: 300,
58//! backend_type: RiCacheBackendType::Memory,
59//! redis_url: "redis://127.0.0.1:6379".to_string(),
60//! redis_pool_size: 10,
61//! };
62//!
63//! // Create cache module
64//! let cache_module = RiCacheModule::new(cache_config);
65//!
66//! // Get cache manager
67//! let cache_manager = cache_module.cache_manager();
68//!
69//! // Use cache manager to get cache instance
70//! let cache = cache_manager.read().await.get_cache();
71//!
72//! // Set a value in cache
73//! cache.set("key", "value", Some(3600)).await?;
74//!
75//! // Get a value from cache
76//! let value = cache.get("key").await?;
77//! println!("Cached value: {:?}", value);
78//!
79//! Ok(())
80//! }
81//! ```
82
83mod core;
84mod manager;
85mod backends;
86mod config;
87
88pub use config::{RiCacheConfig, RiCacheBackendType, RiCachePolicy};
89pub use manager::{RiCacheManager, RiCacheEvent};
90pub use core::{RiCachedValue, RiCacheStats, RiCache, RiCacheEvent as CoreCacheEvent};
91// Re-export backend implementations
92pub use backends::RiMemoryCache;
93#[cfg(feature = "redis")]
94pub use backends::{RiRedisCache, RiHybridCache};
95
96use crate::core::{RiResult, RiServiceContext};
97use std::sync::Arc;
98use tokio::sync::RwLock;
99
100#[cfg(feature = "pyo3")]
101use pyo3::pymethods;
102
103/// Main cache module for Ri.
104///
105/// This module provides a unified caching abstraction with support for multiple backend implementations.
106/// It implements both the `AsyncServiceModule` and `ServiceModule` traits for seamless integration
107/// into the Ri application lifecycle.
108#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
109pub struct RiCacheModule {
110 /// Cache configuration
111 config: RiCacheConfig,
112 /// Cache manager wrapped in an async RwLock for thread-safe access
113 manager: std::sync::Arc<tokio::sync::RwLock<RiCacheManager>>,
114}
115
116impl RiCacheModule {
117 /// Creates a new cache module with the given configuration.
118 ///
119 /// This method initializes the cache manager with the appropriate backend based on the
120 /// provided configuration. The backend is created immediately, not as a placeholder.
121 ///
122 /// # Parameters
123 ///
124 /// - `config`: The cache configuration to use
125 ///
126 /// # Returns
127 ///
128 /// A new `RiCacheModule` instance
129 pub async fn new(config: RiCacheConfig) -> Self {
130 #[cfg(feature = "redis")]
131 let backend: std::sync::Arc<dyn crate::cache::RiCache> = match config.backend_type {
132 crate::cache::config::RiCacheBackendType::Memory => {
133 std::sync::Arc::new(RiMemoryCache::new())
134 }
135 crate::cache::config::RiCacheBackendType::Redis => {
136 match RiRedisCache::new(&config.redis_url).await {
137 Ok(cache) => Arc::new(cache),
138 Err(e) => {
139 log::warn!("Failed to create Redis cache ({}): {}. Falling back to memory backend", config.redis_url, e);
140 Arc::new(RiMemoryCache::new()) as Arc<dyn crate::cache::RiCache>
141 }
142 }
143 }
144 crate::cache::config::RiCacheBackendType::Hybrid => {
145 match RiHybridCache::new(&config.redis_url).await {
146 Ok(backend) => Arc::new(backend),
147 Err(e) => {
148 log::warn!("Failed to create hybrid cache backend (Redis URL: {}): {}. Falling back to memory backend", config.redis_url, e);
149 Arc::new(RiMemoryCache::new()) as Arc<dyn crate::cache::RiCache>
150 }
151 }
152 }
153 };
154
155 #[cfg(not(feature = "redis"))]
156 let backend: std::sync::Arc<dyn crate::cache::RiCache> = match config.backend_type {
157 crate::cache::config::RiCacheBackendType::Memory => {
158 std::sync::Arc::new(RiMemoryCache::new())
159 }
160 crate::cache::config::RiCacheBackendType::Redis | crate::cache::config::RiCacheBackendType::Hybrid => {
161 std::sync::Arc::new(RiMemoryCache::new())
162 }
163 };
164
165 let manager = RiCacheManager::new(backend);
166
167 Self {
168 config,
169 manager: Arc::new(RwLock::new(manager)),
170 }
171 }
172
173 /// Creates a new cache module with the given configuration (synchronous version).
174 ///
175 /// This is a synchronous wrapper around the async `new` method for use in the builder pattern.
176 /// For Redis and Hybrid backends, it will block on the current thread to create the backend.
177 ///
178 /// # Parameters
179 ///
180 /// - `config`: The cache configuration to use
181 ///
182 /// # Returns
183 ///
184 /// A new `RiCacheModule` instance
185 pub fn with_config(config: RiCacheConfig) -> Self {
186 #[cfg(feature = "redis")]
187 let backend: std::sync::Arc<dyn crate::cache::RiCache> = match config.backend_type {
188 crate::cache::config::RiCacheBackendType::Memory => {
189 std::sync::Arc::new(RiMemoryCache::new())
190 }
191 crate::cache::config::RiCacheBackendType::Redis => {
192 match tokio::runtime::Handle::try_current() {
193 Ok(handle) => {
194 match handle.block_on(RiRedisCache::new(&config.redis_url)) {
195 Ok(cache) => Arc::new(cache),
196 Err(e) => {
197 log::warn!("Failed to create Redis cache ({}): {}. Falling back to memory backend", config.redis_url, e);
198 Arc::new(RiMemoryCache::new()) as Arc<dyn crate::cache::RiCache>
199 }
200 }
201 }
202 Err(_) => {
203 log::warn!("No Tokio runtime available for Redis cache creation. Falling back to memory backend");
204 Arc::new(RiMemoryCache::new()) as Arc<dyn crate::cache::RiCache>
205 }
206 }
207 }
208 crate::cache::config::RiCacheBackendType::Hybrid => {
209 match tokio::runtime::Handle::try_current() {
210 Ok(handle) => {
211 match handle.block_on(RiHybridCache::new(&config.redis_url)) {
212 Ok(backend) => Arc::new(backend),
213 Err(e) => {
214 log::warn!("Failed to create hybrid cache backend (Redis URL: {}): {}. Falling back to memory backend", config.redis_url, e);
215 Arc::new(RiMemoryCache::new()) as Arc<dyn crate::cache::RiCache>
216 }
217 }
218 }
219 Err(_) => {
220 log::warn!("No Tokio runtime available for hybrid cache creation. Falling back to memory backend");
221 Arc::new(RiMemoryCache::new()) as Arc<dyn crate::cache::RiCache>
222 }
223 }
224 }
225 };
226
227 #[cfg(not(feature = "redis"))]
228 let backend: std::sync::Arc<dyn crate::cache::RiCache> = match config.backend_type {
229 crate::cache::config::RiCacheBackendType::Memory => {
230 std::sync::Arc::new(RiMemoryCache::new())
231 }
232 crate::cache::config::RiCacheBackendType::Redis | crate::cache::config::RiCacheBackendType::Hybrid => {
233 log::warn!("Redis feature not enabled. Falling back to memory backend");
234 std::sync::Arc::new(RiMemoryCache::new())
235 }
236 };
237
238 let manager = RiCacheManager::new(backend);
239
240 Self {
241 config,
242 manager: Arc::new(RwLock::new(manager)),
243 }
244 }
245
246 /// Returns a reference to the cache manager.
247 ///
248 /// The cache manager is wrapped in an Arc<RwLock<>> to allow thread-safe access
249 /// from multiple async tasks.
250 ///
251 /// # Returns
252 ///
253 /// An Arc<RwLock<RiCacheManager>> providing thread-safe access to the cache manager
254 pub fn cache_manager(&self) -> Arc<RwLock<RiCacheManager>> {
255 self.manager.clone()
256 }
257}
258
259#[cfg(feature = "pyo3")]
260#[pymethods]
261impl RiCacheModule {
262 #[new]
263 fn py_new(config: RiCacheConfig) -> Result<Self, pyo3::PyErr> {
264 crate::py_runtime::py_runtime().block_on(async {
265 Ok(Self::new(config).await)
266 })
267 }
268
269 /// Get cache manager status for Python (Python wrapper)
270 fn get_cache_manager_status(&self) -> String {
271 format!("Cache manager initialized with backend: {:?}", self.config.backend_type)
272 }
273}
274
275#[async_trait::async_trait]
276impl crate::core::RiModule for RiCacheModule {
277 /// Returns the name of the cache module.
278 ///
279 /// # Returns
280 ///
281 /// The module name as a string
282 fn name(&self) -> &str {
283 "Ri.Cache"
284 }
285
286 /// Indicates whether the cache module is critical.
287 ///
288 /// The cache module is non-critical, meaning that if it fails to initialize or operate,
289 /// it should not break the entire application. This allows the core functionality to continue
290 /// even if caching features are unavailable.
291 ///
292 /// # Returns
293 ///
294 /// `false` since cache is non-critical
295 fn is_critical(&self) -> bool {
296 false // Cache failures should not break the application
297 }
298
299 /// Initializes the cache module asynchronously.
300 ///
301 /// This method performs the following steps:
302 /// 1. Loads configuration from the service context
303 /// 2. Updates the module configuration if provided
304 /// 3. Initializes the appropriate cache backend based on configuration
305 /// 4. Creates and sets the cache manager with the initialized backend
306 ///
307 /// # Parameters
308 ///
309 /// - `ctx`: The service context containing configuration and other services
310 ///
311 /// # Returns
312 ///
313 /// A `RiResult<()>` indicating success or failure
314 async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
315 log::info!("Initializing Ri Cache Module");
316
317 // Load configuration
318 let binding = ctx.config();
319 let cfg = binding.config();
320
321 // Update configuration if provided
322 if let Some(cache_config) = cfg.get("cache") {
323 self.config = serde_yaml::from_str(cache_config)
324 .unwrap_or_else(|_| RiCacheConfig::default());
325 } else {
326 self.config = RiCacheConfig::default();
327 }
328
329 // Initialize the cache manager based on configuration
330 match self.config.backend_type {
331 crate::cache::config::RiCacheBackendType::Memory => {
332 let backend = Arc::new(RiMemoryCache::new());
333 let manager = RiCacheManager::new(backend);
334 *self.manager.write().await = manager;
335 }
336 #[cfg(feature = "redis")]
337 crate::cache::config::RiCacheBackendType::Redis => {
338 let backend = Arc::new(RiRedisCache::new(&self.config.redis_url).await?);
339 let manager = RiCacheManager::new(backend);
340 *self.manager.write().await = manager;
341 }
342 #[cfg(feature = "redis")]
343 crate::cache::config::RiCacheBackendType::Hybrid => {
344 let backend = Arc::new(RiHybridCache::new(&self.config.redis_url).await?);
345 let manager = RiCacheManager::new(backend);
346 *self.manager.write().await = manager;
347 }
348 #[cfg(not(feature = "redis"))]
349 crate::cache::config::RiCacheBackendType::Redis | crate::cache::config::RiCacheBackendType::Hybrid => {
350 // Fallback to memory cache if Redis is not enabled
351 let backend = Arc::new(RiMemoryCache::new());
352 let manager = RiCacheManager::new(backend);
353 *self.manager.write().await = manager;
354 }
355 }
356
357 // Log successful initialization
358 if let Ok(fs) = crate::fs::RiFileSystem::new_auto_root() {
359 let logger = crate::log::RiLogger::new(&crate::log::RiLogConfig::default(), fs);
360 let _ = logger.info("cache", "Ri Cache Module initialized successfully");
361 }
362 Ok(())
363 }
364
365 /// Performs asynchronous cleanup after the application has shut down.
366 ///
367 /// This method performs the following steps:
368 /// 1. Prints cache statistics
369 /// 2. Cleans up expired cache entries
370 /// 3. Prints cleanup results
371 ///
372 /// # Parameters
373 ///
374 /// - `_ctx`: The service context (not used in this implementation)
375 ///
376 /// # Returns
377 ///
378 /// A `RiResult<()>` indicating success or failure
379 async fn after_shutdown(&mut self, _ctx: &mut RiServiceContext) -> RiResult<()> {
380 log::info!("Cleaning up Ri Cache Module");
381
382 let manager = self.manager.read().await;
383 let stats = manager.stats().await;
384 log::info!("Cache stats: {stats:?}");
385
386 // Cleanup expired entries
387 let cleaned = manager.cleanup_expired().await?;
388 log::info!("Cleaned up {cleaned} expired cache entries");
389 log::info!("Ri Cache Module cleanup completed");
390 Ok(())
391 }
392}
393
394impl crate::core::ServiceModule for RiCacheModule {
395 fn name(&self) -> &str {
396 "Ri.Cache"
397 }
398
399 fn is_critical(&self) -> bool {
400 false
401 }
402
403 fn priority(&self) -> i32 {
404 10
405 }
406
407 fn dependencies(&self) -> Vec<&str> {
408 vec![]
409 }
410
411 fn init(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
412 Ok(())
413 }
414
415 fn start(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
416 Ok(())
417 }
418
419 fn shutdown(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
420 Ok(())
421 }
422}