Skip to main content

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}