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}