344 lines
11 KiB
Rust
344 lines
11 KiB
Rust
#![deny(unsafe_code)]
|
||
|
||
//! Caching handle into the [ArcSwapAny].
|
||
//!
|
||
//! The [Cache] keeps a copy of the internal [Arc] for faster access.
|
||
//!
|
||
//! [Arc]: std::sync::Arc
|
||
|
||
use std::ops::Deref;
|
||
use std::sync::atomic::Ordering;
|
||
|
||
use super::ref_cnt::RefCnt;
|
||
use super::strategy::Strategy;
|
||
use super::ArcSwapAny;
|
||
|
||
/// Generalization of caches providing access to `T`.
|
||
///
|
||
/// This abstracts over all kinds of caches that can provide a cheap access to values of type `T`.
|
||
/// This is useful in cases where some code doesn't care if the `T` is the whole structure or just
|
||
/// a part of it.
|
||
///
|
||
/// See the example at [`Cache::map`].
|
||
pub trait Access<T> {
|
||
/// Loads the value from cache.
|
||
///
|
||
/// This revalidates the value in the cache, then provides the access to the cached value.
|
||
fn load(&mut self) -> &T;
|
||
}
|
||
|
||
/// Caching handle for [`ArcSwapAny`][ArcSwapAny].
|
||
///
|
||
/// Instead of loading the [`Arc`][Arc] on every request from the shared storage, this keeps
|
||
/// another copy inside itself. Upon request it only cheaply revalidates it is up to
|
||
/// date. If it is, access is significantly faster. If it is stale, the [load_full] is done and the
|
||
/// cache value is replaced. Under a read-heavy loads, the measured speedup are 10-25 times,
|
||
/// depending on the architecture.
|
||
///
|
||
/// There are, however, downsides:
|
||
///
|
||
/// * The handle needs to be kept around by the caller (usually, one per thread). This is fine if
|
||
/// there's one global `ArcSwapAny`, but starts being tricky with eg. data structures build from
|
||
/// them.
|
||
/// * As it keeps a copy of the [Arc] inside the cache, the old value may be kept alive for longer
|
||
/// period of time ‒ it is replaced by the new value on [load][Cache::load]. You may not want to
|
||
/// use this if dropping the old value in timely manner is important (possibly because of
|
||
/// releasing large amount of RAM or because of closing file handles).
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```rust
|
||
/// # fn do_something<V>(_v: V) { }
|
||
/// use std::sync::Arc;
|
||
/// use std::sync::atomic::{AtomicBool, Ordering};
|
||
///
|
||
/// use arc_swap::{ArcSwap, Cache};
|
||
///
|
||
/// let shared = Arc::new(ArcSwap::from_pointee(42));
|
||
/// # let mut threads = Vec::new();
|
||
/// let terminate = Arc::new(AtomicBool::new(false));
|
||
/// // Start 10 worker threads...
|
||
/// for _ in 0..10 {
|
||
/// let mut cache = Cache::new(Arc::clone(&shared));
|
||
/// let terminate = Arc::clone(&terminate);
|
||
/// # let thread =
|
||
/// std::thread::spawn(move || {
|
||
/// // Keep loading it like mad..
|
||
/// while !terminate.load(Ordering::Relaxed) {
|
||
/// let value = cache.load();
|
||
/// do_something(value);
|
||
/// }
|
||
/// });
|
||
/// # threads.push(thread);
|
||
/// }
|
||
/// shared.store(Arc::new(12));
|
||
/// # terminate.store(true, Ordering::Relaxed);
|
||
/// # for thread in threads { thread.join().unwrap() }
|
||
/// ```
|
||
///
|
||
/// Another one with using a thread local storage and explicit types:
|
||
///
|
||
/// ```rust
|
||
/// # use std::sync::Arc;
|
||
/// # use std::ops::Deref;
|
||
/// # use std::cell::RefCell;
|
||
/// #
|
||
/// # use arc_swap::ArcSwap;
|
||
/// # use arc_swap::cache::Cache;
|
||
/// # use once_cell::sync::Lazy;
|
||
/// #
|
||
/// # #[derive(Debug, Default)]
|
||
/// # struct Config;
|
||
/// #
|
||
/// static CURRENT_CONFIG: Lazy<ArcSwap<Config>> = Lazy::new(|| ArcSwap::from_pointee(Config::default()));
|
||
///
|
||
/// thread_local! {
|
||
/// static CACHE: RefCell<Cache<&'static ArcSwap<Config>, Arc<Config>>> = RefCell::new(Cache::from(CURRENT_CONFIG.deref()));
|
||
/// }
|
||
///
|
||
/// CACHE.with(|c| {
|
||
/// // * RefCell needed, because load on cache is `&mut`.
|
||
/// // * You want to operate inside the `with` ‒ cloning the Arc is comparably expensive as
|
||
/// // ArcSwap::load itself and whatever you'd save by the cache would be lost on that.
|
||
/// println!("{:?}", c.borrow_mut().load());
|
||
/// });
|
||
/// ```
|
||
///
|
||
/// [Arc]: std::sync::Arc
|
||
/// [load_full]: ArcSwapAny::load_full
|
||
#[derive(Clone, Debug)]
|
||
pub struct Cache<A, T> {
|
||
arc_swap: A,
|
||
cached: T,
|
||
}
|
||
|
||
impl<A, T, S> Cache<A, T>
|
||
where
|
||
A: Deref<Target = ArcSwapAny<T, S>>,
|
||
T: RefCnt,
|
||
S: Strategy<T>,
|
||
{
|
||
/// Creates a new caching handle.
|
||
///
|
||
/// The parameter is something dereferencing into an [`ArcSwapAny`] (eg. either to [`ArcSwap`]
|
||
/// or [`ArcSwapOption`]). That can be [`ArcSwapAny`] itself, but that's not very useful. But
|
||
/// it also can be a reference to it or `Arc`, which makes it possible to share the
|
||
/// [`ArcSwapAny`] with multiple caches or access it in non-cached way too.
|
||
///
|
||
/// [`ArcSwapOption`]: crate::ArcSwapOption
|
||
/// [`ArcSwap`]: crate::ArcSwap
|
||
pub fn new(arc_swap: A) -> Self {
|
||
let cached = arc_swap.load_full();
|
||
Self { arc_swap, cached }
|
||
}
|
||
|
||
/// Gives access to the (possibly shared) cached [`ArcSwapAny`].
|
||
pub fn arc_swap(&self) -> &A::Target {
|
||
&self.arc_swap
|
||
}
|
||
|
||
/// Loads the currently held value.
|
||
///
|
||
/// This first checks if the cached value is up to date. This check is very cheap.
|
||
///
|
||
/// If it is up to date, the cached value is simply returned without additional costs. If it is
|
||
/// outdated, a load is done on the underlying shared storage. The newly loaded value is then
|
||
/// stored in the cache and returned.
|
||
#[inline]
|
||
pub fn load(&mut self) -> &T {
|
||
self.revalidate();
|
||
self.load_no_revalidate()
|
||
}
|
||
|
||
#[inline]
|
||
fn load_no_revalidate(&self) -> &T {
|
||
&self.cached
|
||
}
|
||
|
||
#[inline]
|
||
fn revalidate(&mut self) {
|
||
let cached_ptr = RefCnt::as_ptr(&self.cached);
|
||
// Node: Relaxed here is fine. We do not synchronize any data through this, we already have
|
||
// it synchronized in self.cache. We just want to check if it changed, if it did, the
|
||
// load_full will be responsible for any synchronization needed.
|
||
let shared_ptr = self.arc_swap.ptr.load(Ordering::Relaxed);
|
||
if cached_ptr != shared_ptr {
|
||
self.cached = self.arc_swap.load_full();
|
||
}
|
||
}
|
||
|
||
/// Turns this cache into a cache with a projection inside the cached value.
|
||
///
|
||
/// You'd use this in case when some part of code needs access to fresh values of `U`, however
|
||
/// a bigger structure containing `U` is provided by this cache. The possibility of giving the
|
||
/// whole structure to the part of the code falls short in terms of reusability (the part of
|
||
/// the code could be used within multiple contexts, each with a bigger different structure
|
||
/// containing `U`) and code separation (the code shouldn't needs to know about the big
|
||
/// structure).
|
||
///
|
||
/// # Warning
|
||
///
|
||
/// As the provided `f` is called inside every [`load`][Access::load], this one should be
|
||
/// cheap. Most often it is expected to be just a closure taking reference of some inner field.
|
||
///
|
||
/// For the same reasons, it should not have side effects and should never panic (these will
|
||
/// not break Rust's safety rules, but might produce behaviour you don't expect).
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```rust
|
||
/// use arc_swap::ArcSwap;
|
||
/// use arc_swap::cache::{Access, Cache};
|
||
///
|
||
/// struct InnerCfg {
|
||
/// answer: usize,
|
||
/// }
|
||
///
|
||
/// struct FullCfg {
|
||
/// inner: InnerCfg,
|
||
/// }
|
||
///
|
||
/// fn use_inner<A: Access<InnerCfg>>(cache: &mut A) {
|
||
/// let value = cache.load();
|
||
/// println!("The answer is: {}", value.answer);
|
||
/// }
|
||
///
|
||
/// let full_cfg = ArcSwap::from_pointee(FullCfg {
|
||
/// inner: InnerCfg {
|
||
/// answer: 42,
|
||
/// }
|
||
/// });
|
||
/// let cache = Cache::new(&full_cfg);
|
||
/// use_inner(&mut cache.map(|full| &full.inner));
|
||
///
|
||
/// let inner_cfg = ArcSwap::from_pointee(InnerCfg { answer: 24 });
|
||
/// let mut inner_cache = Cache::new(&inner_cfg);
|
||
/// use_inner(&mut inner_cache);
|
||
/// ```
|
||
pub fn map<F, U>(self, f: F) -> MapCache<A, T, F>
|
||
where
|
||
F: FnMut(&T) -> &U,
|
||
{
|
||
MapCache {
|
||
inner: self,
|
||
projection: f,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl<A, T, S> Access<T::Target> for Cache<A, T>
|
||
where
|
||
A: Deref<Target = ArcSwapAny<T, S>>,
|
||
T: Deref<Target = <T as RefCnt>::Base> + RefCnt,
|
||
S: Strategy<T>,
|
||
{
|
||
fn load(&mut self) -> &T::Target {
|
||
self.load().deref()
|
||
}
|
||
}
|
||
|
||
impl<A, T, S> From<A> for Cache<A, T>
|
||
where
|
||
A: Deref<Target = ArcSwapAny<T, S>>,
|
||
T: RefCnt,
|
||
S: Strategy<T>,
|
||
{
|
||
fn from(arc_swap: A) -> Self {
|
||
Self::new(arc_swap)
|
||
}
|
||
}
|
||
|
||
/// An implementation of a cache with a projection into the accessed value.
|
||
///
|
||
/// This is the implementation structure for [`Cache::map`]. It can't be created directly and it
|
||
/// should be used through the [`Access`] trait.
|
||
#[derive(Clone, Debug)]
|
||
pub struct MapCache<A, T, F> {
|
||
inner: Cache<A, T>,
|
||
projection: F,
|
||
}
|
||
|
||
impl<A, T, S, F, U> Access<U> for MapCache<A, T, F>
|
||
where
|
||
A: Deref<Target = ArcSwapAny<T, S>>,
|
||
T: RefCnt,
|
||
S: Strategy<T>,
|
||
F: FnMut(&T) -> &U,
|
||
{
|
||
fn load(&mut self) -> &U {
|
||
(self.projection)(self.inner.load())
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use std::sync::Arc;
|
||
|
||
use super::*;
|
||
use crate::{ArcSwap, ArcSwapOption};
|
||
|
||
#[test]
|
||
fn cached_value() {
|
||
let a = ArcSwap::from_pointee(42);
|
||
let mut c1 = Cache::new(&a);
|
||
let mut c2 = Cache::new(&a);
|
||
|
||
assert_eq!(42, **c1.load());
|
||
assert_eq!(42, **c2.load());
|
||
|
||
a.store(Arc::new(43));
|
||
assert_eq!(42, **c1.load_no_revalidate());
|
||
assert_eq!(43, **c1.load());
|
||
}
|
||
|
||
#[test]
|
||
fn cached_through_arc() {
|
||
let a = Arc::new(ArcSwap::from_pointee(42));
|
||
let mut c = Cache::new(Arc::clone(&a));
|
||
assert_eq!(42, **c.load());
|
||
a.store(Arc::new(0));
|
||
drop(a); // A is just one handle, the ArcSwap is kept alive by the cache.
|
||
}
|
||
|
||
#[test]
|
||
fn cache_option() {
|
||
let a = ArcSwapOption::from_pointee(42);
|
||
let mut c = Cache::new(&a);
|
||
|
||
assert_eq!(42, **c.load().as_ref().unwrap());
|
||
a.store(None);
|
||
assert!(c.load().is_none());
|
||
}
|
||
|
||
struct Inner {
|
||
answer: usize,
|
||
}
|
||
|
||
struct Outer {
|
||
inner: Inner,
|
||
}
|
||
|
||
#[test]
|
||
fn map_cache() {
|
||
let a = ArcSwap::from_pointee(Outer {
|
||
inner: Inner { answer: 42 },
|
||
});
|
||
|
||
let mut cache = Cache::new(&a);
|
||
let mut inner = cache.clone().map(|outer| &outer.inner);
|
||
let mut answer = cache.clone().map(|outer| &outer.inner.answer);
|
||
|
||
assert_eq!(42, cache.load().inner.answer);
|
||
assert_eq!(42, inner.load().answer);
|
||
assert_eq!(42, *answer.load());
|
||
|
||
a.store(Arc::new(Outer {
|
||
inner: Inner { answer: 24 },
|
||
}));
|
||
|
||
assert_eq!(24, cache.load().inner.answer);
|
||
assert_eq!(24, inner.load().answer);
|
||
assert_eq!(24, *answer.load());
|
||
}
|
||
}
|