dmsc/cache/
mod.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC 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 DMSC, 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//! - **DMSCCacheModule**: Main cache module implementing both sync and async service module traits
27//! - **DMSCCacheManager**: Central cache management component
28//! - **DMSCCache**: Unified cache interface implemented by all backends
29//! - **DMSCCacheConfig**: Configuration for cache behavior
30//! - **Backend Implementations**:
31//!   - **DMSCMemoryCache**: In-memory cache implementation (internal)
32//!   - **DMSCRedisCache**: Redis-based distributed cache (internal)
33//!   - **DMSCHybridCache**: 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 dmsc::prelude::*;
50//! 
51//! async fn example() -> DMSCResult<()> {
52//!     // Create cache configuration
53//!     let cache_config = DMSCCacheConfig {
54//!         enabled: true,
55//!         default_ttl_secs: 3600,
56//!         max_memory_mb: 512,
57//!         cleanup_interval_secs: 300,
58//!         backend_type: DMSCCacheBackendType::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 = DMSCCacheModule::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::{DMSCCacheConfig, DMSCCacheBackendType, DMSCCachePolicy};
89pub use manager::{DMSCCacheManager, DMSCCacheEvent};
90pub use core::{DMSCCachedValue, DMSCCacheStats, DMSCCache, DMSCCacheEvent as CoreCacheEvent};
91// Re-export backend implementations
92pub use backends::DMSCMemoryCache;
93#[cfg(feature = "redis")]
94pub use backends::{DMSCRedisCache, DMSCHybridCache};
95
96use crate::core::{DMSCResult, DMSCServiceContext};
97use std::sync::Arc;
98use tokio::sync::RwLock;
99
100#[cfg(feature = "pyo3")]
101use pyo3::pymethods;
102
103/// Main cache module for DMSC.
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 DMSC application lifecycle.
108#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
109pub struct DMSCCacheModule {
110    /// Cache configuration
111    config: DMSCCacheConfig,
112    /// Cache manager wrapped in an async RwLock for thread-safe access
113    manager: std::sync::Arc<tokio::sync::RwLock<DMSCCacheManager>>,
114}
115
116impl DMSCCacheModule {
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 `DMSCCacheModule` instance
129    pub async fn new(config: DMSCCacheConfig) -> Self {
130        #[cfg(feature = "redis")]
131        let backend: std::sync::Arc<dyn crate::cache::DMSCCache> = match config.backend_type {
132            crate::cache::config::DMSCCacheBackendType::Memory => {
133                std::sync::Arc::new(DMSCMemoryCache::new())
134            }
135            crate::cache::config::DMSCCacheBackendType::Redis => {
136                match DMSCRedisCache::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(DMSCMemoryCache::new()) as Arc<dyn crate::cache::DMSCCache>
141                    }
142                }
143            }
144            crate::cache::config::DMSCCacheBackendType::Hybrid => {
145                match DMSCHybridCache::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(DMSCMemoryCache::new()) as Arc<dyn crate::cache::DMSCCache>
150                    }
151                }
152            }
153        };
154
155        #[cfg(not(feature = "redis"))]
156        let backend: std::sync::Arc<dyn crate::cache::DMSCCache> = match config.backend_type {
157            crate::cache::config::DMSCCacheBackendType::Memory => {
158                std::sync::Arc::new(DMSCMemoryCache::new())
159            }
160            crate::cache::config::DMSCCacheBackendType::Redis | crate::cache::config::DMSCCacheBackendType::Hybrid => {
161                std::sync::Arc::new(DMSCMemoryCache::new())
162            }
163        };
164
165        let manager = DMSCCacheManager::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 `DMSCCacheModule` instance
185    pub fn with_config(config: DMSCCacheConfig) -> Self {
186        #[cfg(feature = "redis")]
187        let backend: std::sync::Arc<dyn crate::cache::DMSCCache> = match config.backend_type {
188            crate::cache::config::DMSCCacheBackendType::Memory => {
189                std::sync::Arc::new(DMSCMemoryCache::new())
190            }
191            crate::cache::config::DMSCCacheBackendType::Redis => {
192                match tokio::runtime::Handle::try_current() {
193                    Ok(handle) => {
194                        match handle.block_on(DMSCRedisCache::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(DMSCMemoryCache::new()) as Arc<dyn crate::cache::DMSCCache>
199                            }
200                        }
201                    }
202                    Err(_) => {
203                        log::warn!("No Tokio runtime available for Redis cache creation. Falling back to memory backend");
204                        Arc::new(DMSCMemoryCache::new()) as Arc<dyn crate::cache::DMSCCache>
205                    }
206                }
207            }
208            crate::cache::config::DMSCCacheBackendType::Hybrid => {
209                match tokio::runtime::Handle::try_current() {
210                    Ok(handle) => {
211                        match handle.block_on(DMSCHybridCache::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(DMSCMemoryCache::new()) as Arc<dyn crate::cache::DMSCCache>
216                            }
217                        }
218                    }
219                    Err(_) => {
220                        log::warn!("No Tokio runtime available for hybrid cache creation. Falling back to memory backend");
221                        Arc::new(DMSCMemoryCache::new()) as Arc<dyn crate::cache::DMSCCache>
222                    }
223                }
224            }
225        };
226
227        #[cfg(not(feature = "redis"))]
228        let backend: std::sync::Arc<dyn crate::cache::DMSCCache> = match config.backend_type {
229            crate::cache::config::DMSCCacheBackendType::Memory => {
230                std::sync::Arc::new(DMSCMemoryCache::new())
231            }
232            crate::cache::config::DMSCCacheBackendType::Redis | crate::cache::config::DMSCCacheBackendType::Hybrid => {
233                log::warn!("Redis feature not enabled. Falling back to memory backend");
234                std::sync::Arc::new(DMSCMemoryCache::new())
235            }
236        };
237
238        let manager = DMSCCacheManager::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<DMSCCacheManager>> providing thread-safe access to the cache manager
254    pub fn cache_manager(&self) -> Arc<RwLock<DMSCCacheManager>> {
255        self.manager.clone()
256    }
257}
258
259#[cfg(feature = "pyo3")]
260#[pymethods]
261impl DMSCCacheModule {
262    #[new]
263    fn py_new(config: DMSCCacheConfig) -> Result<Self, pyo3::PyErr> {
264        let rt = match tokio::runtime::Runtime::new() {
265            Ok(r) => r,
266            Err(e) => {
267                return Err(pyo3::PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
268                    format!("Failed to create runtime: {}", e),
269                ));
270            }
271        };
272        rt.block_on(async {
273            Ok(Self::new(config).await)
274        })
275    }
276    
277    /// Get cache manager status for Python (Python wrapper)
278    fn get_cache_manager_status(&self) -> String {
279        format!("Cache manager initialized with backend: {:?}", self.config.backend_type)
280    }
281}
282
283#[async_trait::async_trait]
284impl crate::core::DMSCModule for DMSCCacheModule {
285    /// Returns the name of the cache module.
286    /// 
287    /// # Returns
288    /// 
289    /// The module name as a string
290    fn name(&self) -> &str {
291        "DMSC.Cache"
292    }
293    
294    /// Indicates whether the cache module is critical.
295    /// 
296    /// The cache module is non-critical, meaning that if it fails to initialize or operate,
297    /// it should not break the entire application. This allows the core functionality to continue
298    /// even if caching features are unavailable.
299    /// 
300    /// # Returns
301    /// 
302    /// `false` since cache is non-critical
303    fn is_critical(&self) -> bool {
304        false // Cache failures should not break the application
305    }
306    
307    /// Initializes the cache module asynchronously.
308    /// 
309    /// This method performs the following steps:
310    /// 1. Loads configuration from the service context
311    /// 2. Updates the module configuration if provided
312    /// 3. Initializes the appropriate cache backend based on configuration
313    /// 4. Creates and sets the cache manager with the initialized backend
314    /// 
315    /// # Parameters
316    /// 
317    /// - `ctx`: The service context containing configuration and other services
318    /// 
319    /// # Returns
320    /// 
321    /// A `DMSCResult<()>` indicating success or failure
322    async fn init(&mut self, ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
323        log::info!("Initializing DMSC Cache Module");
324        
325        // Load configuration
326        let binding = ctx.config();
327        let cfg = binding.config();
328        
329        // Update configuration if provided
330        if let Some(cache_config) = cfg.get("cache") {
331            self.config = serde_yaml::from_str(cache_config)
332                .unwrap_or_else(|_| DMSCCacheConfig::default());
333        } else {
334            self.config = DMSCCacheConfig::default();
335        }
336        
337        // Initialize the cache manager based on configuration
338        match self.config.backend_type {
339            crate::cache::config::DMSCCacheBackendType::Memory => {
340                let backend = Arc::new(DMSCMemoryCache::new());
341                let manager = DMSCCacheManager::new(backend);
342                *self.manager.write().await = manager;
343            }
344            #[cfg(feature = "redis")]
345            crate::cache::config::DMSCCacheBackendType::Redis => {
346                let backend = Arc::new(DMSCRedisCache::new(&self.config.redis_url).await?);
347                let manager = DMSCCacheManager::new(backend);
348                *self.manager.write().await = manager;
349            }
350            #[cfg(feature = "redis")]
351            crate::cache::config::DMSCCacheBackendType::Hybrid => {
352                let backend = Arc::new(DMSCHybridCache::new(&self.config.redis_url).await?);
353                let manager = DMSCCacheManager::new(backend);
354                *self.manager.write().await = manager;
355            }
356            #[cfg(not(feature = "redis"))]
357            crate::cache::config::DMSCCacheBackendType::Redis | crate::cache::config::DMSCCacheBackendType::Hybrid => {
358                // Fallback to memory cache if Redis is not enabled
359                let backend = Arc::new(DMSCMemoryCache::new());
360                let manager = DMSCCacheManager::new(backend);
361                *self.manager.write().await = manager;
362            }
363        }
364        
365                // Log successful initialization
366        if let Ok(fs) = crate::fs::DMSCFileSystem::new_auto_root() {
367            let logger = crate::log::DMSCLogger::new(&crate::log::DMSCLogConfig::default(), fs);
368            let _ = logger.info("cache", "DMSC Cache Module initialized successfully");
369        }
370        Ok(())
371    }
372    
373    /// Performs asynchronous cleanup after the application has shut down.
374    /// 
375    /// This method performs the following steps:
376    /// 1. Prints cache statistics
377    /// 2. Cleans up expired cache entries
378    /// 3. Prints cleanup results
379    /// 
380    /// # Parameters
381    /// 
382    /// - `_ctx`: The service context (not used in this implementation)
383    /// 
384    /// # Returns
385    /// 
386    /// A `DMSCResult<()>` indicating success or failure
387    async fn after_shutdown(&mut self, _ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
388        log::info!("Cleaning up DMSC Cache Module");
389        
390        let manager = self.manager.read().await;
391        let stats = manager.stats().await;
392        log::info!("Cache stats: {stats:?}");
393        
394        // Cleanup expired entries
395        let cleaned = manager.cleanup_expired().await?;
396        log::info!("Cleaned up {cleaned} expired cache entries");
397        log::info!("DMSC Cache Module cleanup completed");
398        Ok(())
399    }
400}
401
402impl crate::core::ServiceModule for DMSCCacheModule {
403    fn name(&self) -> &str {
404        "DMSC.Cache"
405    }
406
407    fn is_critical(&self) -> bool {
408        false
409    }
410
411    fn priority(&self) -> i32 {
412        10
413    }
414
415    fn dependencies(&self) -> Vec<&str> {
416        vec![]
417    }
418
419    fn init(&mut self, _ctx: &mut crate::core::DMSCServiceContext) -> crate::core::DMSCResult<()> {
420        Ok(())
421    }
422
423    fn start(&mut self, _ctx: &mut crate::core::DMSCServiceContext) -> crate::core::DMSCResult<()> {
424        Ok(())
425    }
426
427    fn shutdown(&mut self, _ctx: &mut crate::core::DMSCServiceContext) -> crate::core::DMSCResult<()> {
428        Ok(())
429    }
430}