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 when MSRV is 1.76 or newer.
106    pub(crate) fn ptr_eq(this: &Self, other: &Self) -> bool {
107        // `addr_eq` requires Rust 1.76 or newer.
108        // ptr::addr_eq(this.ptr.as_ptr(), other.ptr.as_ptr())
109        this.ptr.as_ptr() == other.ptr.as_ptr()
110    }
111
112    #[inline]
113    fn data(&self) -> &ArcData<T> {
114        unsafe { self.ptr.as_ref() }
115    }
116}
117
118impl<T: ?Sized> Deref for MiniArc<T> {
119    type Target = T;
120
121    fn deref(&self) -> &T {
122        &self.data().data
123    }
124}
125
126impl<T: ?Sized> Clone for MiniArc<T> {
127    fn clone(&self) -> Self {
128        use atomic::Ordering::Relaxed;
129
130        if self.data().ref_count.fetch_add(1, Relaxed) > MAX_REFCOUNT {
131            std::process::abort();
132        }
133
134        MiniArc { ptr: self.ptr }
135    }
136}
137
138impl<T: ?Sized> Drop for MiniArc<T> {
139    fn drop(&mut self) {
140        use std::sync::atomic::Ordering::{Acquire, Release};
141
142        if self.data().ref_count.fetch_sub(1, Release) == 1 {
143            atomic::fence(Acquire);
144            unsafe {
145                drop(Box::from_raw(self.ptr.as_ptr()));
146            }
147        }
148    }
149}
150
151impl<T: Default> Default for MiniArc<T> {
152    /// Creates a new `MiniArc<T>`, with the `Default` value for `T`.
153    fn default() -> MiniArc<T> {
154        MiniArc::new(Default::default())
155    }
156}
157
158impl<T: ?Sized + PartialEq> PartialEq for MiniArc<T> {
159    fn eq(&self, other: &MiniArc<T>) -> bool {
160        // TODO: pointer equality is incorrect if `T` is not `Eq`.
161        // See: https://github.com/Manishearth/triomphe/pull/88
162        Self::ptr_eq(self, other) || *(*self) == *(*other)
163    }
164
165    #[allow(clippy::partialeq_ne_impl)]
166    fn ne(&self, other: &MiniArc<T>) -> bool {
167        !Self::ptr_eq(self, other) && *(*self) != *(*other)
168    }
169}
170
171impl<T: ?Sized + Eq> Eq for MiniArc<T> {}
172
173impl<T: ?Sized + fmt::Display> fmt::Display for MiniArc<T> {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        fmt::Display::fmt(&**self, f)
176    }
177}
178
179impl<T: ?Sized + fmt::Debug> fmt::Debug for MiniArc<T> {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        fmt::Debug::fmt(&**self, f)
182    }
183}
184
185impl<T: ?Sized> fmt::Pointer for MiniArc<T> {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        fmt::Pointer::fmt(&self.ptr.as_ptr(), f)
188    }
189}
190
191impl<T: ?Sized + Hash> Hash for MiniArc<T> {
192    fn hash<H: Hasher>(&self, state: &mut H) {
193        (**self).hash(state)
194    }
195}
196
197#[cfg(all(test, not(moka_loom)))]
198mod tests {
199    use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
200
201    use super::*;
202
203    #[test]
204    fn test_drop() {
205        static NUM_DROPS: AtomicUsize = AtomicUsize::new(0);
206
207        struct DetectDrop;
208
209        impl Drop for DetectDrop {
210            fn drop(&mut self) {
211                NUM_DROPS.fetch_add(1, Relaxed);
212            }
213        }
214
215        // Create two MiniArcs sharing an object containing a string
216        // and a DetectDrop, to detect when it is dropped.
217        let x = MiniArc::new(("hello", DetectDrop));
218        let y = x.clone();
219
220        // Send x to another thread, and use it there.
221        let t = std::thread::spawn(move || {
222            assert_eq!(x.0, "hello");
223        });
224
225        // In parallel, y should still be usable here.
226        assert_eq!(y.0, "hello");
227        assert!(MiniArc::count(&y) >= 1);
228
229        // Wait for the thread to finish.
230        t.join().unwrap();
231
232        // One MiniArc, x, should be dropped by now.
233        // We still have y, so the object should not have been dropped yet.
234        assert_eq!(NUM_DROPS.load(Relaxed), 0);
235        assert_eq!(MiniArc::count(&y), 1);
236
237        // Drop the remaining `MiniArc`.
238        drop(y);
239
240        // Now that `y` is dropped too,
241        // the object should have been dropped.
242        assert_eq!(NUM_DROPS.load(Relaxed), 1);
243    }
244
245    #[test]
246    fn test_eq() {
247        let w = MiniArc::new(6502);
248        let x = w.clone();
249        let y = MiniArc::new(6502);
250        let z = MiniArc::new(8086);
251
252        assert_eq!(w, x);
253        assert_eq!(x, w);
254        assert_eq!(w, y);
255        assert_eq!(y, w);
256        assert_ne!(y, z);
257        assert_ne!(z, y);
258    }
259
260    #[test]
261    fn test_partial_eq_bug() {
262        let float = f32::NAN;
263        assert_ne!(float, float);
264        let arc = MiniArc::new(f32::NAN);
265        // TODO: this is a bug.
266        // See: https://github.com/Manishearth/triomphe/pull/88
267        assert_eq!(arc, arc);
268    }
269
270    #[allow(dead_code)]
271    const fn is_partial_eq<T: ?Sized + PartialEq>() {}
272
273    #[allow(dead_code)]
274    const fn is_eq<T: ?Sized + Eq>() {}
275
276    // compile-time check that PartialEq/Eq is correctly derived
277    const _: () = is_partial_eq::<MiniArc<i32>>();
278    const _: () = is_eq::<MiniArc<i32>>();
279}
280
281#[cfg(all(test, moka_loom))]
282mod loom_tests {
283    use super::*;
284
285    #[test]
286    fn test_drop() {
287        use loom::sync::atomic::{AtomicUsize, Ordering::Relaxed};
288
289        struct DetectDrop(loom::sync::Arc<AtomicUsize>);
290
291        impl Drop for DetectDrop {
292            fn drop(&mut self) {
293                self.0.fetch_add(1, Relaxed);
294            }
295        }
296
297        loom::model(move || {
298            let num_drops = loom::sync::Arc::new(AtomicUsize::new(0));
299
300            // Create two MiniArcs sharing an object containing a string
301            // and a DetectDrop, to detect when it is dropped.
302            let x = MiniArc::new(("hello", DetectDrop(loom::sync::Arc::clone(&num_drops))));
303            let y = x.clone();
304
305            // Send x to another thread, and use it there.
306            let t = loom::thread::spawn(move || {
307                assert_eq!(x.0, "hello");
308            });
309
310            // In parallel, y should still be usable here.
311            assert_eq!(y.0, "hello");
312            assert!(MiniArc::count(&y) >= 1);
313
314            // Wait for the thread to finish.
315            t.join().unwrap();
316
317            // One MiniArc, x, should be dropped by now.
318            // We still have y, so the object should not have been dropped yet.
319            assert_eq!(num_drops.load(Relaxed), 0);
320            assert_eq!(MiniArc::count(&y), 1);
321
322            // Drop the remaining `MiniArc`.
323            drop(y);
324
325            // Now that `y` is dropped too,
326            // the object should have been dropped.
327            assert_eq!(num_drops.load(Relaxed), 1);
328        });
329    }
330}