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