From 21072bee48ff6ec19b79e0d9527ad8cc34a4e9e0 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 22 Aug 2022 21:46:09 +0200 Subject: split `embassy-util` into `embassy-futures`, `embassy-sync`. --- embassy-sync/src/blocking_mutex/mod.rs | 189 +++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 embassy-sync/src/blocking_mutex/mod.rs (limited to 'embassy-sync/src/blocking_mutex/mod.rs') diff --git a/embassy-sync/src/blocking_mutex/mod.rs b/embassy-sync/src/blocking_mutex/mod.rs new file mode 100644 index 00000000..8a4a4c64 --- /dev/null +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -0,0 +1,189 @@ +//! Blocking mutex. +//! +//! This module provides a blocking mutex that can be used to synchronize data. +pub mod raw; + +use core::cell::UnsafeCell; + +use self::raw::RawMutex; + +/// Blocking mutex (not async) +/// +/// Provides a blocking mutual exclusion primitive backed by an implementation of [`raw::RawMutex`]. +/// +/// Which implementation you select depends on the context in which you're using the mutex, and you can choose which kind +/// of interior mutability fits your use case. +/// +/// Use [`CriticalSectionMutex`] when data can be shared between threads and interrupts. +/// +/// Use [`NoopMutex`] when data is only shared between tasks running on the same executor. +/// +/// Use [`ThreadModeMutex`] when data is shared between tasks running on the same executor but you want a global singleton. +/// +/// In all cases, the blocking mutex is intended to be short lived and not held across await points. +/// Use the async [`Mutex`](crate::mutex::Mutex) if you need a lock that is held across await points. +pub struct Mutex { + // NOTE: `raw` must be FIRST, so when using ThreadModeMutex the "can't drop in non-thread-mode" gets + // to run BEFORE dropping `data`. + raw: R, + data: UnsafeCell, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +impl Mutex { + /// Creates a new mutex in an unlocked state ready for use. + #[inline] + pub const fn new(val: T) -> Mutex { + Mutex { + raw: R::INIT, + data: UnsafeCell::new(val), + } + } + + /// Creates a critical section and grants temporary access to the protected data. + pub fn lock(&self, f: impl FnOnce(&T) -> U) -> U { + self.raw.lock(|| { + let ptr = self.data.get() as *const T; + let inner = unsafe { &*ptr }; + f(inner) + }) + } +} + +impl Mutex { + /// Creates a new mutex based on a pre-existing raw mutex. + /// + /// This allows creating a mutex in a constant context on stable Rust. + #[inline] + pub const fn const_new(raw_mutex: R, val: T) -> Mutex { + Mutex { + raw: raw_mutex, + data: UnsafeCell::new(val), + } + } + + /// Consumes this mutex, returning the underlying data. + #[inline] + pub fn into_inner(self) -> T { + self.data.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the `Mutex` mutably, no actual locking needs to + /// take place---the mutable borrow statically guarantees no locks exist. + #[inline] + pub fn get_mut(&mut self) -> &mut T { + unsafe { &mut *self.data.get() } + } +} + +/// A mutex that allows borrowing data across executors and interrupts. +/// +/// # Safety +/// +/// This mutex is safe to share between different executors and interrupts. +pub type CriticalSectionMutex = Mutex; + +/// A mutex that allows borrowing data in the context of a single executor. +/// +/// # Safety +/// +/// **This Mutex is only safe within a single executor.** +pub type NoopMutex = Mutex; + +impl Mutex { + /// Borrows the data for the duration of the critical section + pub fn borrow<'cs>(&'cs self, _cs: critical_section::CriticalSection<'cs>) -> &'cs T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } + } +} + +impl Mutex { + /// Borrows the data + pub fn borrow(&self) -> &T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } + } +} + +// ThreadModeMutex does NOT use the generic mutex from above because it's special: +// it's Send+Sync even if T: !Send. There's no way to do that without specialization (I think?). +// +// There's still a ThreadModeRawMutex for use with the generic Mutex (handy with Channel, for example), +// but that will require T: Send even though it shouldn't be needed. + +#[cfg(any(cortex_m, feature = "std"))] +pub use thread_mode_mutex::*; +#[cfg(any(cortex_m, feature = "std"))] +mod thread_mode_mutex { + use super::*; + + /// A "mutex" that only allows borrowing from thread mode. + /// + /// # Safety + /// + /// **This Mutex is only safe on single-core systems.** + /// + /// On multi-core systems, a `ThreadModeMutex` **is not sufficient** to ensure exclusive access. + pub struct ThreadModeMutex { + inner: UnsafeCell, + } + + // NOTE: ThreadModeMutex only allows borrowing from one execution context ever: thread mode. + // Therefore it cannot be used to send non-sendable stuff between execution contexts, so it can + // be Send+Sync even if T is not Send (unlike CriticalSectionMutex) + unsafe impl Sync for ThreadModeMutex {} + unsafe impl Send for ThreadModeMutex {} + + impl ThreadModeMutex { + /// Creates a new mutex + pub const fn new(value: T) -> Self { + ThreadModeMutex { + inner: UnsafeCell::new(value), + } + } + } + + impl ThreadModeMutex { + /// Lock the `ThreadModeMutex`, granting access to the data. + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn lock(&self, f: impl FnOnce(&T) -> R) -> R { + f(self.borrow()) + } + + /// Borrows the data + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn borrow(&self) -> &T { + assert!( + raw::in_thread_mode(), + "ThreadModeMutex can only be borrowed from thread mode." + ); + unsafe { &*self.inner.get() } + } + } + + impl Drop for ThreadModeMutex { + fn drop(&mut self) { + // Only allow dropping from thread mode. Dropping calls drop on the inner `T`, so + // `drop` needs the same guarantees as `lock`. `ThreadModeMutex` is Send even if + // T isn't, so without this check a user could create a ThreadModeMutex in thread mode, + // send it to interrupt context and drop it there, which would "send" a T even if T is not Send. + assert!( + raw::in_thread_mode(), + "ThreadModeMutex can only be dropped from thread mode." + ); + + // Drop of the inner `T` happens after this. + } + } +} -- cgit v1.2.3