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}