Skip to main content

hazarc/
cache.rs

1//! Cache wrapper to optimize atomic loads of `Arc`-like pointers.
2
3use core::ops::Deref;
4
5use crate::{
6    arc::{ArcPtr, NonNullArcPtr},
7    atomic::{AtomicArcPtr, AtomicOptionArcPtr},
8    domain::Domain,
9    write_policy::WritePolicy,
10};
11
12/// A type that dereferences to an [`AtomicArc`](AtomicArcPtr).
13pub trait AtomicArcRef {
14    /// The concrete Arc pointer used by the `AtomicArc`.
15    type Arc;
16    /// The owned Arc pointer loaded from the `AtomicArc`.
17    ///
18    /// It can be `Option<Self::Arc>`.
19    type Owned;
20    /// The value returned by [`AtomicArc::load_cached`](AtomicArcPtr::load_cached).
21    type LoadCached<'a>
22    where
23        Self::Arc: 'a;
24    /// Load an owned Arc pointer.
25    ///
26    /// See [`AtomicArc::load_owned`](AtomicArcPtr::load_owned).
27    fn load_owned(&self) -> Self::Owned;
28    /// Load an Arc pointer using a given cached value, updating it if necessary.
29    ///
30    /// See [`AtomicArc::load_cached`](AtomicArcPtr::load_cached).
31    fn load_cached<'a>(&self, cached: &'a mut Self::Owned) -> Self::LoadCached<'a>;
32}
33
34impl<A: ArcPtr, D: Domain, W: WritePolicy> AtomicArcRef for AtomicArcPtr<A, D, W> {
35    type Arc = A;
36    type Owned = A;
37    type LoadCached<'a>
38        = &'a A
39    where
40        Self::Arc: 'a;
41    #[inline]
42    fn load_owned(&self) -> Self::Owned {
43        self.load_owned()
44    }
45    #[inline(always)]
46    fn load_cached<'a>(&self, cached: &'a mut Self::Owned) -> Self::LoadCached<'a> {
47        self.load_cached(cached)
48    }
49}
50
51impl<A: NonNullArcPtr, D: Domain, W: WritePolicy> AtomicArcRef for AtomicOptionArcPtr<A, D, W> {
52    type Arc = A;
53    type Owned = Option<A>;
54    type LoadCached<'a>
55        = Option<&'a A>
56    where
57        Self::Arc: 'a;
58    #[inline]
59    fn load_owned(&self) -> Self::Owned {
60        self.load_owned()
61    }
62    #[inline(always)]
63    fn load_cached<'a>(&self, cached: &'a mut Self::Owned) -> Self::LoadCached<'a> {
64        self.load_cached(cached)
65    }
66}
67
68impl<T: Deref> AtomicArcRef for T
69where
70    T::Target: AtomicArcRef,
71{
72    type Arc = <T::Target as AtomicArcRef>::Arc;
73    type Owned = <T::Target as AtomicArcRef>::Owned;
74    type LoadCached<'a>
75        = <T::Target as AtomicArcRef>::LoadCached<'a>
76    where
77        Self::Arc: 'a;
78    #[inline]
79    fn load_owned(&self) -> Self::Owned {
80        (**self).load_owned()
81    }
82    #[inline]
83    fn load_cached<'a>(&self, cached: &'a mut Self::Owned) -> Self::LoadCached<'a> {
84        (**self).load_cached(cached)
85    }
86}
87
88/// A cache for a shared [`AtomicArc`](AtomicArcPtr).
89///
90/// Built as a wrapper around [`AtomicArc::load_cached`](AtomicArcPtr::load_cached),
91/// it essentially makes loads of up-to-date `Arc`s free, but requires a mutable reference.
92///
93/// As the cache stores the latest loaded `Arc`, it can delay its reclamation until a new `Arc`
94/// is loaded.
95///
96/// # Examples
97///
98/// ```rust
99/// # use std::sync::Arc;
100/// # hazarc::domain!(Domain(8));
101/// # type AtomicArc<T> = hazarc::AtomicArc<T, Domain>;
102/// let atomic_arc = Arc::new(AtomicArc::<usize>::from(0));
103/// let mut cache = hazarc::Cache::new(atomic_arc);
104/// assert_eq!(**cache.load(), 0);
105/// ```
106///
107/// It also works with [`AtomicOptionArc`](AtomicOptionArcPtr).
108///
109/// ```rust
110/// # use std::sync::Arc;
111/// # hazarc::domain!(Domain(8));
112/// # type AtomicOptionArc<T> = hazarc::AtomicOptionArc<T, Domain>;
113/// let atomic_arc = Arc::new(AtomicOptionArc::<usize>::none());
114/// let mut cache = hazarc::Cache::new(atomic_arc);
115/// assert_eq!(cache.load(), None);
116/// ```
117#[derive(Debug, Clone)]
118pub struct Cache<A: AtomicArcRef> {
119    inner: A,
120    cached: A::Owned,
121}
122
123impl<A: AtomicArcRef> Cache<A> {
124    /// Constructs a new `Cache`, loading and storing the up-to-date `Arc`.
125    #[inline]
126    pub fn new(inner: A) -> Self {
127        let cached = inner.load_owned();
128        Self { inner, cached }
129    }
130
131    /// Accesses the inner shared `AtomicArc`.
132    pub fn inner(&self) -> &A {
133        &self.inner
134    }
135
136    /// Consumes the cache to returns the inner shared `AtomicArc`.
137    pub fn into_inner(self) -> A {
138        self.inner
139    }
140
141    /// Returns the cached `Arc` if it is up-to-date, or loads and caches the latest `Arc`.
142    #[inline]
143    pub fn load(&mut self) -> A::LoadCached<'_> {
144        self.inner.load_cached(&mut self.cached)
145    }
146}
147
148impl<A: AtomicArcRef> From<A> for Cache<A> {
149    fn from(value: A) -> Self {
150        Self::new(value)
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use alloc::sync::Arc;
157
158    use crate::{domain, AtomicArc, AtomicOptionArc, Cache};
159
160    #[test]
161    fn cache() {
162        domain!(TestDomain(1));
163        let atomic_arc = Arc::new(AtomicArc::<usize, TestDomain>::from(0));
164        let mut cache = Cache::new(atomic_arc);
165        assert_eq!(**cache.load(), 0);
166        cache.inner().store(1.into());
167        assert_eq!(**cache.load(), 1);
168        assert_eq!(*cache.cached, 1);
169    }
170
171    #[test]
172    fn cache_option() {
173        domain!(TestDomain(1));
174        let atomic_arc = Arc::new(AtomicOptionArc::<usize, TestDomain>::from(0));
175        let mut cache = Cache::new(atomic_arc);
176        assert_eq!(**cache.load().unwrap(), 0);
177        cache.inner().store(None);
178        assert!(cache.load().is_none());
179        assert_eq!(cache.cached, None);
180    }
181}