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}