/rust/registry/src/index.crates.io-1949cf8c6b5b557f/spin-0.12.0/src/mutex/spin.rs
Line | Count | Source |
1 | | //! A naïve spinning mutex. |
2 | | //! |
3 | | //! Waiting threads hammer an atomic variable until it becomes available. Best-case latency is low, but worst-case |
4 | | //! latency is theoretically infinite. |
5 | | |
6 | | use crate::{ |
7 | | atomic::{AtomicBool, Ordering}, |
8 | | RelaxStrategy, Spin, |
9 | | }; |
10 | | use core::{ |
11 | | cell::UnsafeCell, |
12 | | fmt, |
13 | | marker::PhantomData, |
14 | | mem::ManuallyDrop, |
15 | | ops::{Deref, DerefMut}, |
16 | | }; |
17 | | |
18 | | /// A [spin lock](https://en.m.wikipedia.org/wiki/Spinlock) providing mutually exclusive access to data. |
19 | | /// |
20 | | /// # Example |
21 | | /// |
22 | | /// ``` |
23 | | /// use spin; |
24 | | /// |
25 | | /// let lock = spin::mutex::SpinMutex::<_>::new(0); |
26 | | /// |
27 | | /// // Modify the data |
28 | | /// *lock.lock() = 2; |
29 | | /// |
30 | | /// // Read the data |
31 | | /// let answer = *lock.lock(); |
32 | | /// assert_eq!(answer, 2); |
33 | | /// ``` |
34 | | /// |
35 | | /// # Thread safety example |
36 | | /// |
37 | | /// ``` |
38 | | /// use spin; |
39 | | /// use std::sync::{Arc, Barrier}; |
40 | | /// |
41 | | /// let thread_count = 1000; |
42 | | /// let spin_mutex = Arc::new(spin::mutex::SpinMutex::<_>::new(0)); |
43 | | /// |
44 | | /// // We use a barrier to ensure the readout happens after all writing |
45 | | /// let barrier = Arc::new(Barrier::new(thread_count + 1)); |
46 | | /// |
47 | | /// # let mut ts = Vec::new(); |
48 | | /// for _ in (0..thread_count) { |
49 | | /// let my_barrier = barrier.clone(); |
50 | | /// let my_lock = spin_mutex.clone(); |
51 | | /// # let t = |
52 | | /// std::thread::spawn(move || { |
53 | | /// let mut guard = my_lock.lock(); |
54 | | /// *guard += 1; |
55 | | /// |
56 | | /// // Release the lock to prevent a deadlock |
57 | | /// drop(guard); |
58 | | /// my_barrier.wait(); |
59 | | /// }); |
60 | | /// # ts.push(t); |
61 | | /// } |
62 | | /// |
63 | | /// barrier.wait(); |
64 | | /// |
65 | | /// let answer = { *spin_mutex.lock() }; |
66 | | /// assert_eq!(answer, thread_count); |
67 | | /// |
68 | | /// # for t in ts { |
69 | | /// # t.join().unwrap(); |
70 | | /// # } |
71 | | /// ``` |
72 | | pub struct SpinMutex<T: ?Sized, R = Spin> { |
73 | | phantom: PhantomData<R>, |
74 | | pub(crate) lock: AtomicBool, |
75 | | data: UnsafeCell<T>, |
76 | | } |
77 | | |
78 | | /// A guard that provides mutable data access. |
79 | | /// |
80 | | /// When the guard falls out of scope it will release the lock. |
81 | | pub struct SpinMutexGuard<'a, T: ?Sized + 'a, R = Spin> { |
82 | | inner: &'a SpinMutex<T, R>, |
83 | | } |
84 | | |
85 | | // SAFETY: Same unsafe impls as `std::sync::Mutex` |
86 | | unsafe impl<T: ?Sized + Send, R> Sync for SpinMutex<T, R> {} |
87 | | unsafe impl<T: ?Sized + Send, R> Send for SpinMutex<T, R> {} |
88 | | |
89 | | // SAFETY: Mutex guards can be thought of as mutable reference to the inner data. In fact, this |
90 | | // would be their ideal representation if it were not for the need for the critical section to end |
91 | | // *after* the reference is no longer live. |
92 | | unsafe impl<T: ?Sized, R> Sync for SpinMutexGuard<'_, T, R> where for<'a> &'a mut T: Sync {} |
93 | | unsafe impl<T: ?Sized, R> Send for SpinMutexGuard<'_, T, R> where for<'a> &'a mut T: Send {} |
94 | | |
95 | | impl<T, R> SpinMutex<T, R> { |
96 | | /// Creates a new [`SpinMutex`] wrapping the supplied data. |
97 | | /// |
98 | | /// # Example |
99 | | /// |
100 | | /// ``` |
101 | | /// use spin::mutex::SpinMutex; |
102 | | /// |
103 | | /// static MUTEX: SpinMutex<()> = SpinMutex::<_>::new(()); |
104 | | /// |
105 | | /// fn demo() { |
106 | | /// let lock = MUTEX.lock(); |
107 | | /// // do something with lock |
108 | | /// drop(lock); |
109 | | /// } |
110 | | /// ``` |
111 | | #[inline(always)] |
112 | | pub const fn new(data: T) -> Self { |
113 | | SpinMutex { |
114 | | lock: AtomicBool::new(false), |
115 | | data: UnsafeCell::new(data), |
116 | | phantom: PhantomData, |
117 | | } |
118 | | } |
119 | | |
120 | | /// Consumes this [`SpinMutex`] and unwraps the underlying data. |
121 | | /// |
122 | | /// # Example |
123 | | /// |
124 | | /// ``` |
125 | | /// let lock = spin::mutex::SpinMutex::<_>::new(42); |
126 | | /// assert_eq!(42, lock.into_inner()); |
127 | | /// ``` |
128 | | #[inline(always)] |
129 | | pub fn into_inner(self) -> T { |
130 | | // We know statically that there are no outstanding references to |
131 | | // `self` so there's no need to lock. |
132 | | let SpinMutex { data, .. } = self; |
133 | | data.into_inner() |
134 | | } |
135 | | |
136 | | /// Returns a mutable pointer to the underlying data. |
137 | | /// |
138 | | /// This is mostly meant to be used for applications which require manual unlocking, but where |
139 | | /// storing both the lock and the pointer to the inner data gets inefficient. |
140 | | /// |
141 | | /// # Example |
142 | | /// ``` |
143 | | /// let lock = spin::mutex::SpinMutex::<_>::new(42); |
144 | | /// |
145 | | /// unsafe { |
146 | | /// core::mem::forget(lock.lock()); |
147 | | /// |
148 | | /// assert_eq!(lock.as_mut_ptr().read(), 42); |
149 | | /// lock.as_mut_ptr().write(58); |
150 | | /// |
151 | | /// lock.force_unlock(); |
152 | | /// } |
153 | | /// |
154 | | /// assert_eq!(*lock.lock(), 58); |
155 | | /// |
156 | | /// ``` |
157 | | #[inline(always)] |
158 | | pub fn as_mut_ptr(&self) -> *mut T { |
159 | | self.data.get() |
160 | | } |
161 | | } |
162 | | |
163 | | impl<T: ?Sized, R: RelaxStrategy> SpinMutex<T, R> { |
164 | | /// Locks the [`SpinMutex`] and returns a guard that permits access to the inner data. |
165 | | /// |
166 | | /// The returned value may be dereferenced for data access |
167 | | /// and the lock will be dropped when the guard falls out of scope. |
168 | | /// |
169 | | /// ``` |
170 | | /// let lock = spin::mutex::SpinMutex::<_>::new(0); |
171 | | /// { |
172 | | /// let mut data = lock.lock(); |
173 | | /// // The lock is now locked and the data can be accessed |
174 | | /// *data += 1; |
175 | | /// // The lock is implicitly dropped at the end of the scope |
176 | | /// } |
177 | | /// ``` |
178 | | #[inline(always)] |
179 | | pub fn lock(&self) -> SpinMutexGuard<'_, T, R> { |
180 | | // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` |
181 | | // when called in a loop. |
182 | | loop { |
183 | | if let Some(guard) = self.try_lock_weak() { |
184 | | break guard; |
185 | | } |
186 | | |
187 | | while self.is_locked() { |
188 | | R::relax(); |
189 | | } |
190 | | } |
191 | | } |
192 | | } |
193 | | |
194 | | impl<T: ?Sized, R> SpinMutex<T, R> { |
195 | | /// Returns `true` if the lock is currently held. |
196 | | /// |
197 | | /// # Safety |
198 | | /// |
199 | | /// This function provides no synchronization guarantees and so its result should be considered 'out of date' |
200 | | /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. |
201 | | #[inline(always)] |
202 | | pub fn is_locked(&self) -> bool { |
203 | | self.lock.load(Ordering::Relaxed) |
204 | | } |
205 | | |
206 | | /// Force unlock this [`SpinMutex`]. |
207 | | /// |
208 | | /// # Safety |
209 | | /// |
210 | | /// This is *extremely* unsafe if the lock is not held by the current |
211 | | /// thread. However, this can be useful in some instances for exposing the |
212 | | /// lock to FFI that doesn't know how to deal with RAII. |
213 | | #[inline(always)] |
214 | | pub unsafe fn force_unlock(&self) { |
215 | | self.lock.store(false, Ordering::Release); |
216 | | } |
217 | | |
218 | | /// Try to lock this [`SpinMutex`], returning a lock guard if successful. |
219 | | /// |
220 | | /// # Example |
221 | | /// |
222 | | /// ``` |
223 | | /// let lock = spin::mutex::SpinMutex::<_>::new(42); |
224 | | /// |
225 | | /// let maybe_guard = lock.try_lock(); |
226 | | /// assert!(maybe_guard.is_some()); |
227 | | /// |
228 | | /// // `maybe_guard` is still held, so the second call fails |
229 | | /// let maybe_guard2 = lock.try_lock(); |
230 | | /// assert!(maybe_guard2.is_none()); |
231 | | /// ``` |
232 | | #[inline(always)] |
233 | | pub fn try_lock(&self) -> Option<SpinMutexGuard<'_, T, R>> { |
234 | | // The reason for using a strong compare_exchange is explained here: |
235 | | // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107 |
236 | | // |
237 | | // See also the giant comment about Ordering::Acquire in try_lock_weak below. |
238 | | if self |
239 | | .lock |
240 | | .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) |
241 | | .is_ok() |
242 | | { |
243 | | Some(SpinMutexGuard { inner: self }) |
244 | | } else { |
245 | | None |
246 | | } |
247 | | } |
248 | | |
249 | | /// Try to lock this [`SpinMutex`], returning a lock guard if successful. |
250 | | /// |
251 | | /// Unlike [`SpinMutex::try_lock`], this function is allowed to spuriously fail even when the mutex is unlocked, |
252 | | /// which can result in more efficient code on some platforms. |
253 | | #[inline(always)] |
254 | | pub fn try_lock_weak(&self) -> Option<SpinMutexGuard<'_, T, R>> { |
255 | | // There's some debate about whether Ordering::Acquire is sufficient here. In one |
256 | | // interpretation of the the C++ standard, a lock operation like this could be re-ordered |
257 | | // with a prior unlock of a different mutex. In other words this... |
258 | | // |
259 | | // let a_guard = A.lock(); |
260 | | // // do stuff... |
261 | | // drop(a_guard); |
262 | | // let b_guard = B.lock(); |
263 | | // // do more stuff... |
264 | | // drop(b_guard); |
265 | | // |
266 | | // ... could be reordered by the compiler into this... |
267 | | // |
268 | | // let a_guard = A.lock(); |
269 | | // let b_guard = B.lock(); |
270 | | // // do stuff... |
271 | | // // do more stuff... |
272 | | // drop(a_guard); |
273 | | // drop(b_guard); |
274 | | // |
275 | | // ...because both the store-release in `drop(a_guard)` and the load-acquire in `B.lock()` |
276 | | // allow this code movement. (Using Ordering::AcqRel here instead would forbid this, |
277 | | // because nothing can move down across a store-release, but it would also prevent valid |
278 | | // optimizations.) The worry is that this could lead to deadlocks in arguably correct |
279 | | // programs, for example one thread locking A-then-B while another thread locks B-then-A, |
280 | | // even though a straight-line reading of the code says that can't happen. |
281 | | // |
282 | | // However, there's another interpretation of the standard that says this reordering is |
283 | | // illegal. The idea is that even though moving a (non-sequentially-consistent) |
284 | | // store-release down across a load-acquire is ok, moving it down across an *unbounded |
285 | | // loop* violates the requirement that atomic stores should be visible to other threads in |
286 | | // a "finite period of time": https://eel.is/c++draft/basic#intro.progress-18. Concretely, |
287 | | // `try_lock` or `try_lock_weak` could be reordered like this (not a problem?), but `lock` |
288 | | // with its internal loop can't be. This seems to be how compilers currently behave, in |
289 | | // any case. See also: |
290 | | // - https://preshing.com/20170612/can-reordering-of-release-acquire-operations-introduce-deadlock |
291 | | // - https://x.com/tvaneerd/status/1258426442649657346 |
292 | | // - https://youtu.be/A8eCGOqgvH4?t=2551 |
293 | | if self |
294 | | .lock |
295 | | .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) |
296 | | .is_ok() |
297 | | { |
298 | | Some(SpinMutexGuard { inner: self }) |
299 | | } else { |
300 | | None |
301 | | } |
302 | | } |
303 | | |
304 | | /// Returns a mutable reference to the underlying data. |
305 | | /// |
306 | | /// Since this call borrows the [`SpinMutex`] mutably, and a mutable reference is guaranteed to be exclusive in |
307 | | /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As |
308 | | /// such, this is a 'zero-cost' operation. |
309 | | /// |
310 | | /// # Example |
311 | | /// |
312 | | /// ``` |
313 | | /// let mut lock = spin::mutex::SpinMutex::<_>::new(0); |
314 | | /// *lock.get_mut() = 10; |
315 | | /// assert_eq!(*lock.lock(), 10); |
316 | | /// ``` |
317 | | #[inline(always)] |
318 | | pub fn get_mut(&mut self) -> &mut T { |
319 | | self.data.get_mut() |
320 | | } |
321 | | } |
322 | | |
323 | | impl<T: ?Sized + fmt::Debug, R> fmt::Debug for SpinMutex<T, R> { |
324 | | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
325 | | match self.try_lock() { |
326 | | Some(guard) => write!(f, "Mutex {{ data: ") |
327 | | .and_then(|()| (*guard).fmt(f)) |
328 | | .and_then(|()| write!(f, " }}")), |
329 | | None => write!(f, "Mutex {{ <locked> }}"), |
330 | | } |
331 | | } |
332 | | } |
333 | | |
334 | | impl<T: Default, R> Default for SpinMutex<T, R> { |
335 | | fn default() -> Self { |
336 | | Self::new(Default::default()) |
337 | | } |
338 | | } |
339 | | |
340 | | impl<T, R> From<T> for SpinMutex<T, R> { |
341 | | fn from(data: T) -> Self { |
342 | | Self::new(data) |
343 | | } |
344 | | } |
345 | | |
346 | | impl<'a, T: ?Sized, R> SpinMutexGuard<'a, T, R> { |
347 | | /// Leak the lock guard, yielding a mutable reference to the underlying data. |
348 | | /// |
349 | | /// Note that this function will permanently lock the original [`SpinMutex`]. |
350 | | /// |
351 | | /// ``` |
352 | | /// let mylock = spin::mutex::SpinMutex::<_>::new(0); |
353 | | /// |
354 | | /// let data: &mut i32 = spin::mutex::SpinMutexGuard::leak(mylock.lock()); |
355 | | /// |
356 | | /// *data = 1; |
357 | | /// assert_eq!(*data, 1); |
358 | | /// ``` |
359 | | #[inline(always)] |
360 | | pub fn leak(this: Self) -> &'a mut T { |
361 | | // Use ManuallyDrop to avoid stacked-borrow invalidation |
362 | | let this = ManuallyDrop::new(this); |
363 | | // We know statically that only we are referencing data |
364 | | unsafe { &mut *this.inner.data.get() } |
365 | | } |
366 | | } |
367 | | |
368 | | impl<'a, T: ?Sized + fmt::Debug, R> fmt::Debug for SpinMutexGuard<'a, T, R> { |
369 | | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
370 | | fmt::Debug::fmt(&**self, f) |
371 | | } |
372 | | } |
373 | | |
374 | | impl<'a, T: ?Sized + fmt::Display, R> fmt::Display for SpinMutexGuard<'a, T, R> { |
375 | | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
376 | | fmt::Display::fmt(&**self, f) |
377 | | } |
378 | | } |
379 | | |
380 | | impl<'a, T: ?Sized, R> Deref for SpinMutexGuard<'a, T, R> { |
381 | | type Target = T; |
382 | | fn deref(&self) -> &T { |
383 | | // We know statically that only we are referencing data |
384 | | unsafe { &*self.inner.data.get() } |
385 | | } |
386 | | } |
387 | | |
388 | | impl<'a, T: ?Sized, R> DerefMut for SpinMutexGuard<'a, T, R> { |
389 | | fn deref_mut(&mut self) -> &mut T { |
390 | | // We know statically that only we are referencing data |
391 | | unsafe { &mut *self.inner.data.get() } |
392 | | } |
393 | | } |
394 | | |
395 | | impl<'a, T: ?Sized, R> Drop for SpinMutexGuard<'a, T, R> { |
396 | | /// The dropping of the MutexGuard will release the lock it was created from. |
397 | 0 | fn drop(&mut self) { |
398 | 0 | self.inner.lock.store(false, Ordering::Release); |
399 | 0 | } |
400 | | } |
401 | | |
402 | | #[cfg(feature = "lock_api")] |
403 | | unsafe impl<R: RelaxStrategy> lock_api_crate::RawMutex for SpinMutex<(), R> { |
404 | | type GuardMarker = lock_api_crate::GuardSend; |
405 | | |
406 | | const INIT: Self = Self::new(()); |
407 | | |
408 | | fn lock(&self) { |
409 | | // Prevent guard destructor running |
410 | | core::mem::forget(Self::lock(self)); |
411 | | } |
412 | | |
413 | | fn try_lock(&self) -> bool { |
414 | | // Prevent guard destructor running |
415 | | Self::try_lock(self).map(core::mem::forget).is_some() |
416 | | } |
417 | | |
418 | | unsafe fn unlock(&self) { |
419 | | self.force_unlock(); |
420 | | } |
421 | | |
422 | | fn is_locked(&self) -> bool { |
423 | | Self::is_locked(self) |
424 | | } |
425 | | } |
426 | | |
427 | | #[cfg(test)] |
428 | | mod tests { |
429 | | use std::prelude::v1::*; |
430 | | |
431 | | use std::sync::atomic::{AtomicUsize, Ordering}; |
432 | | use std::sync::mpsc::channel; |
433 | | use std::sync::Arc; |
434 | | use std::thread; |
435 | | |
436 | | type SpinMutex<T> = super::SpinMutex<T>; |
437 | | |
438 | | #[derive(Eq, PartialEq, Debug)] |
439 | | struct NonCopy(i32); |
440 | | |
441 | | #[test] |
442 | | fn smoke() { |
443 | | let m = SpinMutex::<_>::new(()); |
444 | | drop(m.lock()); |
445 | | drop(m.lock()); |
446 | | } |
447 | | |
448 | | #[test] |
449 | | fn lots_and_lots() { |
450 | | static M: SpinMutex<()> = SpinMutex::<_>::new(()); |
451 | | static mut CNT: u32 = 0; |
452 | | const J: u32 = 1000; |
453 | | const K: u32 = 3; |
454 | | |
455 | | fn inc() { |
456 | | for _ in 0..J { |
457 | | unsafe { |
458 | | let _g = M.lock(); |
459 | | CNT += 1; |
460 | | } |
461 | | } |
462 | | } |
463 | | |
464 | | let (tx, rx) = channel(); |
465 | | let mut ts = Vec::new(); |
466 | | for _ in 0..K { |
467 | | let tx2 = tx.clone(); |
468 | | ts.push(thread::spawn(move || { |
469 | | inc(); |
470 | | tx2.send(()).unwrap(); |
471 | | })); |
472 | | let tx2 = tx.clone(); |
473 | | ts.push(thread::spawn(move || { |
474 | | inc(); |
475 | | tx2.send(()).unwrap(); |
476 | | })); |
477 | | } |
478 | | |
479 | | drop(tx); |
480 | | for _ in 0..2 * K { |
481 | | rx.recv().unwrap(); |
482 | | } |
483 | | assert_eq!(unsafe { CNT }, J * K * 2); |
484 | | |
485 | | for t in ts { |
486 | | t.join().unwrap(); |
487 | | } |
488 | | } |
489 | | |
490 | | #[test] |
491 | | fn try_lock() { |
492 | | let mutex = SpinMutex::<_>::new(42); |
493 | | |
494 | | // First lock succeeds |
495 | | let a = mutex.try_lock(); |
496 | | assert_eq!(a.as_ref().map(|r| **r), Some(42)); |
497 | | |
498 | | // Additional lock fails |
499 | | let b = mutex.try_lock(); |
500 | | assert!(b.is_none()); |
501 | | |
502 | | // After dropping lock, it succeeds again |
503 | | ::core::mem::drop(a); |
504 | | let c = mutex.try_lock(); |
505 | | assert_eq!(c.as_ref().map(|r| **r), Some(42)); |
506 | | } |
507 | | |
508 | | #[test] |
509 | | fn test_into_inner() { |
510 | | let m = SpinMutex::<_>::new(NonCopy(10)); |
511 | | assert_eq!(m.into_inner(), NonCopy(10)); |
512 | | } |
513 | | |
514 | | #[test] |
515 | | fn test_into_inner_drop() { |
516 | | struct Foo(Arc<AtomicUsize>); |
517 | | impl Drop for Foo { |
518 | | fn drop(&mut self) { |
519 | | self.0.fetch_add(1, Ordering::SeqCst); |
520 | | } |
521 | | } |
522 | | let num_drops = Arc::new(AtomicUsize::new(0)); |
523 | | let m = SpinMutex::<_>::new(Foo(num_drops.clone())); |
524 | | assert_eq!(num_drops.load(Ordering::SeqCst), 0); |
525 | | { |
526 | | let _inner = m.into_inner(); |
527 | | assert_eq!(num_drops.load(Ordering::SeqCst), 0); |
528 | | } |
529 | | assert_eq!(num_drops.load(Ordering::SeqCst), 1); |
530 | | } |
531 | | |
532 | | #[test] |
533 | | fn test_mutex_arc_nested() { |
534 | | // Tests nested mutexes and access |
535 | | // to underlying data. |
536 | | let arc = Arc::new(SpinMutex::<_>::new(1)); |
537 | | let arc2 = Arc::new(SpinMutex::<_>::new(arc)); |
538 | | let (tx, rx) = channel(); |
539 | | let t = thread::spawn(move || { |
540 | | let lock = arc2.lock(); |
541 | | let lock2 = lock.lock(); |
542 | | assert_eq!(*lock2, 1); |
543 | | tx.send(()).unwrap(); |
544 | | }); |
545 | | rx.recv().unwrap(); |
546 | | t.join().unwrap(); |
547 | | } |
548 | | |
549 | | #[test] |
550 | | fn test_mutex_arc_access_in_unwind() { |
551 | | let arc = Arc::new(SpinMutex::<_>::new(1)); |
552 | | let arc2 = arc.clone(); |
553 | | let _ = thread::spawn(move || -> () { |
554 | | struct Unwinder { |
555 | | i: Arc<SpinMutex<i32>>, |
556 | | } |
557 | | impl Drop for Unwinder { |
558 | | fn drop(&mut self) { |
559 | | *self.i.lock() += 1; |
560 | | } |
561 | | } |
562 | | let _u = Unwinder { i: arc2 }; |
563 | | panic!(); |
564 | | }) |
565 | | .join(); |
566 | | let lock = arc.lock(); |
567 | | assert_eq!(*lock, 2); |
568 | | } |
569 | | |
570 | | #[test] |
571 | | fn test_mutex_unsized() { |
572 | | let mutex: &SpinMutex<[i32]> = &SpinMutex::<_>::new([1, 2, 3]); |
573 | | { |
574 | | let b = &mut *mutex.lock(); |
575 | | b[0] = 4; |
576 | | b[2] = 5; |
577 | | } |
578 | | let comp: &[i32] = &[4, 2, 5]; |
579 | | assert_eq!(&*mutex.lock(), comp); |
580 | | } |
581 | | |
582 | | #[test] |
583 | | fn test_mutex_force_lock() { |
584 | | let lock = SpinMutex::<_>::new(()); |
585 | | ::std::mem::forget(lock.lock()); |
586 | | unsafe { |
587 | | lock.force_unlock(); |
588 | | } |
589 | | assert!(lock.try_lock().is_some()); |
590 | | } |
591 | | } |