sieve_cache/sync.rs
1use crate::SieveCache;
2use std::borrow::Borrow;
3use std::fmt;
4use std::hash::Hash;
5use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
6
7/// A thread-safe wrapper around `SieveCache`.
8///
9/// This provides a thread-safe implementation of the SIEVE cache algorithm by wrapping
10/// the standard `SieveCache` in an `Arc<Mutex<>>`. It offers the same functionality as
11/// the underlying cache but with thread safety guarantees.
12///
13/// # Thread Safety
14///
15/// All operations acquire a lock on the entire cache, which provides strong consistency
16/// but may become a bottleneck under high contention. For workloads with high concurrency,
17/// consider using [`ShardedSieveCache`](crate::ShardedSieveCache) instead, which partitions
18/// the cache into multiple independently-locked segments.
19///
20/// ## Lock Behavior
21///
22/// The thread safety mechanism works as follows:
23///
24/// - Simple query operations (e.g., `get`, `contains_key`) hold the lock only long enough to
25/// read and clone the value
26/// - Modification operations (e.g., `insert`, `remove`) hold the lock for the duration of the change
27/// - Operations that accept callbacks have specific lock behavior:
28/// - `get_mut` holds the lock while executing the callback, which may lead to contention
29/// with long-running callbacks
30/// - `for_each_value`, `for_each_entry`, and `retain` collect data under the lock, then
31/// release it before processing to avoid blocking other threads, meaning they operate
32/// on a snapshot and may miss concurrent modifications
33///
34/// ## Consistency Guarantees
35///
36/// - Operations on individual keys are atomic and isolated
37/// - Snapshot-based operations (e.g., iteration, bulk operations) may not reflect
38/// concurrent modifications
39/// - When using callback functions, beware of causing deadlocks by acquiring other locks
40/// or making recursive calls to the same cache
41///
42/// # Examples
43///
44/// ```
45/// # use sieve_cache::SyncSieveCache;
46/// let cache = SyncSieveCache::new(100).unwrap();
47///
48/// // Multiple threads can safely access the cache
49/// cache.insert("key1".to_string(), "value1".to_string());
50/// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
51/// ```
52///
53/// Example with callbacks:
54///
55/// ```
56/// # use sieve_cache::SyncSieveCache;
57/// # use std::thread;
58/// let cache = SyncSieveCache::new(100).unwrap();
59/// cache.insert("key1".to_string(), 1);
60/// cache.insert("key2".to_string(), 2);
61///
62/// // Create a clone to move into another thread
63/// let cache_clone = cache.clone();
64///
65/// // Spawn a thread that modifies values
66/// let handle = thread::spawn(move || {
67/// // The cache is safely accessible from multiple threads
68/// cache_clone.for_each_value(|value| {
69/// *value += 10; // Add 10 to each value
70/// });
71/// });
72///
73/// // Wait for the background thread to complete
74/// handle.join().unwrap();
75///
76/// // Values have been updated
77/// assert_eq!(cache.get(&"key1".to_string()), Some(11));
78/// assert_eq!(cache.get(&"key2".to_string()), Some(12));
79/// ```
80#[derive(Clone)]
81pub struct SyncSieveCache<K, V>
82where
83 K: Eq + Hash + Clone + Send + Sync,
84 V: Send + Sync,
85{
86 inner: Arc<Mutex<SieveCache<K, V>>>,
87}
88
89unsafe impl<K, V> Sync for SyncSieveCache<K, V>
90where
91 K: Eq + Hash + Clone + Send + Sync,
92 V: Send + Sync,
93{
94}
95
96impl<K, V> Default for SyncSieveCache<K, V>
97where
98 K: Eq + Hash + Clone + Send + Sync,
99 V: Send + Sync,
100{
101 /// Creates a new cache with a default capacity of 100 entries.
102 ///
103 /// # Panics
104 ///
105 /// Panics if the underlying `SieveCache::new()` returns an error, which should never
106 /// happen for a non-zero capacity.
107 ///
108 /// # Examples
109 ///
110 /// ```
111 /// # use sieve_cache::SyncSieveCache;
112 /// # use std::default::Default;
113 /// let cache: SyncSieveCache<String, u32> = Default::default();
114 /// assert_eq!(cache.capacity(), 100);
115 /// ```
116 fn default() -> Self {
117 Self::new(100).expect("Failed to create cache with default capacity")
118 }
119}
120
121impl<K, V> fmt::Debug for SyncSieveCache<K, V>
122where
123 K: Eq + Hash + Clone + Send + Sync + fmt::Debug,
124 V: Send + Sync + fmt::Debug,
125{
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 let guard = self.locked_cache();
128 f.debug_struct("SyncSieveCache")
129 .field("capacity", &guard.capacity())
130 .field("len", &guard.len())
131 .finish()
132 }
133}
134
135impl<K, V> From<SieveCache<K, V>> for SyncSieveCache<K, V>
136where
137 K: Eq + Hash + Clone + Send + Sync,
138 V: Send + Sync,
139{
140 /// Creates a new thread-safe cache from an existing `SieveCache`.
141 ///
142 /// This allows for easily converting a single-threaded cache to a thread-safe version.
143 ///
144 /// # Examples
145 ///
146 /// ```
147 /// # use sieve_cache::{SieveCache, SyncSieveCache};
148 /// let mut single_threaded = SieveCache::new(100).unwrap();
149 /// single_threaded.insert("key".to_string(), "value".to_string());
150 ///
151 /// // Convert to thread-safe version
152 /// let thread_safe = SyncSieveCache::from(single_threaded);
153 /// assert_eq!(thread_safe.get(&"key".to_string()), Some("value".to_string()));
154 /// ```
155 fn from(cache: SieveCache<K, V>) -> Self {
156 Self {
157 inner: Arc::new(Mutex::new(cache)),
158 }
159 }
160}
161
162impl<K, V> IntoIterator for SyncSieveCache<K, V>
163where
164 K: Eq + Hash + Clone + Send + Sync,
165 V: Clone + Send + Sync,
166{
167 type Item = (K, V);
168 type IntoIter = std::vec::IntoIter<(K, V)>;
169
170 /// Converts the cache into an iterator over its key-value pairs.
171 ///
172 /// This collects all entries into a Vec and returns an iterator over that Vec.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// # use sieve_cache::SyncSieveCache;
178 /// # use std::collections::HashMap;
179 /// let cache = SyncSieveCache::new(100).unwrap();
180 /// cache.insert("key1".to_string(), "value1".to_string());
181 /// cache.insert("key2".to_string(), "value2".to_string());
182 ///
183 /// // Collect into a HashMap
184 /// let map: HashMap<_, _> = cache.into_iter().collect();
185 /// assert_eq!(map.len(), 2);
186 /// assert_eq!(map.get("key1"), Some(&"value1".to_string()));
187 /// ```
188 fn into_iter(self) -> Self::IntoIter {
189 self.entries().into_iter()
190 }
191}
192
193impl<K, V> SyncSieveCache<K, V>
194where
195 K: Eq + Hash + Clone + Send + Sync,
196 V: Send + Sync,
197{
198 /// Creates a new thread-safe cache with the given capacity.
199 ///
200 /// # Errors
201 ///
202 /// Returns an error if the capacity is zero.
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// # use sieve_cache::SyncSieveCache;
208 /// let cache = SyncSieveCache::<String, String>::new(100).unwrap();
209 /// ```
210 pub fn new(capacity: usize) -> Result<Self, &'static str> {
211 let cache = SieveCache::new(capacity)?;
212 Ok(Self {
213 inner: Arc::new(Mutex::new(cache)),
214 })
215 }
216
217 /// Returns a locked reference to the underlying cache.
218 ///
219 /// This is an internal helper method to abstract away the lock handling.
220 /// If the mutex is poisoned due to a panic in another thread, the poison
221 /// error is recovered from by calling `into_inner()` to access the underlying data.
222 #[inline]
223 fn locked_cache(&self) -> MutexGuard<'_, SieveCache<K, V>> {
224 self.inner.lock().unwrap_or_else(PoisonError::into_inner)
225 }
226
227 /// Returns the capacity of the cache.
228 ///
229 /// # Examples
230 ///
231 /// ```
232 /// # use sieve_cache::SyncSieveCache;
233 /// let cache = SyncSieveCache::<String, u32>::new(100).unwrap();
234 /// assert_eq!(cache.capacity(), 100);
235 /// ```
236 pub fn capacity(&self) -> usize {
237 self.locked_cache().capacity()
238 }
239
240 /// Returns the number of cached values.
241 ///
242 /// # Examples
243 ///
244 /// ```
245 /// # use sieve_cache::SyncSieveCache;
246 /// let cache = SyncSieveCache::new(100).unwrap();
247 /// cache.insert("key".to_string(), "value".to_string());
248 /// assert_eq!(cache.len(), 1);
249 /// ```
250 pub fn len(&self) -> usize {
251 self.locked_cache().len()
252 }
253
254 /// Returns `true` when no values are currently cached.
255 ///
256 /// # Examples
257 ///
258 /// ```
259 /// # use sieve_cache::SyncSieveCache;
260 /// let cache = SyncSieveCache::<String, String>::new(100).unwrap();
261 /// assert!(cache.is_empty());
262 ///
263 /// cache.insert("key".to_string(), "value".to_string());
264 /// assert!(!cache.is_empty());
265 /// ```
266 pub fn is_empty(&self) -> bool {
267 self.locked_cache().is_empty()
268 }
269
270 /// Returns `true` if there is a value in the cache mapped to by `key`.
271 ///
272 /// # Examples
273 ///
274 /// ```
275 /// # use sieve_cache::SyncSieveCache;
276 /// let cache = SyncSieveCache::new(100).unwrap();
277 /// cache.insert("key".to_string(), "value".to_string());
278 ///
279 /// assert!(cache.contains_key(&"key".to_string()));
280 /// assert!(!cache.contains_key(&"missing".to_string()));
281 /// ```
282 pub fn contains_key<Q>(&self, key: &Q) -> bool
283 where
284 Q: Hash + Eq + ?Sized,
285 K: Borrow<Q>,
286 {
287 let mut guard = self.locked_cache();
288 guard.contains_key(key)
289 }
290
291 /// Gets a clone of the value in the cache mapped to by `key`.
292 ///
293 /// If no value exists for `key`, this returns `None`.
294 ///
295 /// # Note
296 ///
297 /// Unlike the unwrapped `SieveCache`, this returns a clone of the value
298 /// rather than a reference, since the mutex guard would be dropped after
299 /// this method returns. This means that `V` must implement `Clone`.
300 ///
301 /// # Examples
302 ///
303 /// ```
304 /// # use sieve_cache::SyncSieveCache;
305 /// let cache = SyncSieveCache::new(100).unwrap();
306 /// cache.insert("key".to_string(), "value".to_string());
307 ///
308 /// assert_eq!(cache.get(&"key".to_string()), Some("value".to_string()));
309 /// assert_eq!(cache.get(&"missing".to_string()), None);
310 /// ```
311 pub fn get<Q>(&self, key: &Q) -> Option<V>
312 where
313 Q: Hash + Eq + ?Sized,
314 K: Borrow<Q>,
315 V: Clone,
316 {
317 let mut guard = self.locked_cache();
318 guard.get(key).cloned()
319 }
320
321 /// Gets a mutable reference to the value in the cache mapped to by `key` via a callback function.
322 ///
323 /// If no value exists for `key`, the callback will not be invoked and this returns `false`.
324 /// Otherwise, the callback is invoked with a mutable reference to the value and this returns `true`.
325 ///
326 /// This operation marks the entry as "visited" in the SIEVE algorithm,
327 /// which affects eviction decisions.
328 ///
329 /// # Thread Safety
330 ///
331 /// This method operates safely with recursive calls by:
332 ///
333 /// 1. Cloning the value with a short-lived lock
334 /// 2. Releasing the lock during callback execution
335 /// 3. Re-acquiring the lock to update the original value
336 ///
337 /// This approach means:
338 ///
339 /// - The callback can safely make other calls to the same cache instance
340 /// - The value can be modified by other threads during the callback execution
341 /// - Changes are not visible to other threads until the callback completes
342 /// - Last writer wins if multiple threads modify the same key concurrently
343 ///
344 /// # Examples
345 ///
346 /// ```
347 /// # use sieve_cache::SyncSieveCache;
348 /// let cache = SyncSieveCache::new(100).unwrap();
349 /// cache.insert("key".to_string(), "value".to_string());
350 ///
351 /// // Modify the value in-place
352 /// cache.get_mut(&"key".to_string(), |value| {
353 /// *value = "new_value".to_string();
354 /// });
355 ///
356 /// assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
357 /// ```
358 pub fn get_mut<Q, F>(&self, key: &Q, f: F) -> bool
359 where
360 Q: Hash + Eq + ?Sized,
361 K: Borrow<Q>,
362 F: FnOnce(&mut V),
363 V: Clone,
364 {
365 // Get a clone of the value if it exists, to avoid deadlocks
366 // if the callback tries to use other methods on this cache
367 let value_opt = {
368 let mut guard = self.locked_cache();
369 if let Some(v) = guard.get_mut(key) {
370 // Clone the value before releasing the lock
371 Some(v.clone())
372 } else {
373 None
374 }
375 };
376
377 if let Some(mut value) = value_opt {
378 // Execute the callback on the cloned value without holding the lock
379 f(&mut value);
380
381 // Update the value back to the cache
382 let mut guard = self.locked_cache();
383 if let Some(original) = guard.get_mut(key) {
384 *original = value;
385 true
386 } else {
387 // Key was removed while callback was executing
388 false
389 }
390 } else {
391 false
392 }
393 }
394
395 /// Maps `key` to `value` in the cache, possibly evicting old entries.
396 ///
397 /// This method returns `true` when this is a new entry, and `false` if an existing entry was
398 /// updated.
399 ///
400 /// # Examples
401 ///
402 /// ```
403 /// # use sieve_cache::SyncSieveCache;
404 /// let cache = SyncSieveCache::new(100).unwrap();
405 ///
406 /// // Insert a new key
407 /// assert!(cache.insert("key1".to_string(), "value1".to_string()));
408 ///
409 /// // Update an existing key
410 /// assert!(!cache.insert("key1".to_string(), "updated_value".to_string()));
411 /// ```
412 pub fn insert(&self, key: K, value: V) -> bool {
413 let mut guard = self.locked_cache();
414 guard.insert(key, value)
415 }
416
417 /// Removes the cache entry mapped to by `key`.
418 ///
419 /// This method returns the value removed from the cache. If `key` did not map to any value,
420 /// then this returns `None`.
421 ///
422 /// # Examples
423 ///
424 /// ```
425 /// # use sieve_cache::SyncSieveCache;
426 /// let cache = SyncSieveCache::new(100).unwrap();
427 /// cache.insert("key".to_string(), "value".to_string());
428 ///
429 /// // Remove an existing key
430 /// assert_eq!(cache.remove(&"key".to_string()), Some("value".to_string()));
431 ///
432 /// // Attempt to remove a missing key
433 /// assert_eq!(cache.remove(&"key".to_string()), None);
434 /// ```
435 pub fn remove<Q>(&self, key: &Q) -> Option<V>
436 where
437 K: Borrow<Q>,
438 Q: Eq + Hash + ?Sized,
439 {
440 let mut guard = self.locked_cache();
441 guard.remove(key)
442 }
443
444 /// Removes and returns a value from the cache that was not recently accessed.
445 ///
446 /// This implements the SIEVE eviction algorithm to select an entry for removal.
447 /// If no suitable value exists, this returns `None`.
448 ///
449 /// # Examples
450 ///
451 /// ```
452 /// # use sieve_cache::SyncSieveCache;
453 /// let cache = SyncSieveCache::new(2).unwrap();
454 /// cache.insert("key1".to_string(), "value1".to_string());
455 /// cache.insert("key2".to_string(), "value2".to_string());
456 ///
457 /// // Accessing key1 marks it as recently used
458 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
459 ///
460 /// // Insert a new key, which should evict key2
461 /// cache.insert("key3".to_string(), "value3".to_string());
462 ///
463 /// // key2 should have been evicted
464 /// assert_eq!(cache.get(&"key2".to_string()), None);
465 /// ```
466 pub fn evict(&self) -> Option<V> {
467 let mut guard = self.locked_cache();
468 guard.evict()
469 }
470
471 /// Removes all entries from the cache.
472 ///
473 /// This operation clears all stored values and resets the cache to an empty state,
474 /// while maintaining the original capacity.
475 ///
476 /// # Examples
477 ///
478 /// ```
479 /// # use sieve_cache::SyncSieveCache;
480 /// let cache = SyncSieveCache::new(100).unwrap();
481 /// cache.insert("key1".to_string(), "value1".to_string());
482 /// cache.insert("key2".to_string(), "value2".to_string());
483 ///
484 /// assert_eq!(cache.len(), 2);
485 ///
486 /// cache.clear();
487 /// assert_eq!(cache.len(), 0);
488 /// assert!(cache.is_empty());
489 /// ```
490 pub fn clear(&self) {
491 let mut guard = self.locked_cache();
492 guard.clear();
493 }
494
495 /// Returns an iterator over all keys in the cache.
496 ///
497 /// The order of keys is not specified and should not be relied upon.
498 ///
499 /// # Examples
500 ///
501 /// ```
502 /// # use sieve_cache::SyncSieveCache;
503 /// # use std::collections::HashSet;
504 /// let cache = SyncSieveCache::new(100).unwrap();
505 /// cache.insert("key1".to_string(), "value1".to_string());
506 /// cache.insert("key2".to_string(), "value2".to_string());
507 ///
508 /// let keys: HashSet<_> = cache.keys().into_iter().collect();
509 /// assert_eq!(keys.len(), 2);
510 /// assert!(keys.contains(&"key1".to_string()));
511 /// assert!(keys.contains(&"key2".to_string()));
512 /// ```
513 pub fn keys(&self) -> Vec<K> {
514 let guard = self.locked_cache();
515 guard.keys().cloned().collect()
516 }
517
518 /// Returns all values in the cache.
519 ///
520 /// The order of values is not specified and should not be relied upon.
521 ///
522 /// # Examples
523 ///
524 /// ```
525 /// # use sieve_cache::SyncSieveCache;
526 /// # use std::collections::HashSet;
527 /// let cache = SyncSieveCache::new(100).unwrap();
528 /// cache.insert("key1".to_string(), "value1".to_string());
529 /// cache.insert("key2".to_string(), "value2".to_string());
530 ///
531 /// let values: HashSet<_> = cache.values().into_iter().collect();
532 /// assert_eq!(values.len(), 2);
533 /// assert!(values.contains(&"value1".to_string()));
534 /// assert!(values.contains(&"value2".to_string()));
535 /// ```
536 pub fn values(&self) -> Vec<V>
537 where
538 V: Clone,
539 {
540 let guard = self.locked_cache();
541 guard.values().cloned().collect()
542 }
543
544 /// Returns all key-value pairs in the cache.
545 ///
546 /// The order of pairs is not specified and should not be relied upon.
547 ///
548 /// # Examples
549 ///
550 /// ```
551 /// # use sieve_cache::SyncSieveCache;
552 /// # use std::collections::HashMap;
553 /// let cache = SyncSieveCache::new(100).unwrap();
554 /// cache.insert("key1".to_string(), "value1".to_string());
555 /// cache.insert("key2".to_string(), "value2".to_string());
556 ///
557 /// let entries: HashMap<_, _> = cache.entries().into_iter().collect();
558 /// assert_eq!(entries.len(), 2);
559 /// assert_eq!(entries.get(&"key1".to_string()), Some(&"value1".to_string()));
560 /// assert_eq!(entries.get(&"key2".to_string()), Some(&"value2".to_string()));
561 /// ```
562 pub fn entries(&self) -> Vec<(K, V)>
563 where
564 V: Clone,
565 {
566 let guard = self.locked_cache();
567 guard.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
568 }
569
570 /// Applies a function to all values in the cache.
571 ///
572 /// This method safely processes values by collecting them with the lock held,
573 /// then releasing the lock before applying the function to each value individually.
574 /// If the function modifies the values, the changes are saved back to the cache.
575 ///
576 /// # Thread Safety
577 ///
578 /// This method operates in three phases:
579 /// 1. It acquires the lock and creates a complete snapshot of the cache
580 /// 2. It releases the lock and applies the callback to each value
581 /// 3. It acquires the lock again individually for each value when writing changes back
582 ///
583 /// This approach means:
584 /// - The lock is not held during callback execution, preventing lock contention
585 /// - If other threads modify the cache between steps 1 and 3, those changes might be overwritten
586 /// - The callback sees a point-in-time snapshot that might not reflect the latest state
587 /// - For long-running operations, consider using individual key operations instead
588 ///
589 /// # Examples
590 ///
591 /// ```
592 /// # use sieve_cache::SyncSieveCache;
593 /// let cache = SyncSieveCache::new(100).unwrap();
594 /// cache.insert("key1".to_string(), "value1".to_string());
595 /// cache.insert("key2".to_string(), "value2".to_string());
596 ///
597 /// // Update all values by appending text
598 /// cache.for_each_value(|value| {
599 /// *value = format!("{}_updated", value);
600 /// });
601 ///
602 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_updated".to_string()));
603 /// assert_eq!(cache.get(&"key2".to_string()), Some("value2_updated".to_string()));
604 /// ```
605 pub fn for_each_value<F>(&self, mut f: F)
606 where
607 F: FnMut(&mut V),
608 V: Clone,
609 {
610 // First, safely collect all keys and values while holding the lock
611 let entries = self.with_lock(|inner_cache| {
612 inner_cache
613 .iter()
614 .map(|(k, v)| (k.clone(), v.clone()))
615 .collect::<Vec<(K, V)>>()
616 });
617
618 // Process each value outside the lock
619 let mut updated_entries = Vec::new();
620 for (key, mut value) in entries {
621 f(&mut value);
622 updated_entries.push((key, value));
623 }
624
625 // Update any changed values back to the cache
626 for (key, value) in updated_entries {
627 self.insert(key, value);
628 }
629 }
630
631 /// Applies a function to all key-value pairs in the cache.
632 ///
633 /// This method safely processes key-value pairs by collecting them with the lock held,
634 /// then releasing the lock before applying the function to each pair individually.
635 /// If the function modifies the values, the changes are saved back to the cache.
636 ///
637 /// # Thread Safety
638 ///
639 /// This method operates in three phases:
640 /// 1. It acquires the lock and creates a complete snapshot of the cache
641 /// 2. It releases the lock and applies the callback to each key-value pair
642 /// 3. It acquires the lock again individually for each entry when writing changes back
643 ///
644 /// This approach means:
645 /// - The lock is not held during callback execution, preventing lock contention
646 /// - If other threads modify the cache between steps 1 and 3, those changes might be overwritten
647 /// - The callback sees a point-in-time snapshot that might not reflect the latest state
648 /// - For long-running operations, consider using individual key operations instead
649 ///
650 /// # Examples
651 ///
652 /// ```
653 /// # use sieve_cache::SyncSieveCache;
654 /// let cache = SyncSieveCache::new(100).unwrap();
655 /// cache.insert("key1".to_string(), "value1".to_string());
656 /// cache.insert("key2".to_string(), "value2".to_string());
657 ///
658 /// // Update all values associated with keys containing '1'
659 /// cache.for_each_entry(|(key, value)| {
660 /// if key.contains('1') {
661 /// *value = format!("{}_special", value);
662 /// }
663 /// });
664 ///
665 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_special".to_string()));
666 /// assert_eq!(cache.get(&"key2".to_string()), Some("value2".to_string()));
667 /// ```
668 pub fn for_each_entry<F>(&self, mut f: F)
669 where
670 F: FnMut((&K, &mut V)),
671 V: Clone,
672 {
673 // First, safely collect all keys and values while holding the lock
674 let entries = self.with_lock(|inner_cache| {
675 inner_cache
676 .iter()
677 .map(|(k, v)| (k.clone(), v.clone()))
678 .collect::<Vec<(K, V)>>()
679 });
680
681 // Process each entry outside the lock
682 let mut updated_entries = Vec::new();
683 for (key, mut value) in entries {
684 let key_ref = &key;
685 f((key_ref, &mut value));
686 updated_entries.push((key, value));
687 }
688
689 // Update any changed values back to the cache
690 for (key, value) in updated_entries {
691 self.insert(key, value);
692 }
693 }
694
695 /// Gets exclusive access to the underlying cache to perform multiple operations atomically.
696 ///
697 /// This is useful when you need to perform a series of operations that depend on each other
698 /// and you want to ensure that no other thread can access the cache between operations.
699 ///
700 /// # Thread Safety
701 ///
702 /// This method provides the strongest thread safety guarantees by:
703 ///
704 /// - Acquiring the lock once for the entire duration of the callback
705 /// - Allowing multiple operations to be performed atomically within a single lock scope
706 /// - Ensuring all operations within the callback are fully isolated from other threads
707 ///
708 /// However, this also means:
709 ///
710 /// - The entire cache is locked during the callback execution
711 /// - Other threads will be blocked from accessing the cache until the callback completes
712 /// - Long-running operations within the callback will cause high contention
713 /// - Callbacks must never try to access the same cache instance recursively (deadlock risk)
714 ///
715 /// This method should be used when you need atomic multi-step operations but used
716 /// sparingly in high-concurrency environments.
717 ///
718 /// # Examples
719 ///
720 /// ```
721 /// # use sieve_cache::SyncSieveCache;
722 /// let cache = SyncSieveCache::new(100).unwrap();
723 ///
724 /// cache.with_lock(|inner_cache| {
725 /// // Perform multiple operations atomically
726 /// inner_cache.insert("key1".to_string(), "value1".to_string());
727 /// inner_cache.insert("key2".to_string(), "value2".to_string());
728 ///
729 /// // We can check internal state mid-transaction
730 /// assert_eq!(inner_cache.len(), 2);
731 /// });
732 /// ```
733 pub fn with_lock<F, T>(&self, f: F) -> T
734 where
735 F: FnOnce(&mut SieveCache<K, V>) -> T,
736 {
737 let mut guard = self.locked_cache();
738 f(&mut guard)
739 }
740
741 /// Retains only the elements specified by the predicate.
742 ///
743 /// Removes all entries for which the provided function returns `false`.
744 /// The elements are visited in arbitrary, unspecified order.
745 ///
746 /// This method offers two modes of operation:
747 /// - Default mode: evaluates predicates outside the lock, with separate remove operations
748 /// - Batch mode: evaluates predicates outside the lock, then performs removals in a single batch
749 ///
750 /// # Thread Safety
751 ///
752 /// This method operates in three phases:
753 /// 1. It acquires the lock and creates a complete snapshot of the cache
754 /// 2. It releases the lock and applies the predicate to each entry
755 /// 3. It either:
756 /// - Acquires the lock again individually for each entry to be removed (default), or
757 /// - Acquires the lock once and removes all entries in a batch (batch mode)
758 ///
759 /// This approach means:
760 /// - The lock is not held during predicate execution, preventing lock contention
761 /// - If other threads modify the cache between steps 1 and 3, the method may:
762 /// - Remove entries that were modified and would now pass the predicate
763 /// - Miss removing entries added after the snapshot was taken
764 /// - The predicate sees a point-in-time snapshot that might not reflect the latest state
765 /// - For strict consistency requirements, use `with_lock` instead
766 ///
767 /// # Examples
768 ///
769 /// Basic usage:
770 ///
771 /// ```
772 /// # use sieve_cache::SyncSieveCache;
773 /// let cache = SyncSieveCache::new(100).unwrap();
774 /// cache.insert("key1".to_string(), 100);
775 /// cache.insert("key2".to_string(), 200);
776 /// cache.insert("key3".to_string(), 300);
777 ///
778 /// // Keep only entries with values greater than 150
779 /// cache.retain(|_, value| *value > 150);
780 ///
781 /// assert_eq!(cache.len(), 2);
782 /// assert!(!cache.contains_key(&"key1".to_string()));
783 /// assert!(cache.contains_key(&"key2".to_string()));
784 /// assert!(cache.contains_key(&"key3".to_string()));
785 /// ```
786 ///
787 /// Using batch mode (more efficient):
788 ///
789 /// ```
790 /// # use sieve_cache::SyncSieveCache;
791 /// let cache = SyncSieveCache::new(100).unwrap();
792 /// cache.insert("key1".to_string(), 100);
793 /// cache.insert("key2".to_string(), 200);
794 /// cache.insert("key3".to_string(), 300);
795 ///
796 /// // Keep only entries with values greater than 150 (batch mode)
797 /// cache.retain_batch(|_, value| *value > 150);
798 ///
799 /// assert_eq!(cache.len(), 2);
800 /// assert!(!cache.contains_key(&"key1".to_string()));
801 /// assert!(cache.contains_key(&"key2".to_string()));
802 /// assert!(cache.contains_key(&"key3".to_string()));
803 /// ```
804 pub fn retain<F>(&self, mut f: F)
805 where
806 F: FnMut(&K, &V) -> bool,
807 V: Clone,
808 {
809 // First, safely collect all entries while holding the lock
810 let entries = self.with_lock(|inner_cache| {
811 inner_cache
812 .iter()
813 .map(|(k, v)| (k.clone(), v.clone()))
814 .collect::<Vec<(K, V)>>()
815 });
816
817 // Now check each entry against the predicate without holding the lock
818 for (key, value) in entries {
819 if !f(&key, &value) {
820 // Remove entries that don't match the predicate
821 // This acquires the lock for each removal operation
822 self.remove(&key);
823 }
824 }
825 }
826
827 /// Retains only the elements specified by the predicate, using batch processing.
828 ///
829 /// This is an optimized version of `retain()` that collects all keys to remove first,
830 /// then removes them in a single batch operation with a single lock acquisition.
831 /// This is more efficient when removing many entries, especially in high-contention scenarios.
832 ///
833 /// # Thread Safety
834 ///
835 /// This method has improved performance characteristics:
836 /// - Only acquires the lock twice (once for collection, once for removal)
837 /// - Performs all removals in a single atomic operation
838 /// - Reduces lock contention compared to standard `retain()`
839 ///
840 /// However, it still has the same consistency characteristics as `retain()`:
841 /// - The snapshot might not reflect concurrent modifications
842 /// - There's a window between collecting entries and removing them where
843 /// other threads might modify the cache
844 ///
845 /// # Examples
846 ///
847 /// ```
848 /// # use sieve_cache::SyncSieveCache;
849 /// let cache = SyncSieveCache::new(100).unwrap();
850 /// cache.insert("key1".to_string(), 100);
851 /// cache.insert("key2".to_string(), 200);
852 /// cache.insert("key3".to_string(), 300);
853 ///
854 /// // Keep only entries with values greater than 150
855 /// cache.retain_batch(|_, value| *value > 150);
856 ///
857 /// assert_eq!(cache.len(), 2);
858 /// assert!(!cache.contains_key(&"key1".to_string()));
859 /// assert!(cache.contains_key(&"key2".to_string()));
860 /// assert!(cache.contains_key(&"key3".to_string()));
861 /// ```
862 pub fn retain_batch<F>(&self, mut f: F)
863 where
864 F: FnMut(&K, &V) -> bool,
865 V: Clone,
866 {
867 // First, safely collect all entries while holding the lock
868 let entries = self.with_lock(|inner_cache| {
869 inner_cache
870 .iter()
871 .map(|(k, v)| (k.clone(), v.clone()))
872 .collect::<Vec<(K, V)>>()
873 });
874
875 // Collect keys to remove without holding the lock
876 let mut keys_to_remove = Vec::new();
877 for (key, value) in entries {
878 if !f(&key, &value) {
879 keys_to_remove.push(key);
880 }
881 }
882
883 // If there are keys to remove, do it in a single batch operation
884 if !keys_to_remove.is_empty() {
885 self.with_lock(|inner_cache| {
886 for key in keys_to_remove {
887 inner_cache.remove(&key);
888 }
889 });
890 }
891 }
892}
893
894#[cfg(test)]
895mod tests {
896 use super::*;
897 use std::thread;
898
899 #[test]
900 fn test_sync_cache() {
901 let cache = SyncSieveCache::new(100).unwrap();
902
903 // Insert a value
904 assert!(cache.insert("key1".to_string(), "value1".to_string()));
905
906 // Read back the value
907 assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
908
909 // Check contains_key
910 assert!(cache.contains_key(&"key1".to_string()));
911
912 // Check capacity and length
913 assert_eq!(cache.capacity(), 100);
914 assert_eq!(cache.len(), 1);
915
916 // Remove a value
917 assert_eq!(
918 cache.remove(&"key1".to_string()),
919 Some("value1".to_string())
920 );
921 assert_eq!(cache.len(), 0);
922 assert!(cache.is_empty());
923 }
924
925 #[test]
926 fn test_multithreaded_access() {
927 let cache = SyncSieveCache::new(100).unwrap();
928 let cache_clone = cache.clone();
929
930 // Add some initial data
931 cache.insert("shared".to_string(), "initial".to_string());
932
933 // Spawn a thread that updates the cache
934 let thread = thread::spawn(move || {
935 cache_clone.insert("shared".to_string(), "updated".to_string());
936 cache_clone.insert("thread_only".to_string(), "thread_value".to_string());
937 });
938
939 // Main thread operations
940 cache.insert("main_only".to_string(), "main_value".to_string());
941
942 // Wait for thread to complete
943 thread.join().unwrap();
944
945 // Verify results
946 assert_eq!(
947 cache.get(&"shared".to_string()),
948 Some("updated".to_string())
949 );
950 assert_eq!(
951 cache.get(&"thread_only".to_string()),
952 Some("thread_value".to_string())
953 );
954 assert_eq!(
955 cache.get(&"main_only".to_string()),
956 Some("main_value".to_string())
957 );
958 assert_eq!(cache.len(), 3);
959 }
960
961 #[test]
962 fn test_with_lock() {
963 let cache = SyncSieveCache::new(100).unwrap();
964
965 // Perform multiple operations atomically
966 cache.with_lock(|inner_cache| {
967 inner_cache.insert("key1".to_string(), "value1".to_string());
968 inner_cache.insert("key2".to_string(), "value2".to_string());
969 inner_cache.insert("key3".to_string(), "value3".to_string());
970 });
971
972 assert_eq!(cache.len(), 3);
973 }
974
975 #[test]
976 fn test_get_mut() {
977 let cache = SyncSieveCache::new(100).unwrap();
978 cache.insert("key".to_string(), "value".to_string());
979
980 // Modify the value in-place
981 let modified = cache.get_mut(&"key".to_string(), |value| {
982 *value = "new_value".to_string();
983 });
984 assert!(modified);
985
986 // Verify the value was updated
987 assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
988
989 // Try to modify a non-existent key
990 let modified = cache.get_mut(&"missing".to_string(), |_| {
991 panic!("This should not be called");
992 });
993 assert!(!modified);
994 }
995
996 #[test]
997 fn test_clear() {
998 let cache = SyncSieveCache::new(10).unwrap();
999 cache.insert("key1".to_string(), "value1".to_string());
1000 cache.insert("key2".to_string(), "value2".to_string());
1001
1002 assert_eq!(cache.len(), 2);
1003 assert!(!cache.is_empty());
1004
1005 cache.clear();
1006
1007 assert_eq!(cache.len(), 0);
1008 assert!(cache.is_empty());
1009 assert_eq!(cache.get(&"key1".to_string()), None);
1010 assert_eq!(cache.get(&"key2".to_string()), None);
1011 }
1012
1013 #[test]
1014 fn test_keys_values_entries() {
1015 let cache = SyncSieveCache::new(10).unwrap();
1016 cache.insert("key1".to_string(), "value1".to_string());
1017 cache.insert("key2".to_string(), "value2".to_string());
1018
1019 // Test keys
1020 let keys = cache.keys();
1021 assert_eq!(keys.len(), 2);
1022 assert!(keys.contains(&"key1".to_string()));
1023 assert!(keys.contains(&"key2".to_string()));
1024
1025 // Test values
1026 let values = cache.values();
1027 assert_eq!(values.len(), 2);
1028 assert!(values.contains(&"value1".to_string()));
1029 assert!(values.contains(&"value2".to_string()));
1030
1031 // Test entries
1032 let entries = cache.entries();
1033 assert_eq!(entries.len(), 2);
1034 assert!(entries.contains(&("key1".to_string(), "value1".to_string())));
1035 assert!(entries.contains(&("key2".to_string(), "value2".to_string())));
1036 }
1037
1038 #[test]
1039 fn test_for_each_methods() {
1040 let cache = SyncSieveCache::new(10).unwrap();
1041 cache.insert("key1".to_string(), "value1".to_string());
1042 cache.insert("key2".to_string(), "value2".to_string());
1043
1044 // Test for_each_value
1045 cache.for_each_value(|value| {
1046 *value = format!("{}_updated", value);
1047 });
1048
1049 assert_eq!(
1050 cache.get(&"key1".to_string()),
1051 Some("value1_updated".to_string())
1052 );
1053 assert_eq!(
1054 cache.get(&"key2".to_string()),
1055 Some("value2_updated".to_string())
1056 );
1057
1058 // Test for_each_entry
1059 cache.for_each_entry(|(key, value)| {
1060 if key == "key1" {
1061 *value = format!("{}_special", value);
1062 }
1063 });
1064
1065 assert_eq!(
1066 cache.get(&"key1".to_string()),
1067 Some("value1_updated_special".to_string())
1068 );
1069 assert_eq!(
1070 cache.get(&"key2".to_string()),
1071 Some("value2_updated".to_string())
1072 );
1073 }
1074
1075 #[test]
1076 fn test_retain() {
1077 let cache = SyncSieveCache::new(10).unwrap();
1078
1079 // Add some entries
1080 cache.insert("even1".to_string(), 2);
1081 cache.insert("even2".to_string(), 4);
1082 cache.insert("odd1".to_string(), 1);
1083 cache.insert("odd2".to_string(), 3);
1084
1085 assert_eq!(cache.len(), 4);
1086
1087 // Keep only entries with even values
1088 cache.retain(|_, v| v % 2 == 0);
1089
1090 assert_eq!(cache.len(), 2);
1091 assert!(cache.contains_key(&"even1".to_string()));
1092 assert!(cache.contains_key(&"even2".to_string()));
1093 assert!(!cache.contains_key(&"odd1".to_string()));
1094 assert!(!cache.contains_key(&"odd2".to_string()));
1095
1096 // Keep only entries with keys containing '1'
1097 cache.retain(|k, _| k.contains('1'));
1098
1099 assert_eq!(cache.len(), 1);
1100 assert!(cache.contains_key(&"even1".to_string()));
1101 assert!(!cache.contains_key(&"even2".to_string()));
1102 }
1103}