feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+308
@@ -0,0 +1,308 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod mutex;
|
||||
#[cfg(feature = "std")]
|
||||
mod std;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use self::mutex::Mutex;
|
||||
|
||||
/// Critical section token.
|
||||
///
|
||||
/// An instance of this type indicates that the current thread is executing code within a critical
|
||||
/// section.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CriticalSection<'cs> {
|
||||
_private: PhantomData<&'cs ()>,
|
||||
|
||||
// Prevent CriticalSection from being Send or Sync
|
||||
// https://github.com/rust-embedded/critical-section/issues/55
|
||||
_not_send_sync: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl<'cs> CriticalSection<'cs> {
|
||||
/// Creates a critical section token.
|
||||
///
|
||||
/// This method is meant to be used to create safe abstractions rather than being directly used
|
||||
/// in applications.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This must only be called when the current thread is in a critical section. The caller must
|
||||
/// ensure that the returned instance will not live beyond the end of the critical section.
|
||||
///
|
||||
/// The caller must use adequate fences to prevent the compiler from moving the
|
||||
/// instructions inside the critical section to the outside of it. Sequentially consistent fences are
|
||||
/// suggested immediately after entry and immediately before exit from the critical section.
|
||||
///
|
||||
/// Note that the lifetime `'cs` of the returned instance is unconstrained. User code must not
|
||||
/// be able to influence the lifetime picked for this type, since that might cause it to be
|
||||
/// inferred to `'static`.
|
||||
#[inline(always)]
|
||||
pub unsafe fn new() -> Self {
|
||||
CriticalSection {
|
||||
_private: PhantomData,
|
||||
_not_send_sync: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "restore-state-none", feature = "restore-state-bool"),
|
||||
all(feature = "restore-state-none", feature = "restore-state-u8"),
|
||||
all(feature = "restore-state-none", feature = "restore-state-u16"),
|
||||
all(feature = "restore-state-none", feature = "restore-state-u32"),
|
||||
all(feature = "restore-state-none", feature = "restore-state-u64"),
|
||||
all(feature = "restore-state-bool", feature = "restore-state-u8"),
|
||||
all(feature = "restore-state-bool", feature = "restore-state-u16"),
|
||||
all(feature = "restore-state-bool", feature = "restore-state-u32"),
|
||||
all(feature = "restore-state-bool", feature = "restore-state-u64"),
|
||||
all(feature = "restore-state-bool", feature = "restore-state-usize"),
|
||||
all(feature = "restore-state-u8", feature = "restore-state-u16"),
|
||||
all(feature = "restore-state-u8", feature = "restore-state-u32"),
|
||||
all(feature = "restore-state-u8", feature = "restore-state-u64"),
|
||||
all(feature = "restore-state-u8", feature = "restore-state-usize"),
|
||||
all(feature = "restore-state-u16", feature = "restore-state-u32"),
|
||||
all(feature = "restore-state-u16", feature = "restore-state-u64"),
|
||||
all(feature = "restore-state-u16", feature = "restore-state-usize"),
|
||||
all(feature = "restore-state-u32", feature = "restore-state-u64"),
|
||||
all(feature = "restore-state-u32", feature = "restore-state-usize"),
|
||||
all(feature = "restore-state-u64", feature = "restore-state-usize"),
|
||||
))]
|
||||
compile_error!("You must set at most one of these Cargo features: restore-state-none, restore-state-bool, restore-state-u8, restore-state-u16, restore-state-u32, restore-state-u64, restore-state-usize");
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "restore-state-bool",
|
||||
feature = "restore-state-u8",
|
||||
feature = "restore-state-u16",
|
||||
feature = "restore-state-u32",
|
||||
feature = "restore-state-u64",
|
||||
feature = "restore-state-usize"
|
||||
)))]
|
||||
type RawRestoreStateInner = ();
|
||||
|
||||
#[cfg(feature = "restore-state-bool")]
|
||||
type RawRestoreStateInner = bool;
|
||||
|
||||
#[cfg(feature = "restore-state-u8")]
|
||||
type RawRestoreStateInner = u8;
|
||||
|
||||
#[cfg(feature = "restore-state-u16")]
|
||||
type RawRestoreStateInner = u16;
|
||||
|
||||
#[cfg(feature = "restore-state-u32")]
|
||||
type RawRestoreStateInner = u32;
|
||||
|
||||
#[cfg(feature = "restore-state-u64")]
|
||||
type RawRestoreStateInner = u64;
|
||||
|
||||
#[cfg(feature = "restore-state-usize")]
|
||||
type RawRestoreStateInner = usize;
|
||||
|
||||
// We have RawRestoreStateInner and RawRestoreState so that we don't have to copypaste the docs 5 times.
|
||||
// In the docs this shows as `pub type RawRestoreState = u8` or whatever the selected type is, because
|
||||
// the "inner" type alias is private.
|
||||
|
||||
/// Raw, transparent "restore state".
|
||||
///
|
||||
/// This type changes based on which Cargo feature is selected, out of
|
||||
/// - `restore-state-none` (default, makes the type be `()`)
|
||||
/// - `restore-state-bool`
|
||||
/// - `restore-state-u8`
|
||||
/// - `restore-state-u16`
|
||||
/// - `restore-state-u32`
|
||||
/// - `restore-state-u64`
|
||||
/// - `restore-state-usize`
|
||||
///
|
||||
/// See [`RestoreState`].
|
||||
///
|
||||
/// User code uses [`RestoreState`] opaquely, critical section implementations
|
||||
/// use [`RawRestoreState`] so that they can use the inner value.
|
||||
pub type RawRestoreState = RawRestoreStateInner;
|
||||
|
||||
/// Opaque "restore state".
|
||||
///
|
||||
/// Implementations use this to "carry over" information between acquiring and releasing
|
||||
/// a critical section. For example, when nesting two critical sections of an
|
||||
/// implementation that disables interrupts globally, acquiring the inner one won't disable
|
||||
/// the interrupts since they're already disabled. The impl would use the restore state to "tell"
|
||||
/// the corresponding release that it does *not* have to reenable interrupts yet, only the
|
||||
/// outer release should do so.
|
||||
///
|
||||
/// User code uses [`RestoreState`] opaquely, critical section implementations
|
||||
/// use [`RawRestoreState`] so that they can use the inner value.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RestoreState(RawRestoreState);
|
||||
|
||||
impl RestoreState {
|
||||
/// Create an invalid, dummy `RestoreState`.
|
||||
///
|
||||
/// This can be useful to avoid `Option` when storing a `RestoreState` in a
|
||||
/// struct field, or a `static`.
|
||||
///
|
||||
/// Note that due to the safety contract of [`acquire`]/[`release`], you must not pass
|
||||
/// a `RestoreState` obtained from this method to [`release`].
|
||||
pub const fn invalid() -> Self {
|
||||
#[cfg(not(any(
|
||||
feature = "restore-state-bool",
|
||||
feature = "restore-state-u8",
|
||||
feature = "restore-state-u16",
|
||||
feature = "restore-state-u32",
|
||||
feature = "restore-state-u64",
|
||||
feature = "restore-state-usize"
|
||||
)))]
|
||||
return Self(());
|
||||
|
||||
#[cfg(feature = "restore-state-bool")]
|
||||
return Self(false);
|
||||
|
||||
#[cfg(feature = "restore-state-u8")]
|
||||
return Self(0);
|
||||
|
||||
#[cfg(feature = "restore-state-u16")]
|
||||
return Self(0);
|
||||
|
||||
#[cfg(feature = "restore-state-u32")]
|
||||
return Self(0);
|
||||
|
||||
#[cfg(feature = "restore-state-u64")]
|
||||
return Self(0);
|
||||
|
||||
#[cfg(feature = "restore-state-usize")]
|
||||
return Self(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Acquire a critical section in the current thread.
|
||||
///
|
||||
/// This function is extremely low level. Strongly prefer using [`with`] instead.
|
||||
///
|
||||
/// Nesting critical sections is allowed. The inner critical sections
|
||||
/// are mostly no-ops since they're already protected by the outer one.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - Each `acquire` call must be paired with exactly one `release` call in the same thread.
|
||||
/// - `acquire` returns a "restore state" that you must pass to the corresponding `release` call.
|
||||
/// - `acquire`/`release` pairs must be "properly nested", ie it's not OK to do `a=acquire(); b=acquire(); release(a); release(b);`.
|
||||
/// - It is UB to call `release` if the critical section is not acquired in the current thread.
|
||||
/// - It is UB to call `release` with a "restore state" that does not come from the corresponding `acquire` call.
|
||||
/// - It must provide ordering guarantees at least equivalent to a [`core::sync::atomic::Ordering::Acquire`]
|
||||
/// on a memory location shared by all critical sections, on which the `release` call will do a
|
||||
/// [`core::sync::atomic::Ordering::Release`] operation.
|
||||
#[inline(always)]
|
||||
pub unsafe fn acquire() -> RestoreState {
|
||||
extern "Rust" {
|
||||
fn _critical_section_1_0_acquire() -> RawRestoreState;
|
||||
}
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
RestoreState(_critical_section_1_0_acquire())
|
||||
}
|
||||
|
||||
/// Release the critical section.
|
||||
///
|
||||
/// This function is extremely low level. Strongly prefer using [`with`] instead.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See [`acquire`] for the safety contract description.
|
||||
#[inline(always)]
|
||||
pub unsafe fn release(restore_state: RestoreState) {
|
||||
extern "Rust" {
|
||||
fn _critical_section_1_0_release(restore_state: RawRestoreState);
|
||||
}
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
_critical_section_1_0_release(restore_state.0)
|
||||
}
|
||||
|
||||
/// Execute closure `f` in a critical section.
|
||||
///
|
||||
/// Nesting critical sections is allowed. The inner critical sections
|
||||
/// are mostly no-ops since they're already protected by the outer one.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the given closure `f` panics. In this case
|
||||
/// the critical section is released before unwinding.
|
||||
#[inline]
|
||||
pub fn with<R>(f: impl FnOnce(CriticalSection) -> R) -> R {
|
||||
// Helper for making sure `release` is called even if `f` panics.
|
||||
struct Guard {
|
||||
state: RestoreState,
|
||||
}
|
||||
|
||||
impl Drop for Guard {
|
||||
#[inline(always)]
|
||||
fn drop(&mut self) {
|
||||
unsafe { release(self.state) }
|
||||
}
|
||||
}
|
||||
|
||||
let state = unsafe { acquire() };
|
||||
let _guard = Guard { state };
|
||||
|
||||
unsafe { f(CriticalSection::new()) }
|
||||
}
|
||||
|
||||
/// Methods required for a critical section implementation.
|
||||
///
|
||||
/// This trait is not intended to be used except when implementing a critical section.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Implementations must uphold the contract specified in [`crate::acquire`] and [`crate::release`].
|
||||
pub unsafe trait Impl {
|
||||
/// Acquire the critical section.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Callers must uphold the contract specified in [`crate::acquire`] and [`crate::release`].
|
||||
unsafe fn acquire() -> RawRestoreState;
|
||||
|
||||
/// Release the critical section.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Callers must uphold the contract specified in [`crate::acquire`] and [`crate::release`].
|
||||
unsafe fn release(restore_state: RawRestoreState);
|
||||
}
|
||||
|
||||
/// Set the critical section implementation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(not(feature = "std"))] // needed for `cargo test --features std`
|
||||
/// # mod no_std {
|
||||
/// use critical_section::RawRestoreState;
|
||||
///
|
||||
/// struct MyCriticalSection;
|
||||
/// critical_section::set_impl!(MyCriticalSection);
|
||||
///
|
||||
/// unsafe impl critical_section::Impl for MyCriticalSection {
|
||||
/// unsafe fn acquire() -> RawRestoreState {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// unsafe fn release(restore_state: RawRestoreState) {
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
#[macro_export]
|
||||
macro_rules! set_impl {
|
||||
($t: ty) => {
|
||||
#[no_mangle]
|
||||
unsafe fn _critical_section_1_0_acquire() -> $crate::RawRestoreState {
|
||||
<$t as $crate::Impl>::acquire()
|
||||
}
|
||||
#[no_mangle]
|
||||
unsafe fn _critical_section_1_0_release(restore_state: $crate::RawRestoreState) {
|
||||
<$t as $crate::Impl>::release(restore_state)
|
||||
}
|
||||
};
|
||||
}
|
||||
Vendored
+208
@@ -0,0 +1,208 @@
|
||||
use super::CriticalSection;
|
||||
use core::cell::{Ref, RefCell, RefMut, UnsafeCell};
|
||||
|
||||
/// A mutex based on critical sections.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use critical_section::Mutex;
|
||||
/// # use std::cell::Cell;
|
||||
///
|
||||
/// static FOO: Mutex<Cell<i32>> = Mutex::new(Cell::new(42));
|
||||
///
|
||||
/// fn main() {
|
||||
/// critical_section::with(|cs| {
|
||||
/// FOO.borrow(cs).set(43);
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// fn interrupt_handler() {
|
||||
/// let _x = critical_section::with(|cs| FOO.borrow(cs).get());
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Design
|
||||
///
|
||||
/// [`std::sync::Mutex`] has two purposes. It converts types that are [`Send`]
|
||||
/// but not [`Sync`] into types that are both; and it provides
|
||||
/// [interior mutability]. `critical_section::Mutex`, on the other hand, only adds
|
||||
/// `Sync`. It does *not* provide interior mutability.
|
||||
///
|
||||
/// This was a conscious design choice. It is possible to create multiple
|
||||
/// [`CriticalSection`] tokens, either by nesting critical sections or `Copy`ing
|
||||
/// an existing token. As a result, it would not be sound for [`Mutex::borrow`]
|
||||
/// to return `&mut T`, because there would be nothing to prevent calling
|
||||
/// `borrow` multiple times to create aliased `&mut T` references.
|
||||
///
|
||||
/// The solution is to include a runtime check to ensure that each resource is
|
||||
/// borrowed only once. This is what `std::sync::Mutex` does. However, this is
|
||||
/// a runtime cost that may not be required in all circumstances. For instance,
|
||||
/// `Mutex<Cell<T>>` never needs to create `&mut T` or equivalent.
|
||||
///
|
||||
/// If `&mut T` is needed, the simplest solution is to use `Mutex<RefCell<T>>`,
|
||||
/// which is the closest analogy to `std::sync::Mutex`. [`RefCell`] inserts the
|
||||
/// exact runtime check necessary to guarantee that the `&mut T` reference is
|
||||
/// unique.
|
||||
///
|
||||
/// To reduce verbosity when using `Mutex<RefCell<T>>`, we reimplement some of
|
||||
/// `RefCell`'s methods on it directly.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use critical_section::Mutex;
|
||||
/// # use std::cell::RefCell;
|
||||
///
|
||||
/// static FOO: Mutex<RefCell<i32>> = Mutex::new(RefCell::new(42));
|
||||
///
|
||||
/// fn main() {
|
||||
/// critical_section::with(|cs| {
|
||||
/// // Instead of calling this
|
||||
/// let _ = FOO.borrow(cs).take();
|
||||
/// // Call this
|
||||
/// let _ = FOO.take(cs);
|
||||
/// // `RefCell::borrow` and `RefCell::borrow_mut` are renamed to
|
||||
/// // `borrow_ref` and `borrow_ref_mut` to avoid name collisions
|
||||
/// let _: &mut i32 = &mut *FOO.borrow_ref_mut(cs);
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`std::sync::Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
|
||||
/// [interior mutability]: https://doc.rust-lang.org/reference/interior-mutability.html
|
||||
#[derive(Debug)]
|
||||
pub struct Mutex<T> {
|
||||
// The `UnsafeCell` is not strictly necessary here: In theory, just using `T` should
|
||||
// be fine.
|
||||
// However, without `UnsafeCell`, the compiler may use niches inside `T`, and may
|
||||
// read the niche value _without locking the mutex_. As we don't provide interior
|
||||
// mutability, this is still not violating any aliasing rules and should be perfectly
|
||||
// fine. But as the cost of adding `UnsafeCell` is very small, we add it out of
|
||||
// cautiousness, just in case the reason `T` is not `Sync` in the first place is
|
||||
// something very obscure we didn't consider.
|
||||
inner: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
impl<T> Mutex<T> {
|
||||
/// Creates a new mutex.
|
||||
#[inline]
|
||||
pub const fn new(value: T) -> Self {
|
||||
Mutex {
|
||||
inner: UnsafeCell::new(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the contained value when the mutex is already uniquely borrowed.
|
||||
///
|
||||
/// This does not require locking or a critical section since it takes `&mut self`, which
|
||||
/// guarantees unique ownership already. Care must be taken when using this method to
|
||||
/// **unsafely** access `static mut` variables, appropriate fences must be used to prevent
|
||||
/// unwanted optimizations.
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.inner.get() }
|
||||
}
|
||||
|
||||
/// Unwraps the contained value, consuming the mutex.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.into_inner()
|
||||
}
|
||||
|
||||
/// Borrows the data for the duration of the critical section.
|
||||
#[inline]
|
||||
pub fn borrow<'cs>(&'cs self, _cs: CriticalSection<'cs>) -> &'cs T {
|
||||
unsafe { &*self.inner.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Mutex<RefCell<T>> {
|
||||
/// Borrow the data and call [`RefCell::replace`]
|
||||
///
|
||||
/// This is equivalent to `self.borrow(cs).replace(t)`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This call could panic. See the documentation for [`RefCell::replace`]
|
||||
/// for more details.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn replace<'cs>(&'cs self, cs: CriticalSection<'cs>, t: T) -> T {
|
||||
self.borrow(cs).replace(t)
|
||||
}
|
||||
|
||||
/// Borrow the data and call [`RefCell::replace_with`]
|
||||
///
|
||||
/// This is equivalent to `self.borrow(cs).replace_with(f)`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This call could panic. See the documentation for
|
||||
/// [`RefCell::replace_with`] for more details.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn replace_with<'cs, F>(&'cs self, cs: CriticalSection<'cs>, f: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut T) -> T,
|
||||
{
|
||||
self.borrow(cs).replace_with(f)
|
||||
}
|
||||
|
||||
/// Borrow the data and call [`RefCell::borrow`]
|
||||
///
|
||||
/// This is equivalent to `self.borrow(cs).borrow()`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This call could panic. See the documentation for [`RefCell::borrow`]
|
||||
/// for more details.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn borrow_ref<'cs>(&'cs self, cs: CriticalSection<'cs>) -> Ref<'cs, T> {
|
||||
self.borrow(cs).borrow()
|
||||
}
|
||||
|
||||
/// Borrow the data and call [`RefCell::borrow_mut`]
|
||||
///
|
||||
/// This is equivalent to `self.borrow(cs).borrow_mut()`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This call could panic. See the documentation for [`RefCell::borrow_mut`]
|
||||
/// for more details.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn borrow_ref_mut<'cs>(&'cs self, cs: CriticalSection<'cs>) -> RefMut<'cs, T> {
|
||||
self.borrow(cs).borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Mutex<RefCell<T>> {
|
||||
/// Borrow the data and call [`RefCell::take`]
|
||||
///
|
||||
/// This is equivalent to `self.borrow(cs).take()`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This call could panic. See the documentation for [`RefCell::take`]
|
||||
/// for more details.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn take<'cs>(&'cs self, cs: CriticalSection<'cs>) -> T {
|
||||
self.borrow(cs).take()
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE A `Mutex` can be used as a channel so the protected data must be `Send`
|
||||
// to prevent sending non-Sendable stuff (e.g. access tokens) across different
|
||||
// threads.
|
||||
unsafe impl<T> Sync for Mutex<T> where T: Send {}
|
||||
|
||||
/// ``` compile_fail
|
||||
/// fn bad(cs: critical_section::CriticalSection) -> &u32 {
|
||||
/// let x = critical_section::Mutex::new(42u32);
|
||||
/// x.borrow(cs)
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(doctest)]
|
||||
const BorrowMustNotOutliveMutexTest: () = ();
|
||||
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
use std::cell::Cell;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
static GLOBAL_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
// This is initialized if a thread has acquired the CS, uninitialized otherwise.
|
||||
static mut GLOBAL_GUARD: MaybeUninit<MutexGuard<'static, ()>> = MaybeUninit::uninit();
|
||||
|
||||
std::thread_local!(static IS_LOCKED: Cell<bool> = Cell::new(false));
|
||||
|
||||
struct StdCriticalSection;
|
||||
crate::set_impl!(StdCriticalSection);
|
||||
|
||||
unsafe impl crate::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.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.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::thread;
|
||||
|
||||
use crate as critical_section;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[test]
|
||||
#[should_panic(expected = "Not a PoisonError!")]
|
||||
fn reusable_after_panic() {
|
||||
let _ = thread::spawn(|| {
|
||||
critical_section::with(|_| {
|
||||
panic!("Boom!");
|
||||
})
|
||||
})
|
||||
.join();
|
||||
|
||||
critical_section::with(|_| {
|
||||
panic!("Not a PoisonError!");
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user