embassy/embassy-sync/src/mutex.rs

388 lines
10 KiB
Rust
Raw Normal View History

//! Async mutex.
//!
//! This module provides a mutex that can be used to synchronize data between asynchronous tasks.
use core::cell::UnsafeCell;
use core::future::poll_fn;
2022-04-05 23:23:42 +00:00
use core::ops::{Deref, DerefMut};
use core::task::Poll;
use core::{fmt, mem};
2022-06-12 20:15:44 +00:00
2024-07-21 13:57:21 +00:00
use scoped_mutex::{BlockingMutex, ConstScopedRawMutex};
2022-04-05 23:23:42 +00:00
use crate::waitqueue::WakerRegistration;
/// Error returned by [`Mutex::try_lock`]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TryLockError;
struct State {
locked: bool,
waker: WakerRegistration,
}
/// Async mutex.
///
/// The mutex is generic over a blocking [`RawMutex`](crate::blocking_mutex::raw::RawMutex).
/// The raw mutex is used to guard access to the internal "is locked" flag. It
/// is held for very short periods only, while locking and unlocking. It is *not* held
/// for the entire time the async Mutex is locked.
///
/// Which implementation you select depends on the context in which you're using the mutex.
///
/// Use [`CriticalSectionRawMutex`](crate::blocking_mutex::raw::CriticalSectionRawMutex) when data can be shared between threads and interrupts.
///
/// Use [`NoopRawMutex`](crate::blocking_mutex::raw::NoopRawMutex) when data is only shared between tasks running on the same executor.
///
/// Use [`ThreadModeRawMutex`](crate::blocking_mutex::raw::ThreadModeRawMutex) when data is shared between tasks running on the same executor but you want a singleton.
///
2022-04-05 23:23:42 +00:00
pub struct Mutex<M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2022-04-05 23:23:42 +00:00
T: ?Sized,
{
state: BlockingMutex<M, State>,
2022-04-05 23:23:42 +00:00
inner: UnsafeCell<T>,
}
2024-07-21 13:57:21 +00:00
unsafe impl<M: ConstScopedRawMutex + Send, T: ?Sized + Send> Send for Mutex<M, T> {}
unsafe impl<M: ConstScopedRawMutex + Sync, T: ?Sized + Send> Sync for Mutex<M, T> {}
2022-04-05 23:23:42 +00:00
/// Async mutex.
impl<M, T> Mutex<M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2022-04-05 23:23:42 +00:00
{
/// Create a new mutex with the given value.
pub const fn new(value: T) -> Self {
Self {
inner: UnsafeCell::new(value),
state: BlockingMutex::new(State {
2022-04-05 23:23:42 +00:00
locked: false,
waker: WakerRegistration::new(),
}),
2022-04-05 23:23:42 +00:00
}
}
}
impl<M, T> Mutex<M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2022-04-05 23:23:42 +00:00
T: ?Sized,
{
/// Lock the mutex.
///
/// This will wait for the mutex to be unlocked if it's already locked.
pub async fn lock(&self) -> MutexGuard<'_, M, T> {
poll_fn(|cx| {
let ready = self.state.lock(|s| {
if s.locked {
s.waker.register(cx.waker());
false
} else {
s.locked = true;
true
}
});
if ready {
Poll::Ready(MutexGuard { mutex: self })
} else {
Poll::Pending
}
})
.await
}
/// Attempt to immediately lock the mutex.
///
/// If the mutex is already locked, this will return an error instead of waiting.
pub fn try_lock(&self) -> Result<MutexGuard<'_, M, T>, TryLockError> {
self.state.lock(|s| {
if s.locked {
Err(TryLockError)
} else {
s.locked = true;
Ok(())
}
})?;
Ok(MutexGuard { mutex: self })
}
/// Consumes this mutex, returning the underlying data.
pub fn into_inner(self) -> T
2022-09-16 08:44:33 +00:00
where
T: Sized,
{
self.inner.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.
pub fn get_mut(&mut self) -> &mut T {
self.inner.get_mut()
}
2022-04-05 23:23:42 +00:00
}
2024-07-21 13:57:21 +00:00
impl<M: ConstScopedRawMutex, T> From<T> for Mutex<M, T> {
fn from(from: T) -> Self {
Self::new(from)
}
}
impl<M, T> Default for Mutex<M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
T: ?Sized + Default,
{
fn default() -> Self {
Self::new(Default::default())
}
}
impl<M, T> fmt::Debug for Mutex<M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
T: ?Sized + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Mutex");
match self.try_lock() {
Ok(value) => {
d.field("inner", &&*value);
}
Err(TryLockError) => {
d.field("inner", &format_args!("<locked>"));
}
}
d.finish_non_exhaustive()
}
}
2022-04-05 23:23:42 +00:00
/// Async mutex guard.
///
/// Owning an instance of this type indicates having
/// successfully locked the mutex, and grants access to the contents.
///
/// Dropping it unlocks the mutex.
2024-04-14 22:35:59 +00:00
#[clippy::has_significant_drop]
2022-04-05 23:23:42 +00:00
pub struct MutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2022-04-05 23:23:42 +00:00
T: ?Sized,
{
mutex: &'a Mutex<M, T>,
}
2024-04-14 22:35:59 +00:00
impl<'a, M, T> MutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2024-04-14 22:35:59 +00:00
T: ?Sized,
{
/// Returns a locked view over a portion of the locked data.
pub fn map<U>(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedMutexGuard<'a, M, U> {
let mutex = this.mutex;
let value = fun(unsafe { &mut *this.mutex.inner.get() });
// Don't run the `drop` method for MutexGuard. The ownership of the underlying
// locked state is being moved to the returned MappedMutexGuard.
mem::forget(this);
MappedMutexGuard {
state: &mutex.state,
value,
}
}
}
2022-04-05 23:23:42 +00:00
impl<'a, M, T> Drop for MutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2022-04-05 23:23:42 +00:00
T: ?Sized,
{
fn drop(&mut self) {
self.mutex.state.lock(|s| {
s.locked = false;
s.waker.wake();
})
}
}
impl<'a, M, T> Deref for MutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2022-04-05 23:23:42 +00:00
T: ?Sized,
{
type Target = T;
fn deref(&self) -> &Self::Target {
// Safety: the MutexGuard represents exclusive access to the contents
// of the mutex, so it's OK to get it.
unsafe { &*(self.mutex.inner.get() as *const T) }
}
}
impl<'a, M, T> DerefMut for MutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2022-04-05 23:23:42 +00:00
T: ?Sized,
{
fn deref_mut(&mut self) -> &mut Self::Target {
// Safety: the MutexGuard represents exclusive access to the contents
// of the mutex, so it's OK to get it.
unsafe { &mut *(self.mutex.inner.get()) }
}
}
2024-04-14 22:35:59 +00:00
impl<'a, M, T> fmt::Debug for MutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
T: ?Sized + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<'a, M, T> fmt::Display for MutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
T: ?Sized + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
2024-04-14 22:35:59 +00:00
/// A handle to a held `Mutex` that has had a function applied to it via [`MutexGuard::map`] or
/// [`MappedMutexGuard::map`].
///
/// This can be used to hold a subfield of the protected data.
#[clippy::has_significant_drop]
pub struct MappedMutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2024-04-14 22:35:59 +00:00
T: ?Sized,
{
state: &'a BlockingMutex<M, State>,
2024-04-14 22:35:59 +00:00
value: *mut T,
}
impl<'a, M, T> MappedMutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2024-04-14 22:35:59 +00:00
T: ?Sized,
{
/// Returns a locked view over a portion of the locked data.
pub fn map<U>(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedMutexGuard<'a, M, U> {
let state = this.state;
let value = fun(unsafe { &mut *this.value });
// Don't run the `drop` method for MutexGuard. The ownership of the underlying
// locked state is being moved to the returned MappedMutexGuard.
mem::forget(this);
MappedMutexGuard { state, value }
}
}
impl<'a, M, T> Deref for MappedMutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2024-04-14 22:35:59 +00:00
T: ?Sized,
{
type Target = T;
fn deref(&self) -> &Self::Target {
// Safety: the MutexGuard represents exclusive access to the contents
// of the mutex, so it's OK to get it.
unsafe { &*self.value }
}
}
impl<'a, M, T> DerefMut for MappedMutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2024-04-14 22:35:59 +00:00
T: ?Sized,
{
fn deref_mut(&mut self) -> &mut Self::Target {
// Safety: the MutexGuard represents exclusive access to the contents
// of the mutex, so it's OK to get it.
unsafe { &mut *self.value }
}
}
impl<'a, M, T> Drop for MappedMutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
2024-04-14 22:35:59 +00:00
T: ?Sized,
{
fn drop(&mut self) {
self.state.lock(|s| {
s.locked = false;
s.waker.wake();
})
}
}
unsafe impl<M, T> Send for MappedMutexGuard<'_, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex + Sync,
2024-04-14 22:35:59 +00:00
T: Send + ?Sized,
{
}
unsafe impl<M, T> Sync for MappedMutexGuard<'_, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex + Sync,
2024-04-14 22:35:59 +00:00
T: Sync + ?Sized,
{
}
impl<'a, M, T> fmt::Debug for MappedMutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
T: ?Sized + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<'a, M, T> fmt::Display for MappedMutexGuard<'a, M, T>
where
2024-07-21 13:57:21 +00:00
M: ConstScopedRawMutex,
T: ?Sized + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
2024-04-14 22:35:59 +00:00
#[cfg(test)]
mod tests {
use crate::blocking_mutex::raw::NoopRawMutex;
use crate::mutex::{Mutex, MutexGuard};
#[futures_test::test]
async fn mapped_guard_releases_lock_when_dropped() {
let mutex: Mutex<NoopRawMutex, [i32; 2]> = Mutex::new([0, 1]);
{
let guard = mutex.lock().await;
assert_eq!(*guard, [0, 1]);
let mut mapped = MutexGuard::map(guard, |this| &mut this[1]);
assert_eq!(*mapped, 1);
*mapped = 2;
}
{
let guard = mutex.lock().await;
assert_eq!(*guard, [0, 2]);
let mut mapped = MutexGuard::map(guard, |this| &mut this[1]);
assert_eq!(*mapped, 2);
*mapped = 3;
}
assert_eq!(*mutex.lock().await, [0, 3]);
}
}