//! Module implementing a thread-safe LRU cache. use std::borrow::Borrow; use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash}; use std::fmt; use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::atomic::{AtomicUsize, Ordering}; use lru_cache::LruCache; /// A thread-safe cache of keys & cached values. /// Actual values stored in the cache are `Arc'`s. /// /// This is a wrapper around `LruCache` that also counts various cache statistics, /// like cache hits or cache misses. pub struct ThreadSafeCache where K: Eq + Hash, S: BuildHasher { inner: Mutex, S>>, // Cache statistics. hits: AtomicUsize, misses: AtomicUsize, } impl ThreadSafeCache { /// Create the cache with given capacity. #[inline] pub fn new(capacity: usize) -> Self { Self::with_hasher(capacity, RandomState::new()) } } impl ThreadSafeCache where K: Eq + Hash, S: BuildHasher { /// Create the cache with custom hasher and given capacity. pub fn with_hasher(capacity: usize, hasher: S) -> Self { ThreadSafeCache{ inner: Mutex::new(LruCache::with_hasher(capacity, hasher)), hits: AtomicUsize::new(0), misses: AtomicUsize::new(0), } } #[doc(hidden)] fn lock(&self) -> MutexGuard, S>> { self.inner.lock().expect("ThreadSafeCache mutex poisoned") } } // LruCache interface wrappers. #[allow(dead_code)] impl ThreadSafeCache { /// Check if the cache contains given key. pub fn contains_key(&self, key: &Q) -> bool where K: Borrow, Q: ?Sized + Eq + Hash { let y = self.lock().contains_key(key); if !y { self.miss(); } y } /// Get the element corresponding to given key if it's present in the cache. pub fn get(&self, key: &Q) -> Option> where K: Borrow, Q: ?Sized + Eq + Hash { match self.lock().get_mut(key) { Some(v) => { self.hit(); Some(v.clone()) } None => { self.miss(); None } } } /// Put an item into cache under given key. /// /// This is like insert(), except it always returns the (`Arc`'d) value /// that's under the cached key. /// If it wasn't there before, it will be the new value just inserted (i.e. `v`). pub fn put(&self, k: K, v: V) -> Arc { let value = Arc::new(v); self.lock().insert(k, value.clone()).unwrap_or_else(|| value) } /// Insert an item into the cache under given key. /// /// If the key is already present in the cache, returns its corresponding value. pub fn insert(&self, k: K, v: V) -> Option> { self.lock().insert(k, Arc::new(v)) } /// Removes a key from the cache, if present, and returns its value. pub fn remove(&self, key: &Q) -> Option> where K: Borrow, Q: ?Sized + Eq + Hash { match self.lock().remove(key) { r @ Some(_) => { self.hit(); r } r @ None => { self.miss(); r } } } /// Cache capacity. pub fn capacity(&self) -> usize { self.lock().capacity() } /// Set the capacity of the cache. /// /// If the new capacity is smaller than current size of the cache, /// elements will be removed from it in the LRU manner. pub fn set_capacity(&self, capacity: usize) { self.lock().set_capacity(capacity); } /// Remove the least recently used element from the cache. pub fn remove_lru(&self) -> Option<(K, Arc)> { self.lock().remove_lru() } /// Current size of the cache. pub fn len(&self) -> usize { self.lock().len() } /// Whether the cache is empty. pub fn is_empty(&self) -> bool { self.lock().is_empty() } /// Remove all elements from the cache. pub fn clear(&self) { self.lock().clear() } } // Incrementing the statistics' counters. impl ThreadSafeCache { /// Increment the number of cache hits. Returns the new total. fn hit(&self) -> usize { let inc = 1; self.hits.fetch_add(inc, Ordering::Relaxed) + inc } /// Increment the number of cache misses. Returns the new total. fn miss(&self) -> usize { let inc = 1; self.misses.fetch_add(inc, Ordering::Relaxed) + inc } } // Getting counter values. impl ThreadSafeCache { /// Returns the number of cache hits. pub fn hits(&self) -> usize { self.hits.load(Ordering::Relaxed) } /// Returns the number of cache misses. pub fn misses(&self) -> usize { self.misses.load(Ordering::Relaxed) } } impl fmt::Debug for ThreadSafeCache { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let mut ds = fmt.debug_struct("ThreadSafeCache"); if let Ok(inner) = self.inner.try_lock() { ds.field("capacity", &inner.capacity()); ds.field("len", &inner.len()); } ds.finish() } }