1use crate::{CacheBackend, CacheError, CacheResult, CacheKey, CacheTag};
7use async_trait::async_trait;
8use futures::future::join_all;
9use regex::Regex;
10use wildmatch::WildMatch;
11use std::{
12 collections::{HashMap, HashSet},
13 sync::Arc,
14 time::{Duration, SystemTime, UNIX_EPOCH},
15};
16use tokio::sync::RwLock;
17use tracing;
18
19#[derive(Debug, Clone)]
21pub enum PatternStrategy {
22 Wildcard(String),
24 Regex(String),
26 Prefix(String),
28 Suffix(String),
30 Contains(String),
32}
33
34impl PatternStrategy {
35 pub fn matches(&self, key: &str) -> CacheResult<bool> {
37 match self {
38 PatternStrategy::Wildcard(pattern) => {
39 let matcher = WildMatch::new(pattern);
40 Ok(matcher.matches(key))
41 }
42 PatternStrategy::Regex(pattern) => {
43 let regex = Regex::new(pattern)
44 .map_err(|e| CacheError::Configuration(format!("Invalid regex: {}", e)))?;
45 Ok(regex.is_match(key))
46 }
47 PatternStrategy::Prefix(prefix) => {
48 Ok(key.starts_with(prefix))
49 }
50 PatternStrategy::Suffix(suffix) => {
51 Ok(key.ends_with(suffix))
52 }
53 PatternStrategy::Contains(substring) => {
54 Ok(key.contains(substring))
55 }
56 }
57 }
58}
59
60
61#[derive(Debug, Clone)]
63pub enum TimeBasedPolicy {
64 ExpiresAt(SystemTime),
66 Ttl(Duration),
68 Scheduled {
70 interval: Duration,
72 last_run: SystemTime,
74 },
75}
76
77impl TimeBasedPolicy {
78 pub fn should_expire(&self, created_at: SystemTime) -> bool {
80 let now = SystemTime::now();
81
82 match self {
83 TimeBasedPolicy::ExpiresAt(expires_at) => now >= *expires_at,
84 TimeBasedPolicy::Ttl(ttl) => {
85 if let Ok(elapsed) = now.duration_since(created_at) {
86 elapsed >= *ttl
87 } else {
88 false
89 }
90 }
91 TimeBasedPolicy::Scheduled { interval, last_run } => {
92 if let Ok(elapsed) = now.duration_since(*last_run) {
93 elapsed >= *interval
94 } else {
95 false
96 }
97 }
98 }
99 }
100}
101
102#[derive(Debug, Clone)]
104pub struct InvalidatableEntry {
105 pub key: CacheKey,
107 pub tags: HashSet<CacheTag>,
109 pub created_at: SystemTime,
111 pub time_policies: Vec<TimeBasedPolicy>,
113 pub dependencies: HashSet<CacheKey>,
115}
116
117impl InvalidatableEntry {
118 pub fn new(key: CacheKey) -> Self {
119 Self {
120 key,
121 tags: HashSet::new(),
122 created_at: SystemTime::now(),
123 time_policies: Vec::new(),
124 dependencies: HashSet::new(),
125 }
126 }
127
128 pub fn should_expire(&self) -> bool {
130 self.time_policies.iter().any(|policy| policy.should_expire(self.created_at))
131 }
132
133 pub fn add_tag(&mut self, tag: CacheTag) {
135 self.tags.insert(tag);
136 }
137
138 pub fn add_tags(&mut self, tags: impl IntoIterator<Item = CacheTag>) {
140 self.tags.extend(tags);
141 }
142
143 pub fn add_time_policy(&mut self, policy: TimeBasedPolicy) {
145 self.time_policies.push(policy);
146 }
147
148 pub fn add_dependency(&mut self, key: CacheKey) {
150 self.dependencies.insert(key);
151 }
152}
153
154#[async_trait]
156pub trait InvalidationManager: Send + Sync {
157 async fn invalidate_pattern(&self, pattern: PatternStrategy) -> CacheResult<Vec<CacheKey>>;
159
160 async fn invalidate_tags(&self, tags: &[&str]) -> CacheResult<Vec<CacheKey>>;
162
163 async fn invalidate_expired(&self) -> CacheResult<Vec<CacheKey>>;
165
166 async fn invalidate_dependencies(&self, keys: &[&str]) -> CacheResult<Vec<CacheKey>>;
168
169 async fn get_tracked_keys(&self) -> CacheResult<Vec<CacheKey>>;
171
172 async fn register_entry(&self, entry: InvalidatableEntry) -> CacheResult<()>;
174
175 async fn unregister_entry(&self, key: &str) -> CacheResult<()>;
177}
178
179pub struct MemoryInvalidationManager<B: CacheBackend> {
181 backend: Arc<B>,
182 entries: Arc<RwLock<HashMap<CacheKey, InvalidatableEntry>>>,
183}
184
185impl<B: CacheBackend> MemoryInvalidationManager<B> {
186 pub fn new(backend: Arc<B>) -> Self {
187 Self {
188 backend,
189 entries: Arc::new(RwLock::new(HashMap::new())),
190 }
191 }
192
193 async fn find_matching_keys(&self, pattern: &PatternStrategy) -> CacheResult<Vec<CacheKey>> {
195 let entries = self.entries.read().await;
196 let mut matching_keys = Vec::new();
197
198 for key in entries.keys() {
199 if pattern.matches(key)? {
200 matching_keys.push(key.clone());
201 }
202 }
203
204 Ok(matching_keys)
205 }
206
207 async fn find_tagged_keys(&self, tags: &[&str]) -> CacheResult<Vec<CacheKey>> {
209 let entries = self.entries.read().await;
210 let mut matching_keys = Vec::new();
211 let tag_set: HashSet<&str> = tags.iter().copied().collect();
212
213 for (key, entry) in entries.iter() {
214 if entry.tags.iter().any(|tag| tag_set.contains(tag.as_str())) {
215 matching_keys.push(key.clone());
216 }
217 }
218
219 Ok(matching_keys)
220 }
221
222 async fn find_expired_keys(&self) -> CacheResult<Vec<CacheKey>> {
224 let entries = self.entries.read().await;
225 let mut expired_keys = Vec::new();
226
227 for (key, entry) in entries.iter() {
228 if entry.should_expire() {
229 expired_keys.push(key.clone());
230 }
231 }
232
233 Ok(expired_keys)
234 }
235
236 async fn find_dependent_keys(&self, dependency_keys: &[&str]) -> CacheResult<Vec<CacheKey>> {
238 let entries = self.entries.read().await;
239 let mut dependent_keys = Vec::new();
240 let dep_set: HashSet<&str> = dependency_keys.iter().copied().collect();
241
242 for (key, entry) in entries.iter() {
243 if entry.dependencies.iter().any(|dep| dep_set.contains(dep.as_str())) {
244 dependent_keys.push(key.clone());
245 }
246 }
247
248 Ok(dependent_keys)
249 }
250
251 async fn execute_invalidation(&self, keys: Vec<CacheKey>) -> CacheResult<Vec<CacheKey>> {
254 if keys.is_empty() {
255 return Ok(Vec::new());
256 }
257
258 let forget_futures = keys.iter().map(|k| self.backend.forget(k));
260 let results = join_all(forget_futures).await;
261
262 let mut invalidated_keys = Vec::new();
264 let mut failed_keys = Vec::new();
265
266 for (i, result) in results.into_iter().enumerate() {
267 match result {
268 Ok(was_removed) => {
269 if was_removed {
270 invalidated_keys.push(keys[i].clone());
271 }
272 }
273 Err(_) => {
274 failed_keys.push(keys[i].clone());
275 }
276 }
277 }
278
279 {
282 let mut entries = self.entries.write().await;
283 for key in &keys {
284 entries.remove(key);
285 }
286 }
287
288 if !failed_keys.is_empty() {
290 tracing::warn!(
291 "Failed to invalidate {} keys from backend: {:?}",
292 failed_keys.len(),
293 failed_keys
294 );
295 }
296
297 Ok(invalidated_keys)
298 }
299}
300
301#[async_trait]
302impl<B: CacheBackend> InvalidationManager for MemoryInvalidationManager<B> {
303 async fn invalidate_pattern(&self, pattern: PatternStrategy) -> CacheResult<Vec<CacheKey>> {
304 let matching_keys = self.find_matching_keys(&pattern).await?;
305 self.execute_invalidation(matching_keys).await
306 }
307
308 async fn invalidate_tags(&self, tags: &[&str]) -> CacheResult<Vec<CacheKey>> {
309 let tagged_keys = self.find_tagged_keys(tags).await?;
310 self.execute_invalidation(tagged_keys).await
311 }
312
313 async fn invalidate_expired(&self) -> CacheResult<Vec<CacheKey>> {
314 let expired_keys = self.find_expired_keys().await?;
315 self.execute_invalidation(expired_keys).await
316 }
317
318 async fn invalidate_dependencies(&self, keys: &[&str]) -> CacheResult<Vec<CacheKey>> {
319 let dependent_keys = self.find_dependent_keys(keys).await?;
320 self.execute_invalidation(dependent_keys).await
321 }
322
323 async fn get_tracked_keys(&self) -> CacheResult<Vec<CacheKey>> {
324 let entries = self.entries.read().await;
325 Ok(entries.keys().cloned().collect())
326 }
327
328 async fn register_entry(&self, entry: InvalidatableEntry) -> CacheResult<()> {
329 let mut entries = self.entries.write().await;
330 entries.insert(entry.key.clone(), entry);
331 Ok(())
332 }
333
334 async fn unregister_entry(&self, key: &str) -> CacheResult<()> {
335 let mut entries = self.entries.write().await;
336 entries.remove(key);
337 Ok(())
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use crate::backends::MemoryBackend;
345 use crate::config::CacheConfig;
346 use std::time::Duration;
347 use tokio::time::sleep;
348
349 #[tokio::test]
350 async fn test_pattern_strategy_wildcard() {
351 let pattern = PatternStrategy::Wildcard("user:*".to_string());
352 assert!(pattern.matches("user:123").unwrap());
353 assert!(pattern.matches("user:abc").unwrap());
354 assert!(!pattern.matches("post:123").unwrap());
355
356 let pattern = PatternStrategy::Wildcard("user:?".to_string());
357 assert!(pattern.matches("user:1").unwrap());
358 assert!(!pattern.matches("user:12").unwrap());
359 assert!(!pattern.matches("user:").unwrap());
360 }
361
362 #[tokio::test]
363 async fn test_pattern_strategy_regex() {
364 let pattern = PatternStrategy::Regex(r"user:\d+".to_string());
365 assert!(pattern.matches("user:123").unwrap());
366 assert!(!pattern.matches("user:abc").unwrap());
367 assert!(!pattern.matches("post:123").unwrap());
368 }
369
370 #[tokio::test]
371 async fn test_pattern_strategy_prefix_suffix_contains() {
372 let prefix = PatternStrategy::Prefix("user:".to_string());
373 assert!(prefix.matches("user:123").unwrap());
374 assert!(!prefix.matches("post:123").unwrap());
375
376 let suffix = PatternStrategy::Suffix(":active".to_string());
377 assert!(suffix.matches("user:active").unwrap());
378 assert!(!suffix.matches("user:inactive").unwrap());
379
380 let contains = PatternStrategy::Contains("temp".to_string());
381 assert!(contains.matches("temp_cache").unwrap());
382 assert!(contains.matches("cache_temp").unwrap());
383 assert!(!contains.matches("permanent").unwrap());
384 }
385
386 #[tokio::test]
387 async fn test_wildcard_match() {
388 let wildcard_all = PatternStrategy::Wildcard("*".to_string());
390 assert!(wildcard_all.matches("anything").unwrap());
391
392 let user_wildcard = PatternStrategy::Wildcard("user:*".to_string());
393 assert!(user_wildcard.matches("user:123").unwrap());
394 assert!(user_wildcard.matches("user:").unwrap());
395 assert!(!user_wildcard.matches("post:123").unwrap());
396
397 let user_question = PatternStrategy::Wildcard("user:?".to_string());
398 assert!(user_question.matches("user:1").unwrap());
399 assert!(!user_question.matches("user:12").unwrap());
400 assert!(!user_question.matches("user:").unwrap());
401
402 let temp_wildcard = PatternStrategy::Wildcard("*temp*".to_string());
403 assert!(temp_wildcard.matches("temporary").unwrap());
404 assert!(temp_wildcard.matches("temp").unwrap());
405 assert!(temp_wildcard.matches("something_temp_else").unwrap());
406 assert!(!temp_wildcard.matches("permanent").unwrap());
407 }
408
409 #[tokio::test]
410 async fn test_time_based_policy() {
411 let now = SystemTime::now();
412 let past = now - Duration::from_secs(60);
413 let future = now + Duration::from_secs(60);
414
415 let expires_at_future = TimeBasedPolicy::ExpiresAt(future);
416 assert!(!expires_at_future.should_expire(past));
417
418 let expires_at_past = TimeBasedPolicy::ExpiresAt(past);
419 assert!(expires_at_past.should_expire(past));
420
421 let ttl_expired = TimeBasedPolicy::Ttl(Duration::from_secs(30));
422 assert!(ttl_expired.should_expire(past));
423
424 let ttl_valid = TimeBasedPolicy::Ttl(Duration::from_secs(120));
425 assert!(!ttl_valid.should_expire(past));
426 }
427
428 #[tokio::test]
429 async fn test_invalidatable_entry() {
430 let mut entry = InvalidatableEntry::new("test:key".to_string());
431
432 entry.add_tag("users".to_string());
433 entry.add_tags(vec!["active".to_string(), "premium".to_string()]);
434
435 assert!(entry.tags.contains("users"));
436 assert!(entry.tags.contains("active"));
437 assert!(entry.tags.contains("premium"));
438 assert_eq!(entry.tags.len(), 3);
439
440 entry.add_time_policy(TimeBasedPolicy::Ttl(Duration::from_millis(1)));
441 sleep(Duration::from_millis(2)).await;
442 assert!(entry.should_expire());
443
444 entry.add_dependency("parent:key".to_string());
445 assert!(entry.dependencies.contains("parent:key"));
446 }
447
448 #[tokio::test]
449 async fn test_memory_invalidation_manager() {
450 let backend = Arc::new(MemoryBackend::new(CacheConfig::default()));
451 let manager = MemoryInvalidationManager::new(backend.clone());
452
453 backend.put("user:1", b"data1".to_vec(), None).await.unwrap();
455 backend.put("user:2", b"data2".to_vec(), None).await.unwrap();
456 backend.put("post:1", b"data3".to_vec(), None).await.unwrap();
457
458 let mut entry1 = InvalidatableEntry::new("user:1".to_string());
460 entry1.add_tag("users".to_string());
461 manager.register_entry(entry1).await.unwrap();
462
463 let mut entry2 = InvalidatableEntry::new("user:2".to_string());
464 entry2.add_tag("users".to_string());
465 manager.register_entry(entry2).await.unwrap();
466
467 let mut entry3 = InvalidatableEntry::new("post:1".to_string());
468 entry3.add_tag("posts".to_string());
469 manager.register_entry(entry3).await.unwrap();
470
471 let invalidated = manager
473 .invalidate_pattern(PatternStrategy::Wildcard("user:*".to_string()))
474 .await
475 .unwrap();
476 assert_eq!(invalidated.len(), 2);
477 assert!(invalidated.contains(&"user:1".to_string()));
478 assert!(invalidated.contains(&"user:2".to_string()));
479
480 assert_eq!(backend.get("user:1").await.unwrap(), None);
482 assert_eq!(backend.get("user:2").await.unwrap(), None);
483 assert_eq!(backend.get("post:1").await.unwrap(), Some(b"data3".to_vec()));
484
485 let invalidated = manager.invalidate_tags(&["posts"]).await.unwrap();
487 assert_eq!(invalidated.len(), 1);
488 assert!(invalidated.contains(&"post:1".to_string()));
489
490 assert_eq!(backend.get("post:1").await.unwrap(), None);
492 }
493
494 #[tokio::test]
495 async fn test_dependency_invalidation() {
496 let backend = Arc::new(MemoryBackend::new(CacheConfig::default()));
497 let manager = MemoryInvalidationManager::new(backend.clone());
498
499 backend.put("parent", b"parent_data".to_vec(), None).await.unwrap();
501 backend.put("child1", b"child1_data".to_vec(), None).await.unwrap();
502 backend.put("child2", b"child2_data".to_vec(), None).await.unwrap();
503 backend.put("independent", b"independent_data".to_vec(), None).await.unwrap();
504
505 let parent_entry = InvalidatableEntry::new("parent".to_string());
507 manager.register_entry(parent_entry).await.unwrap();
508
509 let mut child1_entry = InvalidatableEntry::new("child1".to_string());
510 child1_entry.add_dependency("parent".to_string());
511 manager.register_entry(child1_entry).await.unwrap();
512
513 let mut child2_entry = InvalidatableEntry::new("child2".to_string());
514 child2_entry.add_dependency("parent".to_string());
515 manager.register_entry(child2_entry).await.unwrap();
516
517 let independent_entry = InvalidatableEntry::new("independent".to_string());
518 manager.register_entry(independent_entry).await.unwrap();
519
520 let invalidated = manager
522 .invalidate_dependencies(&["parent"])
523 .await
524 .unwrap();
525
526 assert_eq!(invalidated.len(), 2);
527 assert!(invalidated.contains(&"child1".to_string()));
528 assert!(invalidated.contains(&"child2".to_string()));
529
530 assert_eq!(backend.get("child1").await.unwrap(), None);
532 assert_eq!(backend.get("child2").await.unwrap(), None);
533 assert_eq!(backend.get("parent").await.unwrap(), Some(b"parent_data".to_vec()));
534 assert_eq!(backend.get("independent").await.unwrap(), Some(b"independent_data".to_vec()));
535 }
536
537 #[tokio::test]
538 async fn test_time_based_invalidation() {
539 let backend = Arc::new(MemoryBackend::new(CacheConfig::default()));
540 let manager = MemoryInvalidationManager::new(backend.clone());
541
542 backend.put("temp1", b"temp1_data".to_vec(), None).await.unwrap();
544 backend.put("temp2", b"temp2_data".to_vec(), None).await.unwrap();
545 backend.put("permanent", b"permanent_data".to_vec(), None).await.unwrap();
546
547 let mut temp1_entry = InvalidatableEntry::new("temp1".to_string());
549 temp1_entry.add_time_policy(TimeBasedPolicy::Ttl(Duration::from_millis(1)));
550 manager.register_entry(temp1_entry).await.unwrap();
551
552 let mut temp2_entry = InvalidatableEntry::new("temp2".to_string());
553 temp2_entry.add_time_policy(TimeBasedPolicy::Ttl(Duration::from_millis(1)));
554 manager.register_entry(temp2_entry).await.unwrap();
555
556 let permanent_entry = InvalidatableEntry::new("permanent".to_string());
557 manager.register_entry(permanent_entry).await.unwrap();
558
559 sleep(Duration::from_millis(2)).await;
561
562 let invalidated = manager.invalidate_expired().await.unwrap();
564 assert_eq!(invalidated.len(), 2);
565 assert!(invalidated.contains(&"temp1".to_string()));
566 assert!(invalidated.contains(&"temp2".to_string()));
567
568 assert_eq!(backend.get("temp1").await.unwrap(), None);
570 assert_eq!(backend.get("temp2").await.unwrap(), None);
571 assert_eq!(backend.get("permanent").await.unwrap(), Some(b"permanent_data".to_vec()));
572 }
573
574 #[tokio::test]
575 async fn test_parallel_invalidation_performance() {
576 let backend = Arc::new(MemoryBackend::new(CacheConfig::default()));
577 let manager = MemoryInvalidationManager::new(backend.clone());
578
579 for i in 0..100 {
581 backend.put(&format!("batch_key_{}", i), b"data".to_vec(), None).await.unwrap();
582
583 let mut entry = InvalidatableEntry::new(format!("batch_key_{}", i));
584 entry.add_tag("batch".to_string());
585 manager.register_entry(entry).await.unwrap();
586 }
587
588 let start_time = std::time::Instant::now();
590 let invalidated = manager
591 .invalidate_pattern(PatternStrategy::Wildcard("batch_key_*".to_string()))
592 .await
593 .unwrap();
594 let duration = start_time.elapsed();
595
596 assert_eq!(invalidated.len(), 100);
598
599 assert!(duration.as_millis() < 1000, "Parallel invalidation took too long: {:?}", duration);
602
603 for i in 0..100 {
605 assert_eq!(backend.get(&format!("batch_key_{}", i)).await.unwrap(), None);
606 }
607 }
608}