• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::cell::Cell;
2 use std::mem::MaybeUninit;
3 use std::sync::{Mutex, MutexGuard};
4 
5 static GLOBAL_MUTEX: Mutex<()> = Mutex::new(());
6 
7 // This is initialized if a thread has acquired the CS, uninitialized otherwise.
8 static mut GLOBAL_GUARD: MaybeUninit<MutexGuard<'static, ()>> = MaybeUninit::uninit();
9 
10 std::thread_local!(static IS_LOCKED: Cell<bool> = Cell::new(false));
11 
12 struct StdCriticalSection;
13 crate::set_impl!(StdCriticalSection);
14 
15 unsafe impl crate::Impl for StdCriticalSection {
acquire() -> bool16     unsafe fn acquire() -> bool {
17         // Allow reentrancy by checking thread local state
18         IS_LOCKED.with(|l| {
19             if l.get() {
20                 // CS already acquired in the current thread.
21                 return true;
22             }
23 
24             // Note: it is fine to set this flag *before* acquiring the mutex because it's thread local.
25             // No other thread can see its value, there's no potential for races.
26             // This way, we hold the mutex for slightly less time.
27             l.set(true);
28 
29             // Not acquired in the current thread, acquire it.
30             let guard = match GLOBAL_MUTEX.lock() {
31                 Ok(guard) => guard,
32                 Err(err) => {
33                     // Ignore poison on the global mutex in case a panic occurred
34                     // while the mutex was held.
35                     err.into_inner()
36                 }
37             };
38             GLOBAL_GUARD.write(guard);
39 
40             false
41         })
42     }
43 
release(nested_cs: bool)44     unsafe fn release(nested_cs: bool) {
45         if !nested_cs {
46             // SAFETY: As per the acquire/release safety contract, release can only be called
47             // if the critical section is acquired in the current thread,
48             // in which case we know the GLOBAL_GUARD is initialized.
49             //
50             // We have to `assume_init_read` then drop instead of `assume_init_drop` because:
51             // - drop requires exclusive access (&mut) to the contents
52             // - mutex guard drop first unlocks the mutex, then returns. In between those, there's a brief
53             //   moment where the mutex is unlocked but a `&mut` to the contents exists.
54             // - During this moment, another thread can go and use GLOBAL_GUARD, causing `&mut` aliasing.
55             #[allow(let_underscore_lock)]
56             let _ = GLOBAL_GUARD.assume_init_read();
57 
58             // Note: it is fine to clear this flag *after* releasing the mutex because it's thread local.
59             // No other thread can see its value, there's no potential for races.
60             // This way, we hold the mutex for slightly less time.
61             IS_LOCKED.with(|l| l.set(false));
62         }
63     }
64 }
65 
66 #[cfg(test)]
67 mod tests {
68     use std::thread;
69 
70     use crate as critical_section;
71 
72     #[cfg(feature = "std")]
73     #[test]
74     #[should_panic(expected = "Not a PoisonError!")]
reusable_after_panic()75     fn reusable_after_panic() {
76         let _ = thread::spawn(|| {
77             critical_section::with(|_| {
78                 panic!("Boom!");
79             })
80         })
81         .join();
82 
83         critical_section::with(|_| {
84             panic!("Not a PoisonError!");
85         })
86     }
87 }
88