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}