moka/common/concurrent/
arc.rs

1// This module's source code was written by us, the `moka` developers, referring to
2// the following book and code:
3//
4// - Chapter 6. Building Our Own "Arc" of the Rust Atomics and Locks book.
5//     - Rust Atomics and Locks by Mara Bos (O’Reilly). Copyright 2023 Mara Bos,
6//       ISBN: 978-1-098-11944-7
7//     - https://marabos.nl/atomics/
8// - The `triomphe` crate v0.1.13 and v0.1.11 by Manish Goregaokar (Manishearth)
9//     - MIT or Apache-2.0 License
10//     - https://github.com/Manishearth/triomphe
11// - `std::sync::Arc` in the Rust Standard Library (1.81.0).
12//     -  MIT or Apache-2.0 License
13
14use std::{
15    fmt,
16    hash::{Hash, Hasher},
17    ops::Deref,
18    ptr::NonNull,
19};
20
21#[cfg(not(moka_loom))]
22use std::sync::atomic::{self, AtomicU32};
23
24#[cfg(moka_loom)]
25use loom::sync::atomic::{self, AtomicU32};
26
27/// A thread-safe reference-counting pointer. `MiniArc` is similar to
28/// `std::sync::Arc`, Atomically Reference Counted shared pointer, but with a few
29/// differences:
30///
31/// - Smaller memory overhead:
32///     - `MiniArc` does not support weak references, so it does not need to store a
33///       weak reference count.
34///     - `MiniArc` uses `AtomicU32` for the reference count, while `std::sync::Arc`
35///       uses `AtomicUsize`. On a 64-bit system, `AtomicU32` is half the size of
36///       `AtomicUsize`.
37///         - Note: Depending on the value type `T`, the Rust compiler may add
38///           padding to the internal struct of `MiniArc<T>`, so the actual memory
39///           overhead may vary.
40/// - Smaller code size:
41///     - Only about 100 lines of code.
42///         - This is because `MiniArc` provides only the methods needed for the
43///           `moka` and `mini-moka` crates.
44///     - Smaller code size means less chance of bugs.
45pub(crate) struct MiniArc<T: ?Sized> {
46    ptr: NonNull<ArcData<T>>,
47}
48
49struct ArcData<T: ?Sized> {
50    ref_count: AtomicU32,
51    data: T,
52}
53
54/// A soft limit on the amount of references that may be made to an `MiniArc`.
55///
56/// Going above this limit will abort your program (although not necessarily)
57/// at _exactly_ `MAX_REFCOUNT + 1` references.
58const MAX_REFCOUNT: u32 = (i32::MAX) as u32;
59
60unsafe impl<T: ?Sized + Send + Sync> Send for MiniArc<T> {}
61unsafe impl<T: ?Sized + Send + Sync> Sync for MiniArc<T> {}
62
63impl<T> MiniArc<T> {
64    pub(crate) fn new(data: T) -> MiniArc<T> {
65        MiniArc {
66            ptr: NonNull::from(Box::leak(Box::new(ArcData {
67                ref_count: AtomicU32::new(1),
68                data,
69            }))),
70        }
71    }
72}
73
74impl<T: ?Sized> MiniArc<T> {
75    /// Gets the number of [`MiniArc`] pointers to this allocation
76    pub(crate) fn count(this: &Self) -> u32 {
77        use atomic::Ordering::Acquire;
78
79        this.data().ref_count.load(Acquire)
80    }
81
82    /// Returns `true` if the two `MiniArc`s point to the same allocation in a
83    /// vein similar to [`ptr::eq`].
84    ///
85    /// # Safety
86    ///
87    /// This function is unreliable when `T` is a `dyn Trait`. Currently
88    /// coercing `MiniArc<SomeTime>` to `MiniArc<dyn Trait>` is not possible, so
89    /// this is not a problem in practice. However, if this coercion becomes
90    /// possible in the future, this function may return incorrect results when
91    /// comparing `MiniArc<dyn Trait>` instances.
92    ///
93    /// To fix this, we must rise the minimum supported Rust version (MSRV) to
94    /// 1.76 and use `std::ptr::addr_eq` internally instead of `eq` (`==`).
95    /// `addr_eq` compares the _addresses_ of the pointers for equality,
96    /// ignoring any metadata in fat pointers.
97    ///
98    /// See the following `triomphe` issue for more information:
99    /// https://github.com/Manishearth/triomphe/pull/84
100    ///
101    /// Note that `triomphe` has a feature called `unsize`, which enables the
102    /// coercion by using the `unsize` crate. `MiniArc` does not have such a
103    /// feature, so we are safe for now.
104    #[inline]
105    #[allow(ambiguous_wide_pointer_comparisons)] // Remove this when MSRV is 1.76 or newer.
106    #[allow(clippy::ptr_eq)] // Remove this when MSRV is 1.76 or newer.
107    pub(crate) fn ptr_eq(this: &Self, other: &Self) -> bool {
108        // `addr_eq` requires Rust 1.76 or newer.
109        // ptr::addr_eq(this.ptr.as_ptr(), other.ptr.as_ptr())
110        this.ptr.as_ptr() == other.ptr.as_ptr()
111    }
112
113    #[inline]
114    fn data(&self) -> &ArcData<T> {
115        unsafe { self.ptr.as_ref() }
116    }
117}
118
119impl<T: ?Sized> Deref for MiniArc<T> {
120    type Target = T;
121
122    fn deref(&self) -> &T {
123        &self.data().data
124    }
125}
126
127impl<T: ?Sized> Clone for MiniArc<T> {
128    fn clone(&self) -> Self {
129        use atomic::Ordering::Relaxed;
130
131        if self.data().ref_count.fetch_add(1, Relaxed) > MAX_REFCOUNT {
132            std::process::abort();
133        }
134
135        MiniArc { ptr: self.ptr }
136    }
137}
138
139impl<T: ?Sized> Drop for MiniArc<T> {
140    fn drop(&mut self) {
141        use std::sync::atomic::Ordering::{Acquire, Release};
142
143        if self.data().ref_count.fetch_sub(1, Release) == 1 {
144            atomic::fence(Acquire);
145            unsafe {
146                drop(Box::from_raw(self.ptr.as_ptr()));
147            }
148        }
149    }
150}
151
152impl<T: Default> Default for MiniArc<T> {
153    /// Creates a new `MiniArc<T>`, with the `Default` value for `T`.
154    fn default() -> MiniArc<T> {
155        MiniArc::new(Default::default())
156    }
157}
158
159impl<T: ?Sized + PartialEq> PartialEq for MiniArc<T> {
160    fn eq(&self, other: &MiniArc<T>) -> bool {
161        // TODO: pointer equality is incorrect if `T` is not `Eq`.
162        // See: https://github.com/Manishearth/triomphe/pull/88
163        Self::ptr_eq(self, other) || *(*self) == *(*other)
164    }
165
166    #[allow(clippy::partialeq_ne_impl)]
167    fn ne(&self, other: &MiniArc<T>) -> bool {
168        !Self::ptr_eq(self, other) && *(*self) != *(*other)
169    }
170}
171
172impl<T: ?Sized + Eq> Eq for MiniArc<T> {}
173
174impl<T: ?Sized + fmt::Display> fmt::Display for MiniArc<T> {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        fmt::Display::fmt(&**self, f)
177    }
178}
179
180impl<T: ?Sized + fmt::Debug> fmt::Debug for MiniArc<T> {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        fmt::Debug::fmt(&**self, f)
183    }
184}
185
186impl<T: ?Sized> fmt::Pointer for MiniArc<T> {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        fmt::Pointer::fmt(&self.ptr.as_ptr(), f)
189    }
190}
191
192impl<T: ?Sized + Hash> Hash for MiniArc<T> {
193    fn hash<H: Hasher>(&self, state: &mut H) {
194        (**self).hash(state)
195    }
196}
197
198#[cfg(all(test, not(moka_loom)))]
199mod tests {
200    use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
201
202    use super::*;
203
204    #[test]
205    fn test_drop() {
206        static NUM_DROPS: AtomicUsize = AtomicUsize::new(0);
207
208        struct DetectDrop;
209
210        impl Drop for DetectDrop {
211            fn drop(&mut self) {
212                NUM_DROPS.fetch_add(1, Relaxed);
213            }
214        }
215
216        // Create two MiniArcs sharing an object containing a string
217        // and a DetectDrop, to detect when it is dropped.
218        let x = MiniArc::new(("hello", DetectDrop));
219        let y = x.clone();
220
221        // Send x to another thread, and use it there.
222        let t = std::thread::spawn(move || {
223            assert_eq!(x.0, "hello");
224        });
225
226        // In parallel, y should still be usable here.
227        assert_eq!(y.0, "hello");
228        assert!(MiniArc::count(&y) >= 1);
229
230        // Wait for the thread to finish.
231        t.join().unwrap();
232
233        // One MiniArc, x, should be dropped by now.
234        // We still have y, so the object should not have been dropped yet.
235        assert_eq!(NUM_DROPS.load(Relaxed), 0);
236        assert_eq!(MiniArc::count(&y), 1);
237
238        // Drop the remaining `MiniArc`.
239        drop(y);
240
241        // Now that `y` is dropped too,
242        // the object should have been dropped.
243        assert_eq!(NUM_DROPS.load(Relaxed), 1);
244    }
245
246    #[test]
247    fn test_eq() {
248        let w = MiniArc::new(6502);
249        let x = w.clone();
250        let y = MiniArc::new(6502);
251        let z = MiniArc::new(8086);
252
253        assert_eq!(w, x);
254        assert_eq!(x, w);
255        assert_eq!(w, y);
256        assert_eq!(y, w);
257        assert_ne!(y, z);
258        assert_ne!(z, y);
259    }
260
261    #[test]
262    fn test_partial_eq_bug() {
263        let float = f32::NAN;
264        assert_ne!(float, float);
265        let arc = MiniArc::new(f32::NAN);
266        // TODO: this is a bug.
267        // See: https://github.com/Manishearth/triomphe/pull/88
268        assert_eq!(arc, arc);
269    }
270
271    #[allow(dead_code)]
272    const fn is_partial_eq<T: ?Sized + PartialEq>() {}
273
274    #[allow(dead_code)]
275    const fn is_eq<T: ?Sized + Eq>() {}
276
277    // compile-time check that PartialEq/Eq is correctly derived
278    const _: () = is_partial_eq::<MiniArc<i32>>();
279    const _: () = is_eq::<MiniArc<i32>>();
280}
281
282#[cfg(all(test, moka_loom))]
283mod loom_tests {
284    use super::*;
285
286    #[test]
287    fn test_drop() {
288        use loom::sync::atomic::{AtomicUsize, Ordering::Relaxed};
289
290        struct DetectDrop(loom::sync::Arc<AtomicUsize>);
291
292        impl Drop for DetectDrop {
293            fn drop(&mut self) {
294                self.0.fetch_add(1, Relaxed);
295            }
296        }
297
298        loom::model(move || {
299            let num_drops = loom::sync::Arc::new(AtomicUsize::new(0));
300
301            // Create two MiniArcs sharing an object containing a string
302            // and a DetectDrop, to detect when it is dropped.
303            let x = MiniArc::new(("hello", DetectDrop(loom::sync::Arc::clone(&num_drops))));
304            let y = x.clone();
305
306            // Send x to another thread, and use it there.
307            let t = loom::thread::spawn(move || {
308                assert_eq!(x.0, "hello");
309            });
310
311            // In parallel, y should still be usable here.
312            assert_eq!(y.0, "hello");
313            assert!(MiniArc::count(&y) >= 1);
314
315            // Wait for the thread to finish.
316            t.join().unwrap();
317
318            // One MiniArc, x, should be dropped by now.
319            // We still have y, so the object should not have been dropped yet.
320            assert_eq!(num_drops.load(Relaxed), 0);
321            assert_eq!(MiniArc::count(&y), 1);
322
323            // Drop the remaining `MiniArc`.
324            drop(y);
325
326            // Now that `y` is dropped too,
327            // the object should have been dropped.
328            assert_eq!(num_drops.load(Relaxed), 1);
329        });
330    }
331}