Skip to main content

ri/cache/
manager.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
20use serde::{Deserialize, Serialize};
21use std::sync::Arc;
22use std::collections::HashMap as FxHashMap;
23use tokio::sync::{RwLock, broadcast};
24use crate::cache::core::{RiCache, RiCacheStats};
25
26#[cfg(feature = "pyo3")]
27use pyo3::prelude::*;
28
29
30/// # Ri Cache Manager
31/// 
32/// This file implements a cache manager that coordinates different cache backends with 
33/// consistency support across multiple instances. It provides a unified interface for cache 
34/// operations while ensuring cache consistency through event-driven architecture.
35/// 
36/// ## Design Principles
37/// 1. **Consistency First**: Ensures cache consistency across multiple instances using broadcast events
38/// 2. **Unified Interface**: Provides a consistent API regardless of the underlying cache backend
39/// 3. **Event-Driven Architecture**: Uses broadcast channels for efficient cache invalidation
40/// 4. **Thread Safety**: Implements thread-safe operations using Arc and RwLock
41/// 5. **Flexibility**: Supports any backend implementing the RiCache trait
42/// 6. **Scalability**: Designed to handle high-throughput cache operations
43/// 
44/// ## Usage Examples
45/// ```rust
46/// // Create a cache manager with a Redis backend
47/// let redis_backend = Arc::new(RiRedisBackend::new(config).await?);
48/// let mut cache_manager = RiCacheManager::new(redis_backend);
49/// 
50/// // Start the consistency listener
51/// let listener_handle = cache_manager.start_consistency_listener().await;
52/// 
53/// // Set a value in cache
54/// cache_manager.set("user:123", &User { id: 123, name: "John" }, Some(3600)).await?;
55/// 
56/// // Get a value from cache
57/// let user: Option<User> = cache_manager.get("user:123").await?;
58/// 
59/// // Delete a value and invalidate across all instances
60/// cache_manager.delete("user:123").await?;
61/// 
62/// // Clear cache and broadcast to all instances
63/// cache_manager.clear().await?;
64/// ```
65/// Cache event type for maintaining cache consistency across instances
66/// 
67/// This enum defines the events that are broadcasted to ensure all cache instances
68/// remain consistent. Each event triggers a corresponding action on all cache instances.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
71pub enum RiCacheEvent {
72    /// Invalidate a specific cache key
73    /// 
74    /// **Parameters:**
75    /// - `key`: The cache key to invalidate
76    Invalidate { key: String },
77    
78    /// Invalidate all cache keys matching a pattern
79    /// 
80    /// **Parameters:**
81    /// - `pattern`: The pattern to match cache keys (supports wildcards depending on backend)
82    InvalidatePattern { pattern: String },
83    
84    /// Clear all cache entries
85    Clear(),
86}
87
88/// Cache manager that coordinates different cache backends with consistency support
89/// 
90/// This struct provides a unified interface for cache operations while ensuring cache
91/// consistency across multiple instances through event-driven architecture. It wraps
92/// any backend implementing the RiCache trait and adds consistency guarantees.
93#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
94pub struct RiCacheManager {
95    /// The underlying cache backend implementation
96    backend: Arc<dyn RiCache + Send + Sync>,
97    
98    /// Broadcast sender for cache consistency events
99    event_sender: broadcast::Sender<RiCacheEvent>,
100    
101    /// Broadcast receiver for cache consistency events (used internally)
102    event_receiver: Option<broadcast::Receiver<RiCacheEvent>>,
103    
104    /// Map of subscribers to cache events (for internal use)
105    _subscribers: Arc<RwLock<FxHashMap<String, broadcast::Receiver<RiCacheEvent>>>>,
106}
107
108impl RiCacheManager {
109    /// Create a new cache manager with the specified backend
110    /// 
111    /// **Parameters:**
112    /// - `backend`: The underlying cache backend implementation
113    /// 
114    /// **Returns:**
115    /// - A new instance of `RiCacheManager`
116    pub fn new(backend: Arc<dyn RiCache + Send + Sync>) -> Self {
117        let (sender, receiver) = broadcast::channel(100);
118        
119        Self {
120            backend,
121            event_sender: sender,
122            event_receiver: Some(receiver),
123            _subscribers: Arc::new(RwLock::new(FxHashMap::default())),
124        }
125    }
126    
127    /// Start the cache consistency event listener
128    /// 
129    /// This method starts a background task that listens for cache consistency events
130    /// and applies them to the underlying cache backend. This ensures that all cache
131    /// instances remain consistent across the system.
132    /// 
133    /// **Returns:**
134    /// - A `JoinHandle` for the background task
135    pub async fn start_consistency_listener(&mut self) -> tokio::task::JoinHandle<()> {
136        let backend = self.backend.clone();
137        let mut receiver = match self.event_receiver.take() {
138            Some(r) => r,
139            None => {
140                log::error!("[Ri.Cache] Event receiver already started or not initialized");
141                return tokio::spawn(async {});
142            }
143        };
144        
145        log::info!("[Ri.Cache] Starting cache consistency event listener");
146        
147        tokio::spawn(async move {
148            let mut event_count = 0;
149            while let Ok(event) = receiver.recv().await {
150                event_count += 1;
151                
152                match event {
153                    RiCacheEvent::Invalidate { key } => {
154                        log::info!("[Ri.Cache] Processing invalidate event for key: {key}");
155                        if let Err(e) = backend.delete(&key).await {
156                            log::error!("[Ri.Cache] Failed to invalidate cache key {key}: {e}");
157                        } else {
158                            log::info!("[Ri.Cache] Successfully invalidated cache key: {key}");
159                        }
160                    },
161                    RiCacheEvent::InvalidatePattern { pattern } => {
162                        log::info!("[Ri.Cache] Processing invalidate pattern event: {pattern}");
163                        match backend.delete_by_pattern(&pattern).await {
164                            Ok(count) => {
165                                log::info!("[Ri.Cache] Successfully invalidated {} cache keys matching pattern: {pattern}", count);
166                            }
167                            Err(e) => {
168                                log::error!("[Ri.Cache] Failed to invalidate cache pattern {pattern}: {e}");
169                            }
170                        }
171                    },
172                    RiCacheEvent::Clear() => {
173                        log::info!("[Ri.Cache] Processing clear cache event");
174                        if let Err(e) = backend.clear().await {
175                            log::error!("[Ri.Cache] Failed to clear cache: {e}");
176                        } else {
177                            log::info!("[Ri.Cache] Successfully cleared cache");
178                        }
179                    },
180                }
181                
182                // Log event processing statistics periodically
183                if event_count % 100 == 0 {
184                    log::info!("[Ri.Cache] Processed {event_count} cache consistency events");
185                }
186            }
187            
188            log::info!("[Ri.Cache] Cache consistency event listener stopped after processing {event_count} events");
189        })
190    }
191    
192    /// Subscribe to cache consistency events
193    /// 
194    /// This method allows external components to subscribe to cache consistency events,
195    /// enabling them to react to cache changes in real-time.
196    /// 
197    /// **Returns:**
198    /// - A broadcast receiver for cache events
199    pub fn subscribe(&self) -> broadcast::Receiver<RiCacheEvent> {
200        self.event_sender.subscribe()
201    }
202    
203    /// Publish a cache consistency event
204    /// 
205    /// This method publishes a cache event to all subscribers, ensuring cache consistency
206    /// across all instances.
207    /// 
208    /// **Parameters:**
209    /// - `event`: The cache event to publish
210    pub fn publish_event(&self, event: RiCacheEvent) {
211        let event_type = match &event {
212            RiCacheEvent::Invalidate { key } => format!("Invalidate(key: {key})"),
213            RiCacheEvent::InvalidatePattern { pattern } => format!("InvalidatePattern(pattern: {pattern})"),
214            RiCacheEvent::Clear() => "Clear".to_string(),
215        };
216        
217        log::info!("[Ri.Cache] Publishing cache event: {event_type}");
218        let _ = self.event_sender.send(event);
219    }
220    
221    /// Get a value from cache
222    /// 
223    /// This method retrieves a value from the cache using the specified key. If the key
224    /// exists and the value is valid, it is deserialized and returned. Otherwise, `None`
225    /// is returned.
226    /// 
227    /// **Parameters:**
228    /// - `key`: The cache key to retrieve
229    /// 
230    /// **Returns:**
231    /// - `Ok(Some(T))` if the key exists and the value is valid
232    /// - `Ok(None)` if the key does not exist
233    /// - `Err(RiError)` if an error occurs during retrieval or deserialization
234    pub async fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> crate::core::RiResult<Option<T>> {
235        log::debug!("[Ri.Cache] Getting cache key: {key}");
236        
237        match self.backend.get(key).await? {
238            Some(cached_value) => {
239                log::debug!("[Ri.Cache] Cache hit for key: {key}");
240                let value = serde_json::from_str(&cached_value)
241                    .map_err(|e| crate::core::RiError::Other(format!("Deserialization error: {e}")))?;
242                Ok(Some(value))
243            }
244            None => {
245                log::debug!("[Ri.Cache] Cache miss for key: {key}");
246                Ok(None)
247            }
248        }
249    }
250    
251    /// Set a value in cache with optional TTL
252    /// 
253    /// This method stores a value in the cache with the specified key and optional TTL.
254    /// It also publishes an invalidate event to ensure cache consistency across all instances.
255    /// 
256    /// **Parameters:**
257    /// - `key`: The cache key to set
258    /// - `value`: The value to store (must implement Serialize)
259    /// - `ttl_seconds`: Optional time-to-live in seconds
260    /// 
261    /// **Returns:**
262    /// - `Ok(())` if the value was successfully stored
263    /// - `Err(RiError)` if an error occurs during serialization or storage
264    pub async fn set<T: serde::Serialize>(&self, key: &str, value: &T, ttl_seconds: Option<u64>) -> crate::core::RiResult<()> {
265        log::debug!("[Ri.Cache] Setting cache key: {key} with TTL: {ttl_seconds:?}");
266        
267        let serialized = serde_json::to_string(value)
268            .map_err(|e| crate::core::RiError::Other(format!("Serialization error: {e}")))?;
269        
270        let result = self.backend.set(key, &serialized, ttl_seconds).await;
271        
272        match &result {
273            Ok(_) => log::debug!("[Ri.Cache] Successfully set cache key: {key}"),
274            Err(e) => log::error!("[Ri.Cache] Failed to set cache key {key}: {e}"),
275        }
276        
277        // Note: Do NOT publish invalidate event after set
278        // The set operation should update the cache, not invalidate it
279        // Invalidate events should only be published for delete operations
280        
281        result
282    }
283    
284    /// Delete a value from cache
285    /// 
286    /// This method deletes a value from the cache using the specified key. It also
287    /// publishes an invalidate event to ensure cache consistency across all instances.
288    /// 
289    /// **Parameters:**
290    /// - `key`: The cache key to delete
291    /// 
292    /// **Returns:**
293    /// - `Ok(true)` if the key was found and deleted
294    /// - `Ok(false)` if the key didn't exist
295    /// - `Err(RiError)` if an error occurs during deletion
296    pub async fn delete(&self, key: &str) -> crate::core::RiResult<bool> {
297        log::debug!("[Ri.Cache] Deleting cache key: {key}");
298        
299        let result = self.backend.delete(key).await;
300        
301        match &result {
302            Ok(true) => log::debug!("[Ri.Cache] Successfully deleted cache key: {key}"),
303            Ok(false) => log::debug!("[Ri.Cache] Cache key not found for deletion: {key}"),
304            Err(e) => log::error!("[Ri.Cache] Failed to delete cache key {key}: {e}"),
305        }
306        
307        // Publish invalidate event to ensure consistency across instances
308        self.publish_event(RiCacheEvent::Invalidate { key: key.to_string() });
309        
310        result
311    }
312    
313    /// Check if a key exists in cache
314    /// 
315    /// This method checks if the specified key exists in the cache.
316    /// 
317    /// **Parameters:**
318    /// - `key`: The cache key to check
319    /// 
320    /// **Returns:**
321    /// - `true` if the key exists, `false` otherwise
322    pub async fn exists(&self, key: &str) -> bool {
323        self.backend.exists(key).await
324    }
325    
326    /// Clear all cache entries
327    /// 
328    /// This method clears all entries from the cache. It also publishes a clear event
329    /// to ensure cache consistency across all instances.
330    /// 
331    /// **Returns:**
332    /// - `Ok(())` if the cache was successfully cleared
333    /// - `Err(RiError)` if an error occurs during clearing
334    pub async fn clear(&self) -> crate::core::RiResult<()> {
335        log::info!("[Ri.Cache] Clearing all cache entries");
336        
337        let result = self.backend.clear().await;
338        
339        match &result {
340            Ok(_) => log::info!("[Ri.Cache] Successfully cleared all cache entries"),
341            Err(e) => log::error!("[Ri.Cache] Failed to clear cache: {e}"),
342        }
343        
344        // Publish clear event to ensure consistency across instances
345        self.publish_event(RiCacheEvent::Clear());
346        
347        result
348    }
349    
350    /// Invalidate cache entries matching a pattern
351    /// 
352    /// This method invalidates all cache entries matching the specified pattern. It
353    /// publishes an invalidate pattern event to ensure cache consistency across all instances.
354    /// 
355    /// **Parameters:**
356    /// - `pattern`: The pattern to match cache keys (supports wildcards depending on backend)
357    /// 
358    /// **Returns:**
359    /// - `Ok(())` if the invalidation event was successfully published
360    pub async fn invalidate_pattern(&self, pattern: &str) -> crate::core::RiResult<()> {
361        // Publish invalidate pattern event to ensure consistency across instances
362        self.publish_event(RiCacheEvent::InvalidatePattern { pattern: pattern.to_string() });
363        
364        Ok(())
365    }
366    
367    /// Get cache statistics
368    /// 
369    /// This method retrieves statistics about the cache, including hit rate, miss rate,
370    /// and the number of entries.
371    /// 
372    /// **Returns:**
373    /// - A `RiCacheStats` struct containing the cache statistics
374    pub async fn stats(&self) -> RiCacheStats {
375        let stats = self.backend.stats().await;
376        
377        // Log cache statistics for monitoring
378        log::info!("[Ri.Cache] Cache Statistics: hits={}, misses={}, entries={}, hit_rate={:.2}%", 
379                 stats.hits, stats.misses, stats.entries, stats.avg_hit_rate * 100.0);
380        
381        // Monitor cache performance
382        if stats.hits + stats.misses > 0 {
383            let current_hit_rate = stats.hits as f64 / (stats.hits + stats.misses) as f64;
384            if current_hit_rate < 0.5 && stats.hits + stats.misses > 100 {
385                log::warn!("[Ri.Cache] Warning: Low cache hit rate ({:.2}%) with {} total operations", 
386                         current_hit_rate * 100.0, stats.hits + stats.misses);
387            }
388        }
389        
390        stats
391    }
392    
393    /// Cleanup expired cache entries
394    /// 
395    /// This method removes all expired entries from the cache.
396    /// 
397    /// **Returns:**
398    /// - `Ok(usize)` with the number of expired entries cleaned up
399    /// - `Err(RiError)` if an error occurs during cleanup
400    pub async fn cleanup_expired(&self) -> crate::core::RiResult<usize> {
401        let cleaned = self.backend.cleanup_expired().await?;
402        
403        // Log cleanup results for monitoring
404        if cleaned > 0 {
405            log::info!("[Ri.Cache] Cleanup completed: {cleaned} expired entries removed");
406        }
407        
408        Ok(cleaned)
409    }
410    
411    /// Get a value from cache or set it if it doesn't exist
412    /// 
413    /// This method retrieves a value from the cache using the specified key. If the key
414    /// exists and the value is valid, it is returned. Otherwise, the provided factory
415    /// function is called to generate the value, which is then stored in the cache and
416    /// returned.
417    /// 
418    /// **Parameters:**
419    /// - `key`: The cache key to retrieve or set
420    /// - `ttl_seconds`: Optional time-to-live in seconds for the new value
421    /// - `factory`: A function that generates the value if it doesn't exist in cache
422    /// 
423    /// **Returns:**
424    /// - `Ok(T)` with the retrieved or generated value
425    /// - `Err(RiError)` if an error occurs during retrieval, generation, or storage
426    pub async fn get_or_set<T, F>(&self, key: &str, ttl_seconds: Option<u64>, factory: F) -> crate::core::RiResult<T>
427    where
428        T: serde::Serialize + serde::de::DeserializeOwned + Clone,
429        F: FnOnce() -> crate::core::RiResult<T>,
430    {
431        log::debug!("[Ri.Cache] get_or_set operation for key: {key} with TTL: {ttl_seconds:?}");
432        
433        // Try to get from cache first
434        if let Some(value) = self.get::<T>(key).await? {
435            log::debug!("[Ri.Cache] get_or_set cache hit for key: {key}");
436            return Ok(value);
437        }
438        
439        log::debug!("[Ri.Cache] get_or_set cache miss for key: {key}, generating value");
440        
441        // If not found, generate the value
442        let value = factory()?;
443        
444        // Store in cache
445        self.set(key, &value, ttl_seconds).await?;
446        
447        log::debug!("[Ri.Cache] get_or_set successfully generated and cached value for key: {key}");
448        Ok(value)
449    }
450}
451
452#[cfg(feature = "pyo3")]
453#[pymethods]
454impl RiCacheManager {
455    #[new]
456    fn py_new() -> Self {
457        use crate::cache::backends::RiMemoryCache;
458        let backend = std::sync::Arc::new(RiMemoryCache::new());
459        Self::new(backend)
460    }
461    
462    #[pyo3(name = "get")]
463    fn get_impl(&self, key: String) -> pyo3::PyResult<Option<String>> {
464        let rt = tokio::runtime::Runtime::new().map_err(|e| {
465            pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
466        })?;
467        
468        rt.block_on(async {
469            self.get::<String>(&key).await.map_err(|e| {
470                pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
471            })
472        })
473    }
474    
475    #[pyo3(name = "set")]
476    fn set_impl(&self, key: String, value: String, ttl_seconds: Option<u64>) -> pyo3::PyResult<()> {
477        let rt = tokio::runtime::Handle::current();
478        
479        rt.block_on(async {
480            self.set(&key, &value, ttl_seconds).await.map_err(|e| {
481                pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
482            })
483        })
484    }
485    
486    #[pyo3(name = "delete")]
487    fn delete_impl(&self, key: String) -> pyo3::PyResult<bool> {
488        let rt = tokio::runtime::Runtime::new().map_err(|e| {
489            pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
490        })?;
491        
492        rt.block_on(async {
493            self.delete(&key).await.map_err(|e| {
494                pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
495            })
496        })
497    }
498    
499    #[pyo3(name = "exists")]
500    fn exists_impl(&self, key: String) -> pyo3::PyResult<bool> {
501        let rt = tokio::runtime::Runtime::new().map_err(|e| {
502            pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
503        })?;
504        
505        Ok(rt.block_on(async {
506            self.exists(&key).await
507        }))
508    }
509    
510    #[pyo3(name = "clear")]
511    fn clear_impl(&self) -> pyo3::PyResult<()> {
512        let rt = tokio::runtime::Runtime::new().map_err(|e| {
513            pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
514        })?;
515        
516        rt.block_on(async {
517            self.clear().await.map_err(|e| {
518                pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
519            })
520        })
521    }
522    
523    #[pyo3(name = "stats")]
524    fn stats_impl(&self) -> pyo3::PyResult<RiCacheStats> {
525        let rt = tokio::runtime::Runtime::new().map_err(|e| {
526            pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
527        })?;
528        
529        Ok(rt.block_on(async {
530            self.stats().await
531        }))
532    }
533    
534    #[pyo3(name = "cleanup_expired")]
535    fn cleanup_expired_impl(&self) -> pyo3::PyResult<usize> {
536        let rt = tokio::runtime::Runtime::new().map_err(|e| {
537            pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
538        })?;
539        
540        rt.block_on(async {
541            self.cleanup_expired().await.map_err(|e| {
542                pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
543            })
544        })
545    }
546}