diff options
| author | datdenkikniet <jcdra1@gmail.com> | 2025-03-16 12:46:23 +0100 |
|---|---|---|
| committer | Emil Fresk <emil.fresk@gmail.com> | 2025-03-24 07:36:23 +0000 |
| commit | b5db43550185c2acd62d3f27bc89f2f24b4fbb22 (patch) | |
| tree | a7fe58d0c5a553ef94f8959f02a3d22c20058d87 /rtic-sync/src/loom_cs.rs | |
| parent | d76252d767cb0990b2362c5fb15ac3ee88675f3e (diff) | |
rtic-sync: introduce loom compat layer and apply it to `channel`
Diffstat (limited to 'rtic-sync/src/loom_cs.rs')
| -rw-r--r-- | rtic-sync/src/loom_cs.rs | 69 |
1 files changed, 69 insertions, 0 deletions
diff --git a/rtic-sync/src/loom_cs.rs b/rtic-sync/src/loom_cs.rs new file mode 100644 index 0000000..3291f52 --- /dev/null +++ b/rtic-sync/src/loom_cs.rs @@ -0,0 +1,69 @@ +//! A loom-based implementation of CriticalSection, effectively copied from the critical_section::std module. + +use core::cell::RefCell; +use core::mem::MaybeUninit; + +use loom::cell::Cell; +use loom::sync::{Mutex, MutexGuard}; + +loom::lazy_static! { + static ref GLOBAL_MUTEX: Mutex<()> = Mutex::new(()); + // This is initialized if a thread has acquired the CS, uninitialized otherwise. + static ref GLOBAL_GUARD: RefCell<MaybeUninit<MutexGuard<'static, ()>>> = RefCell::new(MaybeUninit::uninit()); +} + +loom::thread_local!(static IS_LOCKED: Cell<bool> = Cell::new(false)); + +struct StdCriticalSection; +critical_section::set_impl!(StdCriticalSection); + +unsafe impl critical_section::Impl for StdCriticalSection { + unsafe fn acquire() -> bool { + // Allow reentrancy by checking thread local state + IS_LOCKED.with(|l| { + if l.get() { + // CS already acquired in the current thread. + return true; + } + + // Note: it is fine to set this flag *before* acquiring the mutex because it's thread local. + // No other thread can see its value, there's no potential for races. + // This way, we hold the mutex for slightly less time. + l.set(true); + + // Not acquired in the current thread, acquire it. + let guard = match GLOBAL_MUTEX.lock() { + Ok(guard) => guard, + Err(err) => { + // Ignore poison on the global mutex in case a panic occurred + // while the mutex was held. + err.into_inner() + } + }; + GLOBAL_GUARD.borrow_mut().write(guard); + + false + }) + } + + unsafe fn release(nested_cs: bool) { + if !nested_cs { + // SAFETY: As per the acquire/release safety contract, release can only be called + // if the critical section is acquired in the current thread, + // in which case we know the GLOBAL_GUARD is initialized. + // + // We have to `assume_init_read` then drop instead of `assume_init_drop` because: + // - drop requires exclusive access (&mut) to the contents + // - mutex guard drop first unlocks the mutex, then returns. In between those, there's a brief + // moment where the mutex is unlocked but a `&mut` to the contents exists. + // - During this moment, another thread can go and use GLOBAL_GUARD, causing `&mut` aliasing. + #[allow(let_underscore_lock)] + let _ = GLOBAL_GUARD.borrow_mut().assume_init_read(); + + // Note: it is fine to clear this flag *after* releasing the mutex because it's thread local. + // No other thread can see its value, there's no potential for races. + // This way, we hold the mutex for slightly less time. + IS_LOCKED.with(|l| l.set(false)); + } + } +} |
