sieve_cache/sync.rs
1use crate::SieveCache;
2use std::borrow::Borrow;
3use std::hash::Hash;
4use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
5
6/// A thread-safe wrapper around `SieveCache`.
7///
8/// This provides a thread-safe implementation of the SIEVE cache algorithm by wrapping
9/// the standard `SieveCache` in an `Arc<Mutex<>>`. It offers the same functionality as
10/// the underlying cache but with thread safety guarantees.
11///
12/// # Thread Safety
13///
14/// All operations acquire a lock on the entire cache, which provides strong consistency
15/// but may become a bottleneck under high contention. For workloads with high concurrency,
16/// consider using [`ShardedSieveCache`](crate::ShardedSieveCache) instead, which partitions
17/// the cache into multiple independently-locked segments.
18///
19/// # Examples
20///
21/// ```
22/// # use sieve_cache::SyncSieveCache;
23/// let cache = SyncSieveCache::new(100).unwrap();
24///
25/// // Multiple threads can safely access the cache
26/// cache.insert("key1".to_string(), "value1".to_string());
27/// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
28/// ```
29#[derive(Clone)]
30pub struct SyncSieveCache<K, V>
31where
32 K: Eq + Hash + Clone + Send + Sync,
33 V: Send + Sync,
34{
35 inner: Arc<Mutex<SieveCache<K, V>>>,
36}
37
38impl<K, V> SyncSieveCache<K, V>
39where
40 K: Eq + Hash + Clone + Send + Sync,
41 V: Send + Sync,
42{
43 /// Creates a new thread-safe cache with the given capacity.
44 ///
45 /// # Errors
46 ///
47 /// Returns an error if the capacity is zero.
48 ///
49 /// # Examples
50 ///
51 /// ```
52 /// # use sieve_cache::SyncSieveCache;
53 /// let cache = SyncSieveCache::<String, String>::new(100).unwrap();
54 /// ```
55 pub fn new(capacity: usize) -> Result<Self, &'static str> {
56 let cache = SieveCache::new(capacity)?;
57 Ok(Self {
58 inner: Arc::new(Mutex::new(cache)),
59 })
60 }
61
62 /// Returns a locked reference to the underlying cache.
63 ///
64 /// This is an internal helper method to abstract away the lock handling.
65 /// If the mutex is poisoned due to a panic in another thread, the poison
66 /// error is recovered from by calling `into_inner()` to access the underlying data.
67 #[inline]
68 fn locked_cache(&self) -> MutexGuard<'_, SieveCache<K, V>> {
69 self.inner.lock().unwrap_or_else(PoisonError::into_inner)
70 }
71
72 /// Returns the capacity of the cache.
73 ///
74 /// # Examples
75 ///
76 /// ```
77 /// # use sieve_cache::SyncSieveCache;
78 /// let cache = SyncSieveCache::<String, u32>::new(100).unwrap();
79 /// assert_eq!(cache.capacity(), 100);
80 /// ```
81 pub fn capacity(&self) -> usize {
82 self.locked_cache().capacity()
83 }
84
85 /// Returns the number of cached values.
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// # use sieve_cache::SyncSieveCache;
91 /// let cache = SyncSieveCache::new(100).unwrap();
92 /// cache.insert("key".to_string(), "value".to_string());
93 /// assert_eq!(cache.len(), 1);
94 /// ```
95 pub fn len(&self) -> usize {
96 self.locked_cache().len()
97 }
98
99 /// Returns `true` when no values are currently cached.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// # use sieve_cache::SyncSieveCache;
105 /// let cache = SyncSieveCache::<String, String>::new(100).unwrap();
106 /// assert!(cache.is_empty());
107 ///
108 /// cache.insert("key".to_string(), "value".to_string());
109 /// assert!(!cache.is_empty());
110 /// ```
111 pub fn is_empty(&self) -> bool {
112 self.locked_cache().is_empty()
113 }
114
115 /// Returns `true` if there is a value in the cache mapped to by `key`.
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// # use sieve_cache::SyncSieveCache;
121 /// let cache = SyncSieveCache::new(100).unwrap();
122 /// cache.insert("key".to_string(), "value".to_string());
123 ///
124 /// assert!(cache.contains_key(&"key".to_string()));
125 /// assert!(!cache.contains_key(&"missing".to_string()));
126 /// ```
127 pub fn contains_key<Q>(&self, key: &Q) -> bool
128 where
129 Q: Hash + Eq + ?Sized,
130 K: Borrow<Q>,
131 {
132 let mut guard = self.locked_cache();
133 guard.contains_key(key)
134 }
135
136 /// Gets a clone of the value in the cache mapped to by `key`.
137 ///
138 /// If no value exists for `key`, this returns `None`.
139 ///
140 /// # Note
141 ///
142 /// Unlike the unwrapped `SieveCache`, this returns a clone of the value
143 /// rather than a reference, since the mutex guard would be dropped after
144 /// this method returns. This means that `V` must implement `Clone`.
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// # use sieve_cache::SyncSieveCache;
150 /// let cache = SyncSieveCache::new(100).unwrap();
151 /// cache.insert("key".to_string(), "value".to_string());
152 ///
153 /// assert_eq!(cache.get(&"key".to_string()), Some("value".to_string()));
154 /// assert_eq!(cache.get(&"missing".to_string()), None);
155 /// ```
156 pub fn get<Q>(&self, key: &Q) -> Option<V>
157 where
158 Q: Hash + Eq + ?Sized,
159 K: Borrow<Q>,
160 V: Clone,
161 {
162 let mut guard = self.locked_cache();
163 guard.get(key).cloned()
164 }
165
166 /// Gets a mutable reference to the value in the cache mapped to by `key` via a callback function.
167 ///
168 /// If no value exists for `key`, the callback will not be invoked and this returns `false`.
169 /// Otherwise, the callback is invoked with a mutable reference to the value and this returns `true`.
170 ///
171 /// This operation marks the entry as "visited" in the SIEVE algorithm,
172 /// which affects eviction decisions.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// # use sieve_cache::SyncSieveCache;
178 /// let cache = SyncSieveCache::new(100).unwrap();
179 /// cache.insert("key".to_string(), "value".to_string());
180 ///
181 /// // Modify the value in-place
182 /// cache.get_mut(&"key".to_string(), |value| {
183 /// *value = "new_value".to_string();
184 /// });
185 ///
186 /// assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
187 /// ```
188 pub fn get_mut<Q, F>(&self, key: &Q, f: F) -> bool
189 where
190 Q: Hash + Eq + ?Sized,
191 K: Borrow<Q>,
192 F: FnOnce(&mut V),
193 {
194 let mut guard = self.locked_cache();
195 if let Some(value) = guard.get_mut(key) {
196 f(value);
197 true
198 } else {
199 false
200 }
201 }
202
203 /// Maps `key` to `value` in the cache, possibly evicting old entries.
204 ///
205 /// This method returns `true` when this is a new entry, and `false` if an existing entry was
206 /// updated.
207 ///
208 /// # Examples
209 ///
210 /// ```
211 /// # use sieve_cache::SyncSieveCache;
212 /// let cache = SyncSieveCache::new(100).unwrap();
213 ///
214 /// // Insert a new key
215 /// assert!(cache.insert("key1".to_string(), "value1".to_string()));
216 ///
217 /// // Update an existing key
218 /// assert!(!cache.insert("key1".to_string(), "updated_value".to_string()));
219 /// ```
220 pub fn insert(&self, key: K, value: V) -> bool {
221 let mut guard = self.locked_cache();
222 guard.insert(key, value)
223 }
224
225 /// Removes the cache entry mapped to by `key`.
226 ///
227 /// This method returns the value removed from the cache. If `key` did not map to any value,
228 /// then this returns `None`.
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// # use sieve_cache::SyncSieveCache;
234 /// let cache = SyncSieveCache::new(100).unwrap();
235 /// cache.insert("key".to_string(), "value".to_string());
236 ///
237 /// // Remove an existing key
238 /// assert_eq!(cache.remove(&"key".to_string()), Some("value".to_string()));
239 ///
240 /// // Attempt to remove a missing key
241 /// assert_eq!(cache.remove(&"key".to_string()), None);
242 /// ```
243 pub fn remove<Q>(&self, key: &Q) -> Option<V>
244 where
245 K: Borrow<Q>,
246 Q: Eq + Hash + ?Sized,
247 {
248 let mut guard = self.locked_cache();
249 guard.remove(key)
250 }
251
252 /// Removes and returns a value from the cache that was not recently accessed.
253 ///
254 /// This implements the SIEVE eviction algorithm to select an entry for removal.
255 /// If no suitable value exists, this returns `None`.
256 ///
257 /// # Examples
258 ///
259 /// ```
260 /// # use sieve_cache::SyncSieveCache;
261 /// let cache = SyncSieveCache::new(2).unwrap();
262 /// cache.insert("key1".to_string(), "value1".to_string());
263 /// cache.insert("key2".to_string(), "value2".to_string());
264 ///
265 /// // Accessing key1 marks it as recently used
266 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
267 ///
268 /// // Insert a new key, which should evict key2
269 /// cache.insert("key3".to_string(), "value3".to_string());
270 ///
271 /// // key2 should have been evicted
272 /// assert_eq!(cache.get(&"key2".to_string()), None);
273 /// ```
274 pub fn evict(&self) -> Option<V> {
275 let mut guard = self.locked_cache();
276 guard.evict()
277 }
278
279 /// Removes all entries from the cache.
280 ///
281 /// This operation clears all stored values and resets the cache to an empty state,
282 /// while maintaining the original capacity.
283 ///
284 /// # Examples
285 ///
286 /// ```
287 /// # use sieve_cache::SyncSieveCache;
288 /// let cache = SyncSieveCache::new(100).unwrap();
289 /// cache.insert("key1".to_string(), "value1".to_string());
290 /// cache.insert("key2".to_string(), "value2".to_string());
291 ///
292 /// assert_eq!(cache.len(), 2);
293 ///
294 /// cache.clear();
295 /// assert_eq!(cache.len(), 0);
296 /// assert!(cache.is_empty());
297 /// ```
298 pub fn clear(&self) {
299 let mut guard = self.locked_cache();
300 guard.clear();
301 }
302
303 /// Returns an iterator over all keys in the cache.
304 ///
305 /// The order of keys is not specified and should not be relied upon.
306 ///
307 /// # Examples
308 ///
309 /// ```
310 /// # use sieve_cache::SyncSieveCache;
311 /// # use std::collections::HashSet;
312 /// let cache = SyncSieveCache::new(100).unwrap();
313 /// cache.insert("key1".to_string(), "value1".to_string());
314 /// cache.insert("key2".to_string(), "value2".to_string());
315 ///
316 /// let keys: HashSet<_> = cache.keys().into_iter().collect();
317 /// assert_eq!(keys.len(), 2);
318 /// assert!(keys.contains(&"key1".to_string()));
319 /// assert!(keys.contains(&"key2".to_string()));
320 /// ```
321 pub fn keys(&self) -> Vec<K> {
322 let guard = self.locked_cache();
323 guard.keys().cloned().collect()
324 }
325
326 /// Returns all values in the cache.
327 ///
328 /// The order of values is not specified and should not be relied upon.
329 ///
330 /// # Examples
331 ///
332 /// ```
333 /// # use sieve_cache::SyncSieveCache;
334 /// # use std::collections::HashSet;
335 /// let cache = SyncSieveCache::new(100).unwrap();
336 /// cache.insert("key1".to_string(), "value1".to_string());
337 /// cache.insert("key2".to_string(), "value2".to_string());
338 ///
339 /// let values: HashSet<_> = cache.values().into_iter().collect();
340 /// assert_eq!(values.len(), 2);
341 /// assert!(values.contains(&"value1".to_string()));
342 /// assert!(values.contains(&"value2".to_string()));
343 /// ```
344 pub fn values(&self) -> Vec<V>
345 where
346 V: Clone,
347 {
348 let guard = self.locked_cache();
349 guard.values().cloned().collect()
350 }
351
352 /// Returns all key-value pairs in the cache.
353 ///
354 /// The order of pairs is not specified and should not be relied upon.
355 ///
356 /// # Examples
357 ///
358 /// ```
359 /// # use sieve_cache::SyncSieveCache;
360 /// # use std::collections::HashMap;
361 /// let cache = SyncSieveCache::new(100).unwrap();
362 /// cache.insert("key1".to_string(), "value1".to_string());
363 /// cache.insert("key2".to_string(), "value2".to_string());
364 ///
365 /// let entries: HashMap<_, _> = cache.entries().into_iter().collect();
366 /// assert_eq!(entries.len(), 2);
367 /// assert_eq!(entries.get(&"key1".to_string()), Some(&"value1".to_string()));
368 /// assert_eq!(entries.get(&"key2".to_string()), Some(&"value2".to_string()));
369 /// ```
370 pub fn entries(&self) -> Vec<(K, V)>
371 where
372 V: Clone,
373 {
374 let guard = self.locked_cache();
375 guard.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
376 }
377
378 /// Applies a function to all values in the cache.
379 ///
380 /// This method marks all entries as visited.
381 ///
382 /// # Examples
383 ///
384 /// ```
385 /// # use sieve_cache::SyncSieveCache;
386 /// let cache = SyncSieveCache::new(100).unwrap();
387 /// cache.insert("key1".to_string(), "value1".to_string());
388 /// cache.insert("key2".to_string(), "value2".to_string());
389 ///
390 /// // Update all values by appending text
391 /// cache.for_each_value(|value| {
392 /// *value = format!("{}_updated", value);
393 /// });
394 ///
395 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_updated".to_string()));
396 /// assert_eq!(cache.get(&"key2".to_string()), Some("value2_updated".to_string()));
397 /// ```
398 pub fn for_each_value<F>(&self, f: F)
399 where
400 F: FnMut(&mut V),
401 {
402 let mut guard = self.locked_cache();
403 guard.values_mut().for_each(f);
404 }
405
406 /// Applies a function to all key-value pairs in the cache.
407 ///
408 /// This method marks all entries as visited.
409 ///
410 /// # Examples
411 ///
412 /// ```
413 /// # use sieve_cache::SyncSieveCache;
414 /// let cache = SyncSieveCache::new(100).unwrap();
415 /// cache.insert("key1".to_string(), "value1".to_string());
416 /// cache.insert("key2".to_string(), "value2".to_string());
417 ///
418 /// // Update all values associated with keys containing '1'
419 /// cache.for_each_entry(|(key, value)| {
420 /// if key.contains('1') {
421 /// *value = format!("{}_special", value);
422 /// }
423 /// });
424 ///
425 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_special".to_string()));
426 /// assert_eq!(cache.get(&"key2".to_string()), Some("value2".to_string()));
427 /// ```
428 pub fn for_each_entry<F>(&self, mut f: F)
429 where
430 F: FnMut((&K, &mut V)),
431 {
432 let mut guard = self.locked_cache();
433 guard.iter_mut().for_each(|entry| f(entry));
434 }
435
436 /// Gets exclusive access to the underlying cache to perform multiple operations atomically.
437 ///
438 /// This is useful when you need to perform a series of operations that depend on each other
439 /// and you want to ensure that no other thread can access the cache between operations.
440 ///
441 /// # Examples
442 ///
443 /// ```
444 /// # use sieve_cache::SyncSieveCache;
445 /// let cache = SyncSieveCache::new(100).unwrap();
446 ///
447 /// cache.with_lock(|inner_cache| {
448 /// // Perform multiple operations atomically
449 /// inner_cache.insert("key1".to_string(), "value1".to_string());
450 /// inner_cache.insert("key2".to_string(), "value2".to_string());
451 ///
452 /// // We can check internal state mid-transaction
453 /// assert_eq!(inner_cache.len(), 2);
454 /// });
455 /// ```
456 pub fn with_lock<F, T>(&self, f: F) -> T
457 where
458 F: FnOnce(&mut SieveCache<K, V>) -> T,
459 {
460 let mut guard = self.locked_cache();
461 f(&mut guard)
462 }
463
464 /// Retains only the elements specified by the predicate.
465 ///
466 /// Removes all entries for which the provided function returns `false`.
467 /// The elements are visited in arbitrary, unspecified order.
468 /// This operation acquires a lock on the entire cache.
469 ///
470 /// # Examples
471 ///
472 /// ```
473 /// # use sieve_cache::SyncSieveCache;
474 /// let cache = SyncSieveCache::new(100).unwrap();
475 /// cache.insert("key1".to_string(), 100);
476 /// cache.insert("key2".to_string(), 200);
477 /// cache.insert("key3".to_string(), 300);
478 ///
479 /// // Keep only entries with values greater than 150
480 /// cache.retain(|_, value| *value > 150);
481 ///
482 /// assert_eq!(cache.len(), 2);
483 /// assert!(!cache.contains_key(&"key1".to_string()));
484 /// assert!(cache.contains_key(&"key2".to_string()));
485 /// assert!(cache.contains_key(&"key3".to_string()));
486 /// ```
487 pub fn retain<F>(&self, f: F)
488 where
489 F: FnMut(&K, &V) -> bool,
490 {
491 let mut guard = self.locked_cache();
492 guard.retain(f);
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use std::thread;
500
501 #[test]
502 fn test_sync_cache() {
503 let cache = SyncSieveCache::new(100).unwrap();
504
505 // Insert a value
506 assert!(cache.insert("key1".to_string(), "value1".to_string()));
507
508 // Read back the value
509 assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
510
511 // Check contains_key
512 assert!(cache.contains_key(&"key1".to_string()));
513
514 // Check capacity and length
515 assert_eq!(cache.capacity(), 100);
516 assert_eq!(cache.len(), 1);
517
518 // Remove a value
519 assert_eq!(
520 cache.remove(&"key1".to_string()),
521 Some("value1".to_string())
522 );
523 assert_eq!(cache.len(), 0);
524 assert!(cache.is_empty());
525 }
526
527 #[test]
528 fn test_multithreaded_access() {
529 let cache = SyncSieveCache::new(100).unwrap();
530 let cache_clone = cache.clone();
531
532 // Add some initial data
533 cache.insert("shared".to_string(), "initial".to_string());
534
535 // Spawn a thread that updates the cache
536 let thread = thread::spawn(move || {
537 cache_clone.insert("shared".to_string(), "updated".to_string());
538 cache_clone.insert("thread_only".to_string(), "thread_value".to_string());
539 });
540
541 // Main thread operations
542 cache.insert("main_only".to_string(), "main_value".to_string());
543
544 // Wait for thread to complete
545 thread.join().unwrap();
546
547 // Verify results
548 assert_eq!(
549 cache.get(&"shared".to_string()),
550 Some("updated".to_string())
551 );
552 assert_eq!(
553 cache.get(&"thread_only".to_string()),
554 Some("thread_value".to_string())
555 );
556 assert_eq!(
557 cache.get(&"main_only".to_string()),
558 Some("main_value".to_string())
559 );
560 assert_eq!(cache.len(), 3);
561 }
562
563 #[test]
564 fn test_with_lock() {
565 let cache = SyncSieveCache::new(100).unwrap();
566
567 // Perform multiple operations atomically
568 cache.with_lock(|inner_cache| {
569 inner_cache.insert("key1".to_string(), "value1".to_string());
570 inner_cache.insert("key2".to_string(), "value2".to_string());
571 inner_cache.insert("key3".to_string(), "value3".to_string());
572 });
573
574 assert_eq!(cache.len(), 3);
575 }
576
577 #[test]
578 fn test_get_mut() {
579 let cache = SyncSieveCache::new(100).unwrap();
580 cache.insert("key".to_string(), "value".to_string());
581
582 // Modify the value in-place
583 let modified = cache.get_mut(&"key".to_string(), |value| {
584 *value = "new_value".to_string();
585 });
586 assert!(modified);
587
588 // Verify the value was updated
589 assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
590
591 // Try to modify a non-existent key
592 let modified = cache.get_mut(&"missing".to_string(), |_| {
593 panic!("This should not be called");
594 });
595 assert!(!modified);
596 }
597
598 #[test]
599 fn test_clear() {
600 let cache = SyncSieveCache::new(10).unwrap();
601 cache.insert("key1".to_string(), "value1".to_string());
602 cache.insert("key2".to_string(), "value2".to_string());
603
604 assert_eq!(cache.len(), 2);
605 assert!(!cache.is_empty());
606
607 cache.clear();
608
609 assert_eq!(cache.len(), 0);
610 assert!(cache.is_empty());
611 assert_eq!(cache.get(&"key1".to_string()), None);
612 assert_eq!(cache.get(&"key2".to_string()), None);
613 }
614
615 #[test]
616 fn test_keys_values_entries() {
617 let cache = SyncSieveCache::new(10).unwrap();
618 cache.insert("key1".to_string(), "value1".to_string());
619 cache.insert("key2".to_string(), "value2".to_string());
620
621 // Test keys
622 let keys = cache.keys();
623 assert_eq!(keys.len(), 2);
624 assert!(keys.contains(&"key1".to_string()));
625 assert!(keys.contains(&"key2".to_string()));
626
627 // Test values
628 let values = cache.values();
629 assert_eq!(values.len(), 2);
630 assert!(values.contains(&"value1".to_string()));
631 assert!(values.contains(&"value2".to_string()));
632
633 // Test entries
634 let entries = cache.entries();
635 assert_eq!(entries.len(), 2);
636 assert!(entries.contains(&("key1".to_string(), "value1".to_string())));
637 assert!(entries.contains(&("key2".to_string(), "value2".to_string())));
638 }
639
640 #[test]
641 fn test_for_each_methods() {
642 let cache = SyncSieveCache::new(10).unwrap();
643 cache.insert("key1".to_string(), "value1".to_string());
644 cache.insert("key2".to_string(), "value2".to_string());
645
646 // Test for_each_value
647 cache.for_each_value(|value| {
648 *value = format!("{}_updated", value);
649 });
650
651 assert_eq!(
652 cache.get(&"key1".to_string()),
653 Some("value1_updated".to_string())
654 );
655 assert_eq!(
656 cache.get(&"key2".to_string()),
657 Some("value2_updated".to_string())
658 );
659
660 // Test for_each_entry
661 cache.for_each_entry(|(key, value)| {
662 if key == "key1" {
663 *value = format!("{}_special", value);
664 }
665 });
666
667 assert_eq!(
668 cache.get(&"key1".to_string()),
669 Some("value1_updated_special".to_string())
670 );
671 assert_eq!(
672 cache.get(&"key2".to_string()),
673 Some("value2_updated".to_string())
674 );
675 }
676
677 #[test]
678 fn test_retain() {
679 let cache = SyncSieveCache::new(10).unwrap();
680
681 // Add some entries
682 cache.insert("even1".to_string(), 2);
683 cache.insert("even2".to_string(), 4);
684 cache.insert("odd1".to_string(), 1);
685 cache.insert("odd2".to_string(), 3);
686
687 assert_eq!(cache.len(), 4);
688
689 // Keep only entries with even values
690 cache.retain(|_, v| v % 2 == 0);
691
692 assert_eq!(cache.len(), 2);
693 assert!(cache.contains_key(&"even1".to_string()));
694 assert!(cache.contains_key(&"even2".to_string()));
695 assert!(!cache.contains_key(&"odd1".to_string()));
696 assert!(!cache.contains_key(&"odd2".to_string()));
697
698 // Keep only entries with keys containing '1'
699 cache.retain(|k, _| k.contains('1'));
700
701 assert_eq!(cache.len(), 1);
702 assert!(cache.contains_key(&"even1".to_string()));
703 assert!(!cache.contains_key(&"even2".to_string()));
704 }
705}