xref: /aosp_15_r20/external/crosvm/cros_async/src/sync/spin.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2020 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::cell::UnsafeCell;
6 use std::hint;
7 use std::ops::Deref;
8 use std::ops::DerefMut;
9 use std::sync::atomic::AtomicBool;
10 use std::sync::atomic::Ordering;
11 
12 const UNLOCKED: bool = false;
13 const LOCKED: bool = true;
14 
15 /// A primitive that provides safe, mutable access to a shared resource.
16 ///
17 /// Unlike `Mutex`, a `SpinLock` will not voluntarily yield its CPU time until the resource is
18 /// available and will instead keep spinning until the resource is acquired. For the vast majority
19 /// of cases, `Mutex` is a better choice than `SpinLock`. If a `SpinLock` must be used then users
20 /// should try to do as little work as possible while holding the `SpinLock` and avoid any sort of
21 /// blocking at all costs as it can severely penalize performance.
22 ///
23 /// # Poisoning
24 ///
25 /// This `SpinLock` does not implement lock poisoning so it is possible for threads to access
26 /// poisoned data if a thread panics while holding the lock. If lock poisoning is needed, it can be
27 /// implemented by wrapping the `SpinLock` in a new type that implements poisoning. See the
28 /// implementation of `std::sync::Mutex` for an example of how to do this.
29 #[repr(align(128))]
30 pub struct SpinLock<T: ?Sized> {
31     lock: AtomicBool,
32     value: UnsafeCell<T>,
33 }
34 
35 impl<T> SpinLock<T> {
36     /// Creates a new, unlocked `SpinLock` that's ready for use.
new(value: T) -> SpinLock<T>37     pub fn new(value: T) -> SpinLock<T> {
38         SpinLock {
39             lock: AtomicBool::new(UNLOCKED),
40             value: UnsafeCell::new(value),
41         }
42     }
43 
44     /// Consumes the `SpinLock` and returns the value guarded by it. This method doesn't perform any
45     /// locking as the compiler guarantees that there are no references to `self`.
into_inner(self) -> T46     pub fn into_inner(self) -> T {
47         // No need to take the lock because the compiler can statically guarantee
48         // that there are no references to the SpinLock.
49         self.value.into_inner()
50     }
51 }
52 
53 impl<T: ?Sized> SpinLock<T> {
54     /// Acquires exclusive, mutable access to the resource protected by the `SpinLock`, blocking the
55     /// current thread until it is able to do so. Upon returning, the current thread will be the
56     /// only thread with access to the resource. The `SpinLock` will be released when the returned
57     /// `SpinLockGuard` is dropped. Attempting to call `lock` while already holding the `SpinLock`
58     /// will cause a deadlock.
lock(&self) -> SpinLockGuard<T>59     pub fn lock(&self) -> SpinLockGuard<T> {
60         loop {
61             let state = self.lock.load(Ordering::Relaxed);
62             if state == UNLOCKED
63                 && self
64                     .lock
65                     .compare_exchange_weak(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed)
66                     .is_ok()
67             {
68                 break;
69             }
70             hint::spin_loop();
71         }
72 
73         // TODO(b/315998194): Add safety comment
74         #[allow(clippy::undocumented_unsafe_blocks)]
75         SpinLockGuard {
76             lock: self,
77             value: unsafe { &mut *self.value.get() },
78         }
79     }
80 
unlock(&self)81     fn unlock(&self) {
82         // Don't need to compare and swap because we exclusively hold the lock.
83         self.lock.store(UNLOCKED, Ordering::Release);
84     }
85 
86     /// Returns a mutable reference to the contained value. This method doesn't perform any locking
87     /// as the compiler will statically guarantee that there are no other references to `self`.
get_mut(&mut self) -> &mut T88     pub fn get_mut(&mut self) -> &mut T {
89         // SAFETY:
90         // Safe because the compiler can statically guarantee that there are no other references to
91         // `self`. This is also why we don't need to acquire the lock.
92         unsafe { &mut *self.value.get() }
93     }
94 }
95 
96 // TODO(b/315998194): Add safety comment
97 #[allow(clippy::undocumented_unsafe_blocks)]
98 unsafe impl<T: ?Sized + Send> Send for SpinLock<T> {}
99 // TODO(b/315998194): Add safety comment
100 #[allow(clippy::undocumented_unsafe_blocks)]
101 unsafe impl<T: ?Sized + Send> Sync for SpinLock<T> {}
102 
103 impl<T: Default> Default for SpinLock<T> {
default() -> Self104     fn default() -> Self {
105         Self::new(Default::default())
106     }
107 }
108 
109 impl<T> From<T> for SpinLock<T> {
from(source: T) -> Self110     fn from(source: T) -> Self {
111         Self::new(source)
112     }
113 }
114 
115 /// An RAII implementation of a "scoped lock" for a `SpinLock`. When this structure is dropped, the
116 /// lock will be released. The resource protected by the `SpinLock` can be accessed via the `Deref`
117 /// and `DerefMut` implementations of this structure.
118 pub struct SpinLockGuard<'a, T: 'a + ?Sized> {
119     lock: &'a SpinLock<T>,
120     value: &'a mut T,
121 }
122 
123 impl<'a, T: ?Sized> Deref for SpinLockGuard<'a, T> {
124     type Target = T;
deref(&self) -> &T125     fn deref(&self) -> &T {
126         self.value
127     }
128 }
129 
130 impl<'a, T: ?Sized> DerefMut for SpinLockGuard<'a, T> {
deref_mut(&mut self) -> &mut T131     fn deref_mut(&mut self) -> &mut T {
132         self.value
133     }
134 }
135 
136 impl<'a, T: ?Sized> Drop for SpinLockGuard<'a, T> {
drop(&mut self)137     fn drop(&mut self) {
138         self.lock.unlock();
139     }
140 }
141 
142 #[cfg(test)]
143 mod test {
144     use std::mem;
145     use std::sync::atomic::AtomicUsize;
146     use std::sync::atomic::Ordering;
147     use std::sync::Arc;
148     use std::thread;
149 
150     use super::*;
151 
152     #[derive(PartialEq, Eq, Debug)]
153     struct NonCopy(u32);
154 
155     #[test]
it_works()156     fn it_works() {
157         let sl = SpinLock::new(NonCopy(13));
158 
159         assert_eq!(*sl.lock(), NonCopy(13));
160     }
161 
162     #[test]
smoke()163     fn smoke() {
164         let sl = SpinLock::new(NonCopy(7));
165 
166         mem::drop(sl.lock());
167         mem::drop(sl.lock());
168     }
169 
170     #[test]
send()171     fn send() {
172         let sl = SpinLock::new(NonCopy(19));
173 
174         thread::spawn(move || {
175             let value = sl.lock();
176             assert_eq!(*value, NonCopy(19));
177         })
178         .join()
179         .unwrap();
180     }
181 
182     #[test]
high_contention()183     fn high_contention() {
184         const THREADS: usize = 23;
185         const ITERATIONS: usize = 101;
186 
187         let mut threads = Vec::with_capacity(THREADS);
188 
189         let sl = Arc::new(SpinLock::new(0usize));
190         for _ in 0..THREADS {
191             let sl2 = sl.clone();
192             threads.push(thread::spawn(move || {
193                 for _ in 0..ITERATIONS {
194                     *sl2.lock() += 1;
195                 }
196             }));
197         }
198 
199         for t in threads.into_iter() {
200             t.join().unwrap();
201         }
202 
203         assert_eq!(*sl.lock(), THREADS * ITERATIONS);
204     }
205 
206     #[test]
get_mut()207     fn get_mut() {
208         let mut sl = SpinLock::new(NonCopy(13));
209         *sl.get_mut() = NonCopy(17);
210 
211         assert_eq!(sl.into_inner(), NonCopy(17));
212     }
213 
214     #[test]
into_inner()215     fn into_inner() {
216         let sl = SpinLock::new(NonCopy(29));
217         assert_eq!(sl.into_inner(), NonCopy(29));
218     }
219 
220     #[test]
into_inner_drop()221     fn into_inner_drop() {
222         struct NeedsDrop(Arc<AtomicUsize>);
223         impl Drop for NeedsDrop {
224             fn drop(&mut self) {
225                 self.0.fetch_add(1, Ordering::AcqRel);
226             }
227         }
228 
229         let value = Arc::new(AtomicUsize::new(0));
230         let needs_drop = SpinLock::new(NeedsDrop(value.clone()));
231         assert_eq!(value.load(Ordering::Acquire), 0);
232 
233         {
234             let inner = needs_drop.into_inner();
235             assert_eq!(inner.0.load(Ordering::Acquire), 0);
236         }
237 
238         assert_eq!(value.load(Ordering::Acquire), 1);
239     }
240 
241     #[test]
arc_nested()242     fn arc_nested() {
243         // Tests nested sltexes and access to underlying data.
244         let sl = SpinLock::new(1);
245         let arc = Arc::new(SpinLock::new(sl));
246         thread::spawn(move || {
247             let nested = arc.lock();
248             let lock2 = nested.lock();
249             assert_eq!(*lock2, 1);
250         })
251         .join()
252         .unwrap();
253     }
254 
255     #[test]
arc_access_in_unwind()256     fn arc_access_in_unwind() {
257         let arc = Arc::new(SpinLock::new(1));
258         let arc2 = arc.clone();
259         thread::spawn(move || {
260             struct Unwinder {
261                 i: Arc<SpinLock<i32>>,
262             }
263             impl Drop for Unwinder {
264                 fn drop(&mut self) {
265                     *self.i.lock() += 1;
266                 }
267             }
268             let _u = Unwinder { i: arc2 };
269             panic!();
270         })
271         .join()
272         .expect_err("thread did not panic");
273         let lock = arc.lock();
274         assert_eq!(*lock, 2);
275     }
276 
277     #[test]
unsized_value()278     fn unsized_value() {
279         let sltex: &SpinLock<[i32]> = &SpinLock::new([1, 2, 3]);
280         {
281             let b = &mut *sltex.lock();
282             b[0] = 4;
283             b[2] = 5;
284         }
285         let expected: &[i32] = &[4, 2, 5];
286         assert_eq!(&*sltex.lock(), expected);
287     }
288 }
289