From 025d9f6e98086e52cf8025ca475f64899b3d7567 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 15:52:07 +0100 Subject: Add RwLock to embassy-sync Fixes #1394 --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/embassy-rs/embassy/issues/1394?shareId=XXXX-XXXX-XXXX-XXXX). --- embassy-sync/src/lib.rs | 1 + embassy-sync/src/rwlock.rs | 256 +++++++++++++++++++++++++++++++++++++++++++ embassy-sync/tests/rwlock.rs | 81 ++++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 embassy-sync/src/rwlock.rs create mode 100644 embassy-sync/tests/rwlock.rs (limited to 'embassy-sync') diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index df0f5e815..5d91b4d9c 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -18,6 +18,7 @@ pub mod once_lock; pub mod pipe; pub mod priority_channel; pub mod pubsub; +pub mod rwlock; pub mod semaphore; pub mod signal; pub mod waitqueue; diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs new file mode 100644 index 000000000..15ea8468e --- /dev/null +++ b/embassy-sync/src/rwlock.rs @@ -0,0 +1,256 @@ +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::ops::{Deref, DerefMut}; +use core::task::Poll; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex as BlockingMutex; +use crate::waitqueue::MultiWakerRegistration; + +/// Error returned by [`RwLock::try_read`] and [`RwLock::try_write`] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TryLockError; + +/// Async read-write lock. +/// +/// The lock is generic over a blocking [`RawMutex`](crate::blocking_mutex::raw::RawMutex). +/// The raw mutex is used to guard access to the internal state. It +/// is held for very short periods only, while locking and unlocking. It is *not* held +/// for the entire time the async RwLock is locked. +/// +/// Which implementation you select depends on the context in which you're using the lock. +/// +/// 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. +/// +pub struct RwLock +where + M: RawMutex, + T: ?Sized, +{ + state: BlockingMutex>, + inner: RefCell, +} + +struct State { + readers: usize, + writer: bool, + writer_waker: MultiWakerRegistration<1>, + reader_wakers: MultiWakerRegistration<8>, +} + +impl State { + fn new() -> Self { + Self { + readers: 0, + writer: false, + writer_waker: MultiWakerRegistration::new(), + reader_wakers: MultiWakerRegistration::new(), + } + } +} + +impl RwLock +where + M: RawMutex, +{ + /// Create a new read-write lock with the given value. + pub const fn new(value: T) -> Self { + Self { + inner: RefCell::new(value), + state: BlockingMutex::new(RefCell::new(State::new())), + } + } +} + +impl RwLock +where + M: RawMutex, + T: ?Sized, +{ + /// Acquire a read lock. + /// + /// This will wait for the lock to be available if it's already locked for writing. + pub fn read(&self) -> impl Future> { + poll_fn(|cx| { + let mut state = self.state.lock(|s| s.borrow_mut()); + if state.writer { + state.reader_wakers.register(cx.waker()); + Poll::Pending + } else { + state.readers += 1; + Poll::Ready(RwLockReadGuard { lock: self }) + } + }) + } + + /// Acquire a write lock. + /// + /// This will wait for the lock to be available if it's already locked for reading or writing. + pub fn write(&self) -> impl Future> { + poll_fn(|cx| { + let mut state = self.state.lock(|s| s.borrow_mut()); + if state.writer || state.readers > 0 { + state.writer_waker.register(cx.waker()); + Poll::Pending + } else { + state.writer = true; + Poll::Ready(RwLockWriteGuard { lock: self }) + } + }) + } + + /// Attempt to immediately acquire a read lock. + /// + /// If the lock is already locked for writing, this will return an error instead of waiting. + pub fn try_read(&self) -> Result, TryLockError> { + let mut state = self.state.lock(|s| s.borrow_mut()); + if state.writer { + Err(TryLockError) + } else { + state.readers += 1; + Ok(RwLockReadGuard { lock: self }) + } + } + + /// Attempt to immediately acquire a write lock. + /// + /// If the lock is already locked for reading or writing, this will return an error instead of waiting. + pub fn try_write(&self) -> Result, TryLockError> { + let mut state = self.state.lock(|s| s.borrow_mut()); + if state.writer || state.readers > 0 { + Err(TryLockError) + } else { + state.writer = true; + Ok(RwLockWriteGuard { lock: self }) + } + } + + /// Consumes this lock, returning the underlying data. + pub fn into_inner(self) -> T + where + T: Sized, + { + self.inner.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the RwLock 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() + } +} + +impl From for RwLock +where + M: RawMutex, +{ + fn from(from: T) -> Self { + Self::new(from) + } +} + +impl Default for RwLock +where + M: RawMutex, + T: Default, +{ + fn default() -> Self { + Self::new(Default::default()) + } +} + +/// Async read lock guard. +/// +/// Owning an instance of this type indicates having +/// successfully locked the RwLock for reading, and grants access to the contents. +/// +/// Dropping it unlocks the RwLock. +#[must_use = "if unused the RwLock will immediately unlock"] +pub struct RwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + lock: &'a RwLock, +} + +impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + let mut state = self.lock.state.lock(|s| s.borrow_mut()); + state.readers -= 1; + if state.readers == 0 { + state.writer_waker.wake(); + } + } +} + +impl<'a, M, T> Deref for RwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + self.lock.inner.borrow() + } +} + +/// Async write lock guard. +/// +/// Owning an instance of this type indicates having +/// successfully locked the RwLock for writing, and grants access to the contents. +/// +/// Dropping it unlocks the RwLock. +#[must_use = "if unused the RwLock will immediately unlock"] +pub struct RwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + lock: &'a RwLock, +} + +impl<'a, M, T> Drop for RwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + let mut state = self.lock.state.lock(|s| s.borrow_mut()); + state.writer = false; + state.reader_wakers.wake(); + state.writer_waker.wake(); + } +} + +impl<'a, M, T> Deref for RwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + self.lock.inner.borrow() + } +} + +impl<'a, M, T> DerefMut for RwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.lock.inner.borrow_mut() + } +} diff --git a/embassy-sync/tests/rwlock.rs b/embassy-sync/tests/rwlock.rs new file mode 100644 index 000000000..301cb0492 --- /dev/null +++ b/embassy-sync/tests/rwlock.rs @@ -0,0 +1,81 @@ +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::rwlock::RwLock; +use futures_executor::block_on; + +#[futures_test::test] +async fn test_rwlock_read() { + let lock = RwLock::::new(5); + + { + let read_guard = lock.read().await; + assert_eq!(*read_guard, 5); + } + + { + let read_guard = lock.read().await; + assert_eq!(*read_guard, 5); + } +} + +#[futures_test::test] +async fn test_rwlock_write() { + let lock = RwLock::::new(5); + + { + let mut write_guard = lock.write().await; + *write_guard = 10; + } + + { + let read_guard = lock.read().await; + assert_eq!(*read_guard, 10); + } +} + +#[futures_test::test] +async fn test_rwlock_try_read() { + let lock = RwLock::::new(5); + + { + let read_guard = lock.try_read().unwrap(); + assert_eq!(*read_guard, 5); + } + + { + let read_guard = lock.try_read().unwrap(); + assert_eq!(*read_guard, 5); + } +} + +#[futures_test::test] +async fn test_rwlock_try_write() { + let lock = RwLock::::new(5); + + { + let mut write_guard = lock.try_write().unwrap(); + *write_guard = 10; + } + + { + let read_guard = lock.try_read().unwrap(); + assert_eq!(*read_guard, 10); + } +} + +#[futures_test::test] +async fn test_rwlock_fairness() { + let lock = RwLock::::new(5); + + let read1 = lock.read().await; + let read2 = lock.read().await; + + let write_fut = lock.write(); + futures_util::pin_mut!(write_fut); + + assert!(futures_util::poll!(write_fut.as_mut()).is_pending()); + + drop(read1); + drop(read2); + + assert!(futures_util::poll!(write_fut.as_mut()).is_ready()); +} -- cgit From 6904b0cc6409aead1a2f49f4c821dffd7fd61ce4 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 16:10:15 +0100 Subject: --- embassy-sync/src/raw_rwlock.rs | 86 ++++++++++++ embassy-sync/src/rwlock.rs | 287 +++++++++++++++++++++-------------------- 2 files changed, 231 insertions(+), 142 deletions(-) create mode 100644 embassy-sync/src/raw_rwlock.rs (limited to 'embassy-sync') diff --git a/embassy-sync/src/raw_rwlock.rs b/embassy-sync/src/raw_rwlock.rs new file mode 100644 index 000000000..de4bd1dc5 --- /dev/null +++ b/embassy-sync/src/raw_rwlock.rs @@ -0,0 +1,86 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; +use core::task::Waker; +use core::cell::UnsafeCell; + +pub trait RawRwLock { + fn lock_read(&self); + fn try_lock_read(&self) -> bool; + fn unlock_read(&self); + fn lock_write(&self); + fn try_lock_write(&self) -> bool; + fn unlock_write(&self); +} + +pub struct RawRwLockImpl { + state: AtomicUsize, + waker: UnsafeCell>, +} + +impl RawRwLockImpl { + pub const fn new() -> Self { + Self { + state: AtomicUsize::new(0), + waker: UnsafeCell::new(None), + } + } +} + +unsafe impl Send for RawRwLockImpl {} +unsafe impl Sync for RawRwLockImpl {} + +impl RawRwLock for RawRwLockImpl { + fn lock_read(&self) { + loop { + let state = self.state.load(Ordering::Acquire); + if state & 1 == 0 { + if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state { + break; + } + } + } + } + + fn try_lock_read(&self) -> bool { + let state = self.state.load(Ordering::Acquire); + if state & 1 == 0 { + if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state { + return true; + } + } + false + } + + fn unlock_read(&self) { + self.state.fetch_sub(2, Ordering::Release); + if self.state.load(Ordering::Acquire) == 0 { + if let Some(waker) = unsafe { &*self.waker.get() } { + waker.wake_by_ref(); + } + } + } + + fn lock_write(&self) { + loop { + let state = self.state.load(Ordering::Acquire); + if state == 0 { + if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 { + break; + } + } + } + } + + fn try_lock_write(&self) -> bool { + if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 { + return true; + } + false + } + + fn unlock_write(&self) { + self.state.store(0, Ordering::Release); + if let Some(waker) = unsafe { &*self.waker.get() } { + waker.wake_by_ref(); + } + } +} diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 15ea8468e..30e1e74ad 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -1,136 +1,134 @@ -use core::cell::RefCell; -use core::future::{poll_fn, Future}; +use core::cell::UnsafeCell; +use core::future::poll_fn; use core::ops::{Deref, DerefMut}; use core::task::Poll; -use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex as BlockingMutex; -use crate::waitqueue::MultiWakerRegistration; +use crate::waitqueue::WakerRegistration; +use crate::raw_rwlock::RawRwLock; -/// Error returned by [`RwLock::try_read`] and [`RwLock::try_write`] -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TryLockError; - -/// Async read-write lock. -/// -/// The lock is generic over a blocking [`RawMutex`](crate::blocking_mutex::raw::RawMutex). -/// The raw mutex is used to guard access to the internal state. It -/// is held for very short periods only, while locking and unlocking. It is *not* held -/// for the entire time the async RwLock is locked. -/// -/// Which implementation you select depends on the context in which you're using the lock. -/// -/// 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. -/// pub struct RwLock where - M: RawMutex, + M: RawRwLock, T: ?Sized, { - state: BlockingMutex>, - inner: RefCell, -} - -struct State { - readers: usize, - writer: bool, - writer_waker: MultiWakerRegistration<1>, - reader_wakers: MultiWakerRegistration<8>, + state: BlockingMutex, + inner: UnsafeCell, } -impl State { - fn new() -> Self { - Self { - readers: 0, - writer: false, - writer_waker: MultiWakerRegistration::new(), - reader_wakers: MultiWakerRegistration::new(), - } - } -} +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} impl RwLock where - M: RawMutex, + M: RawRwLock, { - /// Create a new read-write lock with the given value. pub const fn new(value: T) -> Self { Self { - inner: RefCell::new(value), - state: BlockingMutex::new(RefCell::new(State::new())), + inner: UnsafeCell::new(value), + state: BlockingMutex::new(RwLockState { + locked: LockedState::Unlocked, + writer_pending: 0, + readers_pending: 0, + waker: WakerRegistration::new(), + }), } } } impl RwLock where - M: RawMutex, + M: RawRwLock, T: ?Sized, { - /// Acquire a read lock. - /// - /// This will wait for the lock to be available if it's already locked for writing. pub fn read(&self) -> impl Future> { poll_fn(|cx| { - let mut state = self.state.lock(|s| s.borrow_mut()); - if state.writer { - state.reader_wakers.register(cx.waker()); - Poll::Pending - } else { - state.readers += 1; + let ready = self.state.lock(|s| { + let mut s = s.borrow_mut(); + match s.locked { + LockedState::Unlocked => { + s.locked = LockedState::ReadLocked(1); + true + } + LockedState::ReadLocked(ref mut count) => { + *count += 1; + true + } + LockedState::WriteLocked => { + s.readers_pending += 1; + s.waker.register(cx.waker()); + false + } + } + }); + + if ready { Poll::Ready(RwLockReadGuard { lock: self }) + } else { + Poll::Pending } }) } - /// Acquire a write lock. - /// - /// This will wait for the lock to be available if it's already locked for reading or writing. pub fn write(&self) -> impl Future> { poll_fn(|cx| { - let mut state = self.state.lock(|s| s.borrow_mut()); - if state.writer || state.readers > 0 { - state.writer_waker.register(cx.waker()); - Poll::Pending - } else { - state.writer = true; + let ready = self.state.lock(|s| { + let mut s = s.borrow_mut(); + match s.locked { + LockedState::Unlocked => { + s.locked = LockedState::WriteLocked; + true + } + _ => { + s.writer_pending += 1; + s.waker.register(cx.waker()); + false + } + } + }); + + if ready { Poll::Ready(RwLockWriteGuard { lock: self }) + } else { + Poll::Pending } }) } - /// Attempt to immediately acquire a read lock. - /// - /// If the lock is already locked for writing, this will return an error instead of waiting. pub fn try_read(&self) -> Result, TryLockError> { - let mut state = self.state.lock(|s| s.borrow_mut()); - if state.writer { - Err(TryLockError) - } else { - state.readers += 1; - Ok(RwLockReadGuard { lock: self }) - } + self.state.lock(|s| { + let mut s = s.borrow_mut(); + match s.locked { + LockedState::Unlocked => { + s.locked = LockedState::ReadLocked(1); + Ok(()) + } + LockedState::ReadLocked(ref mut count) => { + *count += 1; + Ok(()) + } + LockedState::WriteLocked => Err(TryLockError), + } + })?; + + Ok(RwLockReadGuard { lock: self }) } - /// Attempt to immediately acquire a write lock. - /// - /// If the lock is already locked for reading or writing, this will return an error instead of waiting. pub fn try_write(&self) -> Result, TryLockError> { - let mut state = self.state.lock(|s| s.borrow_mut()); - if state.writer || state.readers > 0 { - Err(TryLockError) - } else { - state.writer = true; - Ok(RwLockWriteGuard { lock: self }) - } + self.state.lock(|s| { + let mut s = s.borrow_mut(); + match s.locked { + LockedState::Unlocked => { + s.locked = LockedState::WriteLocked; + Ok(()) + } + _ => Err(TryLockError), + } + })?; + + Ok(RwLockWriteGuard { lock: self }) } - /// Consumes this lock, returning the underlying data. pub fn into_inner(self) -> T where T: Sized, @@ -138,19 +136,12 @@ where self.inner.into_inner() } - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the RwLock 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() } } -impl From for RwLock -where - M: RawMutex, -{ +impl From for RwLock { fn from(from: T) -> Self { Self::new(from) } @@ -158,7 +149,7 @@ where impl Default for RwLock where - M: RawMutex, + M: RawRwLock, T: Default, { fn default() -> Self { @@ -166,91 +157,103 @@ where } } -/// Async read lock guard. -/// -/// Owning an instance of this type indicates having -/// successfully locked the RwLock for reading, and grants access to the contents. -/// -/// Dropping it unlocks the RwLock. -#[must_use = "if unused the RwLock will immediately unlock"] pub struct RwLockReadGuard<'a, M, T> where - M: RawMutex, + M: RawRwLock, T: ?Sized, { lock: &'a RwLock, } -impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> +impl<'a, M, T> Deref for RwLockReadGuard<'a, M, T> where - M: RawMutex, + M: RawRwLock, T: ?Sized, { - fn drop(&mut self) { - let mut state = self.lock.state.lock(|s| s.borrow_mut()); - state.readers -= 1; - if state.readers == 0 { - state.writer_waker.wake(); - } + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.lock.inner.get() } } } -impl<'a, M, T> Deref for RwLockReadGuard<'a, M, T> +impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> where - M: RawMutex, + M: RawRwLock, T: ?Sized, { - type Target = T; - fn deref(&self) -> &Self::Target { - self.lock.inner.borrow() + fn drop(&mut self) { + self.lock.state.lock(|s| { + let mut s = s.borrow_mut(); + match s.locked { + LockedState::ReadLocked(ref mut count) => { + *count -= 1; + if *count == 0 { + s.locked = LockedState::Unlocked; + s.waker.wake(); + } + } + _ => unreachable!(), + } + }); } } -/// Async write lock guard. -/// -/// Owning an instance of this type indicates having -/// successfully locked the RwLock for writing, and grants access to the contents. -/// -/// Dropping it unlocks the RwLock. -#[must_use = "if unused the RwLock will immediately unlock"] pub struct RwLockWriteGuard<'a, M, T> where - M: RawMutex, + M: RawRwLock, T: ?Sized, { lock: &'a RwLock, } -impl<'a, M, T> Drop for RwLockWriteGuard<'a, M, T> +impl<'a, M, T> Deref for RwLockWriteGuard<'a, M, T> where - M: RawMutex, + M: RawRwLock, T: ?Sized, { - fn drop(&mut self) { - let mut state = self.lock.state.lock(|s| s.borrow_mut()); - state.writer = false; - state.reader_wakers.wake(); - state.writer_waker.wake(); + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.lock.inner.get() } } } -impl<'a, M, T> Deref for RwLockWriteGuard<'a, M, T> +impl<'a, M, T> DerefMut for RwLockWriteGuard<'a, M, T> where - M: RawMutex, + M: RawRwLock, T: ?Sized, { - type Target = T; - fn deref(&self) -> &Self::Target { - self.lock.inner.borrow() + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.lock.inner.get() } } } -impl<'a, M, T> DerefMut for RwLockWriteGuard<'a, M, T> +impl<'a, M, T> Drop for RwLockWriteGuard<'a, M, T> where - M: RawMutex, + M: RawRwLock, T: ?Sized, { - fn deref_mut(&mut self) -> &mut Self::Target { - self.lock.inner.borrow_mut() + fn drop(&mut self) { + self.lock.state.lock(|s| { + let mut s = s.borrow_mut(); + s.locked = LockedState::Unlocked; + s.waker.wake(); + }); } } + +struct RwLockState { + locked: LockedState, + writer_pending: usize, + readers_pending: usize, + waker: WakerRegistration, +} + +enum LockedState { + Unlocked, + ReadLocked(usize), + WriteLocked, +} + +pub struct TryLockError; -- cgit From 114cfdd86b19640ca109488fb960020de673ec57 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 16:22:16 +0100 Subject: Add `RawRwLock` trait and `RawRwLockImpl` struct implementation * Implement `RawRwLock` trait with methods for read and write locking * Implement `RawRwLockImpl` struct with atomic state and waker * Implement `RawRwLockImpl::lock_read`, `RawRwLockImpl::try_lock_read`, and `RawRwLockImpl::unlock_read` methods * Implement `RawRwLockImpl::lock_write`, `RawRwLockImpl::try_lock_write`, and `RawRwLockImpl::unlock_write` methods --- embassy-sync/src/blocking_mutex/raw_rwlock.rs | 86 +++++++++++++++++++++++++++ embassy-sync/src/raw_rwlock.rs | 86 --------------------------- 2 files changed, 86 insertions(+), 86 deletions(-) create mode 100644 embassy-sync/src/blocking_mutex/raw_rwlock.rs (limited to 'embassy-sync') diff --git a/embassy-sync/src/blocking_mutex/raw_rwlock.rs b/embassy-sync/src/blocking_mutex/raw_rwlock.rs new file mode 100644 index 000000000..de4bd1dc5 --- /dev/null +++ b/embassy-sync/src/blocking_mutex/raw_rwlock.rs @@ -0,0 +1,86 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; +use core::task::Waker; +use core::cell::UnsafeCell; + +pub trait RawRwLock { + fn lock_read(&self); + fn try_lock_read(&self) -> bool; + fn unlock_read(&self); + fn lock_write(&self); + fn try_lock_write(&self) -> bool; + fn unlock_write(&self); +} + +pub struct RawRwLockImpl { + state: AtomicUsize, + waker: UnsafeCell>, +} + +impl RawRwLockImpl { + pub const fn new() -> Self { + Self { + state: AtomicUsize::new(0), + waker: UnsafeCell::new(None), + } + } +} + +unsafe impl Send for RawRwLockImpl {} +unsafe impl Sync for RawRwLockImpl {} + +impl RawRwLock for RawRwLockImpl { + fn lock_read(&self) { + loop { + let state = self.state.load(Ordering::Acquire); + if state & 1 == 0 { + if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state { + break; + } + } + } + } + + fn try_lock_read(&self) -> bool { + let state = self.state.load(Ordering::Acquire); + if state & 1 == 0 { + if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state { + return true; + } + } + false + } + + fn unlock_read(&self) { + self.state.fetch_sub(2, Ordering::Release); + if self.state.load(Ordering::Acquire) == 0 { + if let Some(waker) = unsafe { &*self.waker.get() } { + waker.wake_by_ref(); + } + } + } + + fn lock_write(&self) { + loop { + let state = self.state.load(Ordering::Acquire); + if state == 0 { + if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 { + break; + } + } + } + } + + fn try_lock_write(&self) -> bool { + if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 { + return true; + } + false + } + + fn unlock_write(&self) { + self.state.store(0, Ordering::Release); + if let Some(waker) = unsafe { &*self.waker.get() } { + waker.wake_by_ref(); + } + } +} diff --git a/embassy-sync/src/raw_rwlock.rs b/embassy-sync/src/raw_rwlock.rs index de4bd1dc5..e69de29bb 100644 --- a/embassy-sync/src/raw_rwlock.rs +++ b/embassy-sync/src/raw_rwlock.rs @@ -1,86 +0,0 @@ -use core::sync::atomic::{AtomicUsize, Ordering}; -use core::task::Waker; -use core::cell::UnsafeCell; - -pub trait RawRwLock { - fn lock_read(&self); - fn try_lock_read(&self) -> bool; - fn unlock_read(&self); - fn lock_write(&self); - fn try_lock_write(&self) -> bool; - fn unlock_write(&self); -} - -pub struct RawRwLockImpl { - state: AtomicUsize, - waker: UnsafeCell>, -} - -impl RawRwLockImpl { - pub const fn new() -> Self { - Self { - state: AtomicUsize::new(0), - waker: UnsafeCell::new(None), - } - } -} - -unsafe impl Send for RawRwLockImpl {} -unsafe impl Sync for RawRwLockImpl {} - -impl RawRwLock for RawRwLockImpl { - fn lock_read(&self) { - loop { - let state = self.state.load(Ordering::Acquire); - if state & 1 == 0 { - if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state { - break; - } - } - } - } - - fn try_lock_read(&self) -> bool { - let state = self.state.load(Ordering::Acquire); - if state & 1 == 0 { - if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state { - return true; - } - } - false - } - - fn unlock_read(&self) { - self.state.fetch_sub(2, Ordering::Release); - if self.state.load(Ordering::Acquire) == 0 { - if let Some(waker) = unsafe { &*self.waker.get() } { - waker.wake_by_ref(); - } - } - } - - fn lock_write(&self) { - loop { - let state = self.state.load(Ordering::Acquire); - if state == 0 { - if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 { - break; - } - } - } - } - - fn try_lock_write(&self) -> bool { - if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 { - return true; - } - false - } - - fn unlock_write(&self) { - self.state.store(0, Ordering::Release); - if let Some(waker) = unsafe { &*self.waker.get() } { - waker.wake_by_ref(); - } - } -} -- cgit From a7ecf14259591ff5b324ec1c7d7c521fabebe7d3 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 16:28:10 +0100 Subject: Add blocking read-write lock implementation and remove obsolete tests --- embassy-sync/src/blocking_mutex/raw_rwlock.rs | 86 ---------- embassy-sync/src/blocking_rwlock/mod.rs | 221 ++++++++++++++++++++++++++ embassy-sync/src/blocking_rwlock/raw.rs | 159 ++++++++++++++++++ embassy-sync/src/lib.rs | 1 + embassy-sync/tests/rwlock.rs | 81 ---------- 5 files changed, 381 insertions(+), 167 deletions(-) delete mode 100644 embassy-sync/src/blocking_mutex/raw_rwlock.rs create mode 100644 embassy-sync/src/blocking_rwlock/mod.rs create mode 100644 embassy-sync/src/blocking_rwlock/raw.rs delete mode 100644 embassy-sync/tests/rwlock.rs (limited to 'embassy-sync') diff --git a/embassy-sync/src/blocking_mutex/raw_rwlock.rs b/embassy-sync/src/blocking_mutex/raw_rwlock.rs deleted file mode 100644 index de4bd1dc5..000000000 --- a/embassy-sync/src/blocking_mutex/raw_rwlock.rs +++ /dev/null @@ -1,86 +0,0 @@ -use core::sync::atomic::{AtomicUsize, Ordering}; -use core::task::Waker; -use core::cell::UnsafeCell; - -pub trait RawRwLock { - fn lock_read(&self); - fn try_lock_read(&self) -> bool; - fn unlock_read(&self); - fn lock_write(&self); - fn try_lock_write(&self) -> bool; - fn unlock_write(&self); -} - -pub struct RawRwLockImpl { - state: AtomicUsize, - waker: UnsafeCell>, -} - -impl RawRwLockImpl { - pub const fn new() -> Self { - Self { - state: AtomicUsize::new(0), - waker: UnsafeCell::new(None), - } - } -} - -unsafe impl Send for RawRwLockImpl {} -unsafe impl Sync for RawRwLockImpl {} - -impl RawRwLock for RawRwLockImpl { - fn lock_read(&self) { - loop { - let state = self.state.load(Ordering::Acquire); - if state & 1 == 0 { - if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state { - break; - } - } - } - } - - fn try_lock_read(&self) -> bool { - let state = self.state.load(Ordering::Acquire); - if state & 1 == 0 { - if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state { - return true; - } - } - false - } - - fn unlock_read(&self) { - self.state.fetch_sub(2, Ordering::Release); - if self.state.load(Ordering::Acquire) == 0 { - if let Some(waker) = unsafe { &*self.waker.get() } { - waker.wake_by_ref(); - } - } - } - - fn lock_write(&self) { - loop { - let state = self.state.load(Ordering::Acquire); - if state == 0 { - if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 { - break; - } - } - } - } - - fn try_lock_write(&self) -> bool { - if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 { - return true; - } - false - } - - fn unlock_write(&self) { - self.state.store(0, Ordering::Release); - if let Some(waker) = unsafe { &*self.waker.get() } { - waker.wake_by_ref(); - } - } -} diff --git a/embassy-sync/src/blocking_rwlock/mod.rs b/embassy-sync/src/blocking_rwlock/mod.rs new file mode 100644 index 000000000..a304b77d6 --- /dev/null +++ b/embassy-sync/src/blocking_rwlock/mod.rs @@ -0,0 +1,221 @@ +//! Blocking read-write lock. +//! +//! This module provides a blocking read-write lock that can be used to synchronize data. +pub mod raw_rwlock; + +use core::cell::UnsafeCell; + +use self::raw_rwlock::RawRwLock; + +/// Blocking read-write lock (not async) +/// +/// Provides a blocking read-write lock primitive backed by an implementation of [`raw_rwlock::RawRwLock`]. +/// +/// Which implementation you select depends on the context in which you're using the read-write lock, and you can choose which kind +/// of interior mutability fits your use case. +/// +/// Use [`CriticalSectionRwLock`] when data can be shared between threads and interrupts. +/// +/// Use [`NoopRwLock`] when data is only shared between tasks running on the same executor. +/// +/// Use [`ThreadModeRwLock`] when data is shared between tasks running on the same executor but you want a global singleton. +/// +/// In all cases, the blocking read-write lock is intended to be short lived and not held across await points. +/// Use the async [`RwLock`](crate::rwlock::RwLock) if you need a lock that is held across await points. +pub struct RwLock { + // NOTE: `raw` must be FIRST, so when using ThreadModeRwLock the "can't drop in non-thread-mode" gets + // to run BEFORE dropping `data`. + raw: R, + data: UnsafeCell, +} + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} + +impl RwLock { + /// Creates a new read-write lock in an unlocked state ready for use. + #[inline] + pub const fn new(val: T) -> RwLock { + RwLock { + raw: R::INIT, + data: UnsafeCell::new(val), + } + } + + /// Creates a critical section and grants temporary read access to the protected data. + pub fn read_lock(&self, f: impl FnOnce(&T) -> U) -> U { + self.raw.read_lock(|| { + let ptr = self.data.get() as *const T; + let inner = unsafe { &*ptr }; + f(inner) + }) + } + + /// Creates a critical section and grants temporary write access to the protected data. + pub fn write_lock(&self, f: impl FnOnce(&mut T) -> U) -> U { + self.raw.write_lock(|| { + let ptr = self.data.get() as *mut T; + let inner = unsafe { &mut *ptr }; + f(inner) + }) + } +} + +impl RwLock { + /// Creates a new read-write lock based on a pre-existing raw read-write lock. + /// + /// This allows creating a read-write lock in a constant context on stable Rust. + #[inline] + pub const fn const_new(raw_rwlock: R, val: T) -> RwLock { + RwLock { + raw: raw_rwlock, + data: UnsafeCell::new(val), + } + } + + /// Consumes this read-write lock, 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 `RwLock` 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 read-write lock that allows borrowing data across executors and interrupts. +/// +/// # Safety +/// +/// This read-write lock is safe to share between different executors and interrupts. +pub type CriticalSectionRwLock = RwLock; + +/// A read-write lock that allows borrowing data in the context of a single executor. +/// +/// # Safety +/// +/// **This Read-Write Lock is only safe within a single executor.** +pub type NoopRwLock = RwLock; + +impl RwLock { + /// 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 RwLock { + /// Borrows the data + #[allow(clippy::should_implement_trait)] + pub fn borrow(&self) -> &T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } + } +} + +// ThreadModeRwLock does NOT use the generic read-write lock 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 ThreadModeRawRwLock for use with the generic RwLock (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_rwlock::*; +#[cfg(any(cortex_m, feature = "std"))] +mod thread_mode_rwlock { + use super::*; + + /// A "read-write lock" that only allows borrowing from thread mode. + /// + /// # Safety + /// + /// **This Read-Write Lock is only safe on single-core systems.** + /// + /// On multi-core systems, a `ThreadModeRwLock` **is not sufficient** to ensure exclusive access. + pub struct ThreadModeRwLock { + inner: UnsafeCell, + } + + // NOTE: ThreadModeRwLock 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 CriticalSectionRwLock) + unsafe impl Sync for ThreadModeRwLock {} + unsafe impl Send for ThreadModeRwLock {} + + impl ThreadModeRwLock { + /// Creates a new read-write lock + pub const fn new(value: T) -> Self { + ThreadModeRwLock { + inner: UnsafeCell::new(value), + } + } + } + + impl ThreadModeRwLock { + /// Lock the `ThreadModeRwLock` for reading, granting access to the data. + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn read_lock(&self, f: impl FnOnce(&T) -> R) -> R { + f(self.borrow()) + } + + /// Lock the `ThreadModeRwLock` for writing, granting access to the data. + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn write_lock(&self, f: impl FnOnce(&mut T) -> R) -> R { + f(self.borrow_mut()) + } + + /// Borrows the data + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn borrow(&self) -> &T { + assert!( + raw_rwlock::in_thread_mode(), + "ThreadModeRwLock can only be borrowed from thread mode." + ); + unsafe { &*self.inner.get() } + } + + /// Mutably borrows the data + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn borrow_mut(&self) -> &mut T { + assert!( + raw_rwlock::in_thread_mode(), + "ThreadModeRwLock can only be borrowed from thread mode." + ); + unsafe { &mut *self.inner.get() } + } + } + + impl Drop for ThreadModeRwLock { + 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`. `ThreadModeRwLock` is Send even if + // T isn't, so without this check a user could create a ThreadModeRwLock 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_rwlock::in_thread_mode(), + "ThreadModeRwLock can only be dropped from thread mode." + ); + + // Drop of the inner `T` happens after this. + } + } +} \ No newline at end of file diff --git a/embassy-sync/src/blocking_rwlock/raw.rs b/embassy-sync/src/blocking_rwlock/raw.rs new file mode 100644 index 000000000..85e8374b5 --- /dev/null +++ b/embassy-sync/src/blocking_rwlock/raw.rs @@ -0,0 +1,159 @@ +//! Read-Write Lock primitives. +//! +//! This module provides a trait for read-write locks that can be used in different contexts. +use core::marker::PhantomData; + +/// Raw read-write lock trait. +/// +/// This read-write lock is "raw", which means it does not actually contain the protected data, it +/// just implements the read-write lock mechanism. For most uses you should use [`super::RwLock`] instead, +/// which is generic over a RawRwLock and contains the protected data. +/// +/// Note that, unlike other read-write locks, implementations only guarantee no +/// concurrent access from other threads: concurrent access from the current +/// thread is allowed. For example, it's possible to lock the same read-write lock multiple times reentrantly. +/// +/// Therefore, locking a `RawRwLock` is only enough to guarantee safe shared (`&`) access +/// to the data, it is not enough to guarantee exclusive (`&mut`) access. +/// +/// # Safety +/// +/// RawRwLock implementations must ensure that, while locked, no other thread can lock +/// the RawRwLock concurrently. +/// +/// Unsafe code is allowed to rely on this fact, so incorrect implementations will cause undefined behavior. +pub unsafe trait RawRwLock { + /// Create a new `RawRwLock` instance. + /// + /// This is a const instead of a method to allow creating instances in const context. + const INIT: Self; + + /// Lock this `RawRwLock` for reading. + fn read_lock(&self, f: impl FnOnce() -> R) -> R; + + /// Lock this `RawRwLock` for writing. + fn write_lock(&self, f: impl FnOnce() -> R) -> R; +} + +/// A read-write lock that allows borrowing data across executors and interrupts. +/// +/// # Safety +/// +/// This read-write lock is safe to share between different executors and interrupts. +pub struct CriticalSectionRawRwLock { + _phantom: PhantomData<()>, +} +unsafe impl Send for CriticalSectionRawRwLock {} +unsafe impl Sync for CriticalSectionRawRwLock {} + +impl CriticalSectionRawRwLock { + /// Create a new `CriticalSectionRawRwLock`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawRwLock for CriticalSectionRawRwLock { + const INIT: Self = Self::new(); + + fn read_lock(&self, f: impl FnOnce() -> R) -> R { + critical_section::with(|_| f()) + } + + fn write_lock(&self, f: impl FnOnce() -> R) -> R { + critical_section::with(|_| f()) + } +} + +// ================ + +/// A read-write lock that allows borrowing data in the context of a single executor. +/// +/// # Safety +/// +/// **This Read-Write Lock is only safe within a single executor.** +pub struct NoopRawRwLock { + _phantom: PhantomData<*mut ()>, +} + +unsafe impl Send for NoopRawRwLock {} + +impl NoopRawRwLock { + /// Create a new `NoopRawRwLock`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawRwLock for NoopRawRwLock { + const INIT: Self = Self::new(); + fn read_lock(&self, f: impl FnOnce() -> R) -> R { + f() + } + + fn write_lock(&self, f: impl FnOnce() -> R) -> R { + f() + } +} + +// ================ + +#[cfg(any(cortex_m, feature = "std"))] +mod thread_mode { + use super::*; + + /// A "read-write lock" that only allows borrowing from thread mode. + /// + /// # Safety + /// + /// **This Read-Write Lock is only safe on single-core systems.** + /// + /// On multi-core systems, a `ThreadModeRawRwLock` **is not sufficient** to ensure exclusive access. + pub struct ThreadModeRawRwLock { + _phantom: PhantomData<()>, + } + + unsafe impl Send for ThreadModeRawRwLock {} + unsafe impl Sync for ThreadModeRawRwLock {} + + impl ThreadModeRawRwLock { + /// Create a new `ThreadModeRawRwLock`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } + } + + unsafe impl RawRwLock for ThreadModeRawRwLock { + const INIT: Self = Self::new(); + fn read_lock(&self, f: impl FnOnce() -> R) -> R { + assert!(in_thread_mode(), "ThreadModeRwLock can only be locked from thread mode."); + + f() + } + + fn write_lock(&self, f: impl FnOnce() -> R) -> R { + assert!(in_thread_mode(), "ThreadModeRwLock can only be locked from thread mode."); + + f() + } + } + + impl Drop for ThreadModeRawRwLock { + fn drop(&mut self) { + assert!( + in_thread_mode(), + "ThreadModeRwLock can only be dropped from thread mode." + ); + } + } + + pub(crate) fn in_thread_mode() -> bool { + #[cfg(feature = "std")] + return Some("main") == std::thread::current().name(); + + #[cfg(not(feature = "std"))] + return unsafe { (0xE000ED04 as *const u32).read_volatile() } & 0x1FF == 0; + } +} +#[cfg(any(cortex_m, feature = "std"))] +pub use thread_mode::*; \ No newline at end of file diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index 5d91b4d9c..b9ead5f79 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -11,6 +11,7 @@ pub(crate) mod fmt; mod ring_buffer; pub mod blocking_mutex; +pub mod blocking_rwlock; pub mod channel; pub mod lazy_lock; pub mod mutex; diff --git a/embassy-sync/tests/rwlock.rs b/embassy-sync/tests/rwlock.rs deleted file mode 100644 index 301cb0492..000000000 --- a/embassy-sync/tests/rwlock.rs +++ /dev/null @@ -1,81 +0,0 @@ -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::rwlock::RwLock; -use futures_executor::block_on; - -#[futures_test::test] -async fn test_rwlock_read() { - let lock = RwLock::::new(5); - - { - let read_guard = lock.read().await; - assert_eq!(*read_guard, 5); - } - - { - let read_guard = lock.read().await; - assert_eq!(*read_guard, 5); - } -} - -#[futures_test::test] -async fn test_rwlock_write() { - let lock = RwLock::::new(5); - - { - let mut write_guard = lock.write().await; - *write_guard = 10; - } - - { - let read_guard = lock.read().await; - assert_eq!(*read_guard, 10); - } -} - -#[futures_test::test] -async fn test_rwlock_try_read() { - let lock = RwLock::::new(5); - - { - let read_guard = lock.try_read().unwrap(); - assert_eq!(*read_guard, 5); - } - - { - let read_guard = lock.try_read().unwrap(); - assert_eq!(*read_guard, 5); - } -} - -#[futures_test::test] -async fn test_rwlock_try_write() { - let lock = RwLock::::new(5); - - { - let mut write_guard = lock.try_write().unwrap(); - *write_guard = 10; - } - - { - let read_guard = lock.try_read().unwrap(); - assert_eq!(*read_guard, 10); - } -} - -#[futures_test::test] -async fn test_rwlock_fairness() { - let lock = RwLock::::new(5); - - let read1 = lock.read().await; - let read2 = lock.read().await; - - let write_fut = lock.write(); - futures_util::pin_mut!(write_fut); - - assert!(futures_util::poll!(write_fut.as_mut()).is_pending()); - - drop(read1); - drop(read2); - - assert!(futures_util::poll!(write_fut.as_mut()).is_ready()); -} -- cgit From 33cf27adf646bacd758b7289ebbf460b24c26fa5 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 16:32:12 +0100 Subject: Refactor blocking read-write lock module structure and improve assertions in ThreadModeRawRwLock --- embassy-sync/src/blocking_rwlock/mod.rs | 6 +- embassy-sync/src/blocking_rwlock/raw.rs | 12 +- embassy-sync/src/rwlock.rs | 376 +++++++++++++++++++++----------- 3 files changed, 265 insertions(+), 129 deletions(-) (limited to 'embassy-sync') diff --git a/embassy-sync/src/blocking_rwlock/mod.rs b/embassy-sync/src/blocking_rwlock/mod.rs index a304b77d6..a8fb7d6bc 100644 --- a/embassy-sync/src/blocking_rwlock/mod.rs +++ b/embassy-sync/src/blocking_rwlock/mod.rs @@ -1,11 +1,11 @@ //! Blocking read-write lock. //! //! This module provides a blocking read-write lock that can be used to synchronize data. -pub mod raw_rwlock; +pub mod raw; use core::cell::UnsafeCell; -use self::raw_rwlock::RawRwLock; +use self::raw::RawRwLock; /// Blocking read-write lock (not async) /// @@ -218,4 +218,4 @@ mod thread_mode_rwlock { // Drop of the inner `T` happens after this. } } -} \ No newline at end of file +} diff --git a/embassy-sync/src/blocking_rwlock/raw.rs b/embassy-sync/src/blocking_rwlock/raw.rs index 85e8374b5..7725edfa5 100644 --- a/embassy-sync/src/blocking_rwlock/raw.rs +++ b/embassy-sync/src/blocking_rwlock/raw.rs @@ -126,13 +126,19 @@ mod thread_mode { unsafe impl RawRwLock for ThreadModeRawRwLock { const INIT: Self = Self::new(); fn read_lock(&self, f: impl FnOnce() -> R) -> R { - assert!(in_thread_mode(), "ThreadModeRwLock can only be locked from thread mode."); + assert!( + in_thread_mode(), + "ThreadModeRwLock can only be locked from thread mode." + ); f() } fn write_lock(&self, f: impl FnOnce() -> R) -> R { - assert!(in_thread_mode(), "ThreadModeRwLock can only be locked from thread mode."); + assert!( + in_thread_mode(), + "ThreadModeRwLock can only be locked from thread mode." + ); f() } @@ -156,4 +162,4 @@ mod thread_mode { } } #[cfg(any(cortex_m, feature = "std"))] -pub use thread_mode::*; \ No newline at end of file +pub use thread_mode::*; diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 30e1e74ad..365f6fda5 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -1,134 +1,160 @@ -use core::cell::UnsafeCell; -use core::future::poll_fn; +//! Async read-write lock. +//! +//! This module provides a read-write lock that can be used to synchronize data between asynchronous tasks. +use core::cell::{RefCell, UnsafeCell}; +use core::future::{poll_fn, Future}; use core::ops::{Deref, DerefMut}; use core::task::Poll; +use core::{fmt, mem}; -use crate::blocking_mutex::Mutex as BlockingMutex; +use crate::blocking_mutex::raw::RawRwLock; +use crate::blocking_mutex::RwLock as BlockingRwLock; use crate::waitqueue::WakerRegistration; -use crate::raw_rwlock::RawRwLock; -pub struct RwLock +/// Error returned by [`RwLock::try_read_lock`] and [`RwLock::try_write_lock`] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TryLockError; + +struct State { + readers: usize, + writer: bool, + waker: WakerRegistration, +} + +/// Async read-write lock. +/// +/// The read-write lock is generic over a blocking [`RawRwLock`](crate::blocking_mutex::raw_rwlock::RawRwLock). +/// The raw read-write lock is used to guard access to the internal state. It +/// is held for very short periods only, while locking and unlocking. It is *not* held +/// for the entire time the async RwLock is locked. +/// +/// Which implementation you select depends on the context in which you're using the read-write lock. +/// +/// Use [`CriticalSectionRawRwLock`](crate::blocking_mutex::raw_rwlock::CriticalSectionRawRwLock) when data can be shared between threads and interrupts. +/// +/// Use [`NoopRawRwLock`](crate::blocking_mutex::raw_rwlock::NoopRawRwLock) when data is only shared between tasks running on the same executor. +/// +/// Use [`ThreadModeRawRwLock`](crate::blocking_mutex::raw_rwlock::ThreadModeRawRwLock) when data is shared between tasks running on the same executor but you want a singleton. +/// +pub struct RwLock where - M: RawRwLock, + R: RawRwLock, T: ?Sized, { - state: BlockingMutex, + state: BlockingRwLock>, inner: UnsafeCell, } -unsafe impl Send for RwLock {} -unsafe impl Sync for RwLock {} +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} -impl RwLock +/// Async read-write lock. +impl RwLock where - M: RawRwLock, + R: RawRwLock, { + /// Create a new read-write lock with the given value. pub const fn new(value: T) -> Self { Self { inner: UnsafeCell::new(value), - state: BlockingMutex::new(RwLockState { - locked: LockedState::Unlocked, - writer_pending: 0, - readers_pending: 0, + state: BlockingRwLock::new(RefCell::new(State { + readers: 0, + writer: false, waker: WakerRegistration::new(), - }), + })), } } } -impl RwLock +impl RwLock where - M: RawRwLock, + R: RawRwLock, T: ?Sized, { - pub fn read(&self) -> impl Future> { + /// Lock the read-write lock for reading. + /// + /// This will wait for the lock to be available if it's already locked for writing. + pub fn read_lock(&self) -> impl Future> { poll_fn(|cx| { let ready = self.state.lock(|s| { let mut s = s.borrow_mut(); - match s.locked { - LockedState::Unlocked => { - s.locked = LockedState::ReadLocked(1); - true - } - LockedState::ReadLocked(ref mut count) => { - *count += 1; - true - } - LockedState::WriteLocked => { - s.readers_pending += 1; - s.waker.register(cx.waker()); - false - } + if s.writer { + s.waker.register(cx.waker()); + false + } else { + s.readers += 1; + true } }); if ready { - Poll::Ready(RwLockReadGuard { lock: self }) + Poll::Ready(RwLockReadGuard { rwlock: self }) } else { Poll::Pending } }) } - pub fn write(&self) -> impl Future> { + /// Lock the read-write lock for writing. + /// + /// This will wait for the lock to be available if it's already locked for reading or writing. + pub fn write_lock(&self) -> impl Future> { poll_fn(|cx| { let ready = self.state.lock(|s| { let mut s = s.borrow_mut(); - match s.locked { - LockedState::Unlocked => { - s.locked = LockedState::WriteLocked; - true - } - _ => { - s.writer_pending += 1; - s.waker.register(cx.waker()); - false - } + if s.readers > 0 || s.writer { + s.waker.register(cx.waker()); + false + } else { + s.writer = true; + true } }); if ready { - Poll::Ready(RwLockWriteGuard { lock: self }) + Poll::Ready(RwLockWriteGuard { rwlock: self }) } else { Poll::Pending } }) } - pub fn try_read(&self) -> Result, TryLockError> { + /// Attempt to immediately lock the read-write lock for reading. + /// + /// If the lock is already locked for writing, this will return an error instead of waiting. + pub fn try_read_lock(&self) -> Result, TryLockError> { self.state.lock(|s| { let mut s = s.borrow_mut(); - match s.locked { - LockedState::Unlocked => { - s.locked = LockedState::ReadLocked(1); - Ok(()) - } - LockedState::ReadLocked(ref mut count) => { - *count += 1; - Ok(()) - } - LockedState::WriteLocked => Err(TryLockError), + if s.writer { + Err(TryLockError) + } else { + s.readers += 1; + Ok(()) } })?; - Ok(RwLockReadGuard { lock: self }) + Ok(RwLockReadGuard { rwlock: self }) } - pub fn try_write(&self) -> Result, TryLockError> { + /// Attempt to immediately lock the read-write lock for writing. + /// + /// If the lock is already locked for reading or writing, this will return an error instead of waiting. + pub fn try_write_lock(&self) -> Result, TryLockError> { self.state.lock(|s| { let mut s = s.borrow_mut(); - match s.locked { - LockedState::Unlocked => { - s.locked = LockedState::WriteLocked; - Ok(()) - } - _ => Err(TryLockError), + if s.readers > 0 || s.writer { + Err(TryLockError) + } else { + s.writer = true; + Ok(()) } })?; - Ok(RwLockWriteGuard { lock: self }) + Ok(RwLockWriteGuard { rwlock: self }) } + /// Consumes this read-write lock, returning the underlying data. pub fn into_inner(self) -> T where T: Sized, @@ -136,20 +162,24 @@ where self.inner.into_inner() } + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the RwLock 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() } } -impl From for RwLock { +impl From for RwLock { fn from(from: T) -> Self { Self::new(from) } } -impl Default for RwLock +impl Default for RwLock where - M: RawRwLock, + R: RawRwLock, T: Default, { fn default() -> Self { @@ -157,103 +187,203 @@ where } } -pub struct RwLockReadGuard<'a, M, T> +impl fmt::Debug for RwLock +where + R: RawRwLock, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("RwLock"); + match self.try_write_lock() { + Ok(value) => { + d.field("inner", &&*value); + } + Err(TryLockError) => { + d.field("inner", &format_args!("")); + } + } + + d.finish_non_exhaustive() + } +} + +/// Async read lock guard. +/// +/// Owning an instance of this type indicates having +/// successfully locked the read-write lock for reading, and grants access to the contents. +/// +/// Dropping it unlocks the read-write lock. +#[clippy::has_significant_drop] +#[must_use = "if unused the RwLock will immediately unlock"] +pub struct RwLockReadGuard<'a, R, T> where - M: RawRwLock, + R: RawRwLock, T: ?Sized, { - lock: &'a RwLock, + rwlock: &'a RwLock, } -impl<'a, M, T> Deref for RwLockReadGuard<'a, M, T> +impl<'a, R, T> Drop for RwLockReadGuard<'a, R, T> where - M: RawRwLock, + R: RawRwLock, T: ?Sized, { - type Target = T; + fn drop(&mut self) { + self.rwlock.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.readers -= 1; + if s.readers == 0 { + s.waker.wake(); + } + }) + } +} +impl<'a, R, T> Deref for RwLockReadGuard<'a, R, T> +where + R: RawRwLock, + T: ?Sized, +{ + type Target = T; fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.inner.get() } + // Safety: the RwLockReadGuard represents shared access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &*(self.rwlock.inner.get() as *const T) } } } -impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> +impl<'a, R, T> fmt::Debug for RwLockReadGuard<'a, R, T> where - M: RawRwLock, - T: ?Sized, + R: RawRwLock, + T: ?Sized + fmt::Debug, { - fn drop(&mut self) { - self.lock.state.lock(|s| { - let mut s = s.borrow_mut(); - match s.locked { - LockedState::ReadLocked(ref mut count) => { - *count -= 1; - if *count == 0 { - s.locked = LockedState::Unlocked; - s.waker.wake(); - } - } - _ => unreachable!(), - } - }); + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) } } -pub struct RwLockWriteGuard<'a, M, T> +impl<'a, R, T> fmt::Display for RwLockReadGuard<'a, R, T> where - M: RawRwLock, + R: RawRwLock, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +/// Async write lock guard. +/// +/// Owning an instance of this type indicates having +/// successfully locked the read-write lock for writing, and grants access to the contents. +/// +/// Dropping it unlocks the read-write lock. +#[clippy::has_significant_drop] +#[must_use = "if unused the RwLock will immediately unlock"] +pub struct RwLockWriteGuard<'a, R, T> +where + R: RawRwLock, T: ?Sized, { - lock: &'a RwLock, + rwlock: &'a RwLock, } -impl<'a, M, T> Deref for RwLockWriteGuard<'a, M, T> +impl<'a, R, T> Drop for RwLockWriteGuard<'a, R, T> where - M: RawRwLock, + R: RawRwLock, T: ?Sized, { - type Target = T; + fn drop(&mut self) { + self.rwlock.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.writer = false; + s.waker.wake(); + }) + } +} +impl<'a, R, T> Deref for RwLockWriteGuard<'a, R, T> +where + R: RawRwLock, + T: ?Sized, +{ + type Target = T; fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.inner.get() } + // Safety: the RwLockWriteGuard represents exclusive access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &*(self.rwlock.inner.get() as *mut T) } } } -impl<'a, M, T> DerefMut for RwLockWriteGuard<'a, M, T> +impl<'a, R, T> DerefMut for RwLockWriteGuard<'a, R, T> where - M: RawRwLock, + R: RawRwLock, T: ?Sized, { fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.lock.inner.get() } + // Safety: the RwLockWriteGuard represents exclusive access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &mut *(self.rwlock.inner.get()) } } } -impl<'a, M, T> Drop for RwLockWriteGuard<'a, M, T> +impl<'a, R, T> fmt::Debug for RwLockWriteGuard<'a, R, T> where - M: RawRwLock, - T: ?Sized, + R: RawRwLock, + T: ?Sized + fmt::Debug, { - fn drop(&mut self) { - self.lock.state.lock(|s| { - let mut s = s.borrow_mut(); - s.locked = LockedState::Unlocked; - s.waker.wake(); - }); + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) } } -struct RwLockState { - locked: LockedState, - writer_pending: usize, - readers_pending: usize, - waker: WakerRegistration, +impl<'a, R, T> fmt::Display for RwLockWriteGuard<'a, R, T> +where + R: RawRwLock, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } } -enum LockedState { - Unlocked, - ReadLocked(usize), - WriteLocked, -} +#[cfg(test)] +mod tests { + use crate::blocking_mutex::raw_rwlock::NoopRawRwLock; + use crate::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -pub struct TryLockError; + #[futures_test::test] + async fn read_guard_releases_lock_when_dropped() { + let rwlock: RwLock = RwLock::new([0, 1]); + + { + let guard = rwlock.read_lock().await; + assert_eq!(*guard, [0, 1]); + } + + { + let guard = rwlock.read_lock().await; + assert_eq!(*guard, [0, 1]); + } + + assert_eq!(*rwlock.read_lock().await, [0, 1]); + } + + #[futures_test::test] + async fn write_guard_releases_lock_when_dropped() { + let rwlock: RwLock = RwLock::new([0, 1]); + + { + let mut guard = rwlock.write_lock().await; + assert_eq!(*guard, [0, 1]); + guard[1] = 2; + } + + { + let guard = rwlock.read_lock().await; + assert_eq!(*guard, [0, 2]); + } + + assert_eq!(*rwlock.read_lock().await, [0, 2]); + } +} -- cgit From 55684782258b0241ede93ac6e43a07a3075ad028 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 16:41:41 +0100 Subject: Fix module references in blocking read-write lock implementation --- embassy-sync/src/blocking_rwlock/mod.rs | 16 ++++++++-------- embassy-sync/src/rwlock.rs | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) (limited to 'embassy-sync') diff --git a/embassy-sync/src/blocking_rwlock/mod.rs b/embassy-sync/src/blocking_rwlock/mod.rs index a8fb7d6bc..88cd2164b 100644 --- a/embassy-sync/src/blocking_rwlock/mod.rs +++ b/embassy-sync/src/blocking_rwlock/mod.rs @@ -9,7 +9,7 @@ use self::raw::RawRwLock; /// Blocking read-write lock (not async) /// -/// Provides a blocking read-write lock primitive backed by an implementation of [`raw_rwlock::RawRwLock`]. +/// Provides a blocking read-write lock primitive backed by an implementation of [`raw::RawRwLock`]. /// /// Which implementation you select depends on the context in which you're using the read-write lock, and you can choose which kind /// of interior mutability fits your use case. @@ -94,16 +94,16 @@ impl RwLock { /// # Safety /// /// This read-write lock is safe to share between different executors and interrupts. -pub type CriticalSectionRwLock = RwLock; +pub type CriticalSectionRwLock = RwLock; /// A read-write lock that allows borrowing data in the context of a single executor. /// /// # Safety /// /// **This Read-Write Lock is only safe within a single executor.** -pub type NoopRwLock = RwLock; +pub type NoopRwLock = RwLock; -impl RwLock { +impl RwLock { /// 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; @@ -111,7 +111,7 @@ impl RwLock { } } -impl RwLock { +impl RwLock { /// Borrows the data #[allow(clippy::should_implement_trait)] pub fn borrow(&self) -> &T { @@ -184,7 +184,7 @@ mod thread_mode_rwlock { /// This will panic if not currently running in thread mode. pub fn borrow(&self) -> &T { assert!( - raw_rwlock::in_thread_mode(), + raw::in_thread_mode(), "ThreadModeRwLock can only be borrowed from thread mode." ); unsafe { &*self.inner.get() } @@ -197,7 +197,7 @@ mod thread_mode_rwlock { /// This will panic if not currently running in thread mode. pub fn borrow_mut(&self) -> &mut T { assert!( - raw_rwlock::in_thread_mode(), + raw::in_thread_mode(), "ThreadModeRwLock can only be borrowed from thread mode." ); unsafe { &mut *self.inner.get() } @@ -211,7 +211,7 @@ mod thread_mode_rwlock { // T isn't, so without this check a user could create a ThreadModeRwLock 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_rwlock::in_thread_mode(), + raw::in_thread_mode(), "ThreadModeRwLock can only be dropped from thread mode." ); diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 365f6fda5..9fa61ee56 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -2,13 +2,13 @@ //! //! This module provides a read-write lock that can be used to synchronize data between asynchronous tasks. use core::cell::{RefCell, UnsafeCell}; +use core::fmt; use core::future::{poll_fn, Future}; use core::ops::{Deref, DerefMut}; use core::task::Poll; -use core::{fmt, mem}; -use crate::blocking_mutex::raw::RawRwLock; -use crate::blocking_mutex::RwLock as BlockingRwLock; +use crate::blocking_rwlock::raw::RawRwLock; +use crate::blocking_rwlock::RwLock as BlockingRwLock; use crate::waitqueue::WakerRegistration; /// Error returned by [`RwLock::try_read_lock`] and [`RwLock::try_write_lock`] @@ -77,7 +77,7 @@ where /// This will wait for the lock to be available if it's already locked for writing. pub fn read_lock(&self) -> impl Future> { poll_fn(|cx| { - let ready = self.state.lock(|s| { + let ready = self.state.write_lock(|s| { let mut s = s.borrow_mut(); if s.writer { s.waker.register(cx.waker()); @@ -101,7 +101,7 @@ where /// This will wait for the lock to be available if it's already locked for reading or writing. pub fn write_lock(&self) -> impl Future> { poll_fn(|cx| { - let ready = self.state.lock(|s| { + let ready = self.state.write_lock(|s| { let mut s = s.borrow_mut(); if s.readers > 0 || s.writer { s.waker.register(cx.waker()); @@ -124,7 +124,7 @@ where /// /// If the lock is already locked for writing, this will return an error instead of waiting. pub fn try_read_lock(&self) -> Result, TryLockError> { - self.state.lock(|s| { + self.state.read_lock(|s| { let mut s = s.borrow_mut(); if s.writer { Err(TryLockError) @@ -141,7 +141,7 @@ where /// /// If the lock is already locked for reading or writing, this will return an error instead of waiting. pub fn try_write_lock(&self) -> Result, TryLockError> { - self.state.lock(|s| { + self.state.write_lock(|s| { let mut s = s.borrow_mut(); if s.readers > 0 || s.writer { Err(TryLockError) @@ -229,7 +229,7 @@ where T: ?Sized, { fn drop(&mut self) { - self.rwlock.state.lock(|s| { + self.rwlock.state.write_lock(|s| { let mut s = unwrap!(s.try_borrow_mut()); s.readers -= 1; if s.readers == 0 { @@ -294,7 +294,7 @@ where T: ?Sized, { fn drop(&mut self) { - self.rwlock.state.lock(|s| { + self.rwlock.state.write_lock(|s| { let mut s = unwrap!(s.try_borrow_mut()); s.writer = false; s.waker.wake(); @@ -349,7 +349,7 @@ where #[cfg(test)] mod tests { - use crate::blocking_mutex::raw_rwlock::NoopRawRwLock; + use crate::blocking_rwlock::raw::NoopRawRwLock; use crate::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; #[futures_test::test] -- cgit From 9cbdc9f11d8e0b857ed9bf483120da03ed3a878c Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 23:26:59 +0100 Subject: Implement read-write lock methods in CriticalSectionRawRwLock and update tests --- embassy-sync/src/blocking_rwlock/raw.rs | 57 +++++++++++++++++++++++++++++---- embassy-sync/src/rwlock.rs | 2 +- 2 files changed, 52 insertions(+), 7 deletions(-) (limited to 'embassy-sync') diff --git a/embassy-sync/src/blocking_rwlock/raw.rs b/embassy-sync/src/blocking_rwlock/raw.rs index 7725edfa5..dc25e6305 100644 --- a/embassy-sync/src/blocking_rwlock/raw.rs +++ b/embassy-sync/src/blocking_rwlock/raw.rs @@ -1,7 +1,7 @@ //! Read-Write Lock primitives. //! //! This module provides a trait for read-write locks that can be used in different contexts. -use core::marker::PhantomData; +use core::{cell::RefCell, marker::PhantomData}; /// Raw read-write lock trait. /// @@ -41,15 +41,50 @@ pub unsafe trait RawRwLock { /// /// This read-write lock is safe to share between different executors and interrupts. pub struct CriticalSectionRawRwLock { - _phantom: PhantomData<()>, + state: RefCell, } + unsafe impl Send for CriticalSectionRawRwLock {} unsafe impl Sync for CriticalSectionRawRwLock {} impl CriticalSectionRawRwLock { - /// Create a new `CriticalSectionRawRwLock`. + /// Creates a new [`CriticalSectionRawRwLock`]. pub const fn new() -> Self { - Self { _phantom: PhantomData } + Self { state: RefCell::new(0) } + } + + fn lock_read(&self) { + critical_section::with(|_| { + let mut state = self.state.borrow_mut(); + + while *state & WRITER != 0 { + // Spin until the writer releases the lock + } + *state += 1; + }); + } + + fn unlock_read(&self) { + critical_section::with(|_| { + *self.state.borrow_mut() -= 1; + }); + } + + fn lock_write(&self) { + critical_section::with(|_| { + let mut state = self.state.borrow_mut(); + + while *state != 0 { + // Spin until all readers and writers release the lock + } + *state = WRITER; + }); + } + + fn unlock_write(&self) { + critical_section::with(|_| { + *self.state.borrow_mut() = 0; + }); } } @@ -57,14 +92,24 @@ unsafe impl RawRwLock for CriticalSectionRawRwLock { const INIT: Self = Self::new(); fn read_lock(&self, f: impl FnOnce() -> R) -> R { - critical_section::with(|_| f()) + self.lock_read(); + let result = f(); + self.unlock_read(); + result } fn write_lock(&self, f: impl FnOnce() -> R) -> R { - critical_section::with(|_| f()) + self.lock_write(); + let result = f(); + self.unlock_write(); + result } } +const WRITER: isize = -1; + +// The rest of the file remains unchanged + // ================ /// A read-write lock that allows borrowing data in the context of a single executor. diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 9fa61ee56..0ad5d5864 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -350,7 +350,7 @@ where #[cfg(test)] mod tests { use crate::blocking_rwlock::raw::NoopRawRwLock; - use crate::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + use crate::rwlock::RwLock; #[futures_test::test] async fn read_guard_releases_lock_when_dropped() { -- cgit From f2afcecfa8cb07df8903412e5eed602c4447e4a8 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 23:29:57 +0100 Subject: Remove obsolete `raw_rwlock.rs` file --- embassy-sync/src/raw_rwlock.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 embassy-sync/src/raw_rwlock.rs (limited to 'embassy-sync') diff --git a/embassy-sync/src/raw_rwlock.rs b/embassy-sync/src/raw_rwlock.rs deleted file mode 100644 index e69de29bb..000000000 -- cgit From 10fd6d13213b88c50cab77cbbbb1d81bdaa5b391 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 23:30:01 +0100 Subject: Refactor imports in raw read-write lock module for clarity --- embassy-sync/src/blocking_rwlock/raw.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'embassy-sync') diff --git a/embassy-sync/src/blocking_rwlock/raw.rs b/embassy-sync/src/blocking_rwlock/raw.rs index dc25e6305..91fa773c4 100644 --- a/embassy-sync/src/blocking_rwlock/raw.rs +++ b/embassy-sync/src/blocking_rwlock/raw.rs @@ -1,7 +1,8 @@ //! Read-Write Lock primitives. //! //! This module provides a trait for read-write locks that can be used in different contexts. -use core::{cell::RefCell, marker::PhantomData}; +use core::cell::RefCell; +use core::marker::PhantomData; /// Raw read-write lock trait. /// -- cgit From 82c0ab01f19bebc82f2edbf60b8edac7b17050d6 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Fri, 28 Feb 2025 23:32:58 +0100 Subject: Remove unnecessary comment in CriticalSectionRawRwLock implementation --- embassy-sync/src/blocking_rwlock/raw.rs | 2 -- 1 file changed, 2 deletions(-) (limited to 'embassy-sync') diff --git a/embassy-sync/src/blocking_rwlock/raw.rs b/embassy-sync/src/blocking_rwlock/raw.rs index 91fa773c4..2fb9ce9d1 100644 --- a/embassy-sync/src/blocking_rwlock/raw.rs +++ b/embassy-sync/src/blocking_rwlock/raw.rs @@ -109,8 +109,6 @@ unsafe impl RawRwLock for CriticalSectionRawRwLock { const WRITER: isize = -1; -// The rest of the file remains unchanged - // ================ /// A read-write lock that allows borrowing data in the context of a single executor. -- cgit From e557ca96065592d5ab6c11c0498a4dee32672635 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Sun, 16 Mar 2025 21:45:25 +0100 Subject: Remove blocking read-write lock module and its references and refactor rwlock for a simpler approach --- embassy-sync/src/blocking_rwlock/mod.rs | 221 -------------------------------- embassy-sync/src/blocking_rwlock/raw.rs | 209 ------------------------------ embassy-sync/src/lib.rs | 1 - embassy-sync/src/rwlock.rs | 157 ++++++++--------------- 4 files changed, 52 insertions(+), 536 deletions(-) delete mode 100644 embassy-sync/src/blocking_rwlock/mod.rs delete mode 100644 embassy-sync/src/blocking_rwlock/raw.rs (limited to 'embassy-sync') diff --git a/embassy-sync/src/blocking_rwlock/mod.rs b/embassy-sync/src/blocking_rwlock/mod.rs deleted file mode 100644 index 88cd2164b..000000000 --- a/embassy-sync/src/blocking_rwlock/mod.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! Blocking read-write lock. -//! -//! This module provides a blocking read-write lock that can be used to synchronize data. -pub mod raw; - -use core::cell::UnsafeCell; - -use self::raw::RawRwLock; - -/// Blocking read-write lock (not async) -/// -/// Provides a blocking read-write lock primitive backed by an implementation of [`raw::RawRwLock`]. -/// -/// Which implementation you select depends on the context in which you're using the read-write lock, and you can choose which kind -/// of interior mutability fits your use case. -/// -/// Use [`CriticalSectionRwLock`] when data can be shared between threads and interrupts. -/// -/// Use [`NoopRwLock`] when data is only shared between tasks running on the same executor. -/// -/// Use [`ThreadModeRwLock`] when data is shared between tasks running on the same executor but you want a global singleton. -/// -/// In all cases, the blocking read-write lock is intended to be short lived and not held across await points. -/// Use the async [`RwLock`](crate::rwlock::RwLock) if you need a lock that is held across await points. -pub struct RwLock { - // NOTE: `raw` must be FIRST, so when using ThreadModeRwLock the "can't drop in non-thread-mode" gets - // to run BEFORE dropping `data`. - raw: R, - data: UnsafeCell, -} - -unsafe impl Send for RwLock {} -unsafe impl Sync for RwLock {} - -impl RwLock { - /// Creates a new read-write lock in an unlocked state ready for use. - #[inline] - pub const fn new(val: T) -> RwLock { - RwLock { - raw: R::INIT, - data: UnsafeCell::new(val), - } - } - - /// Creates a critical section and grants temporary read access to the protected data. - pub fn read_lock(&self, f: impl FnOnce(&T) -> U) -> U { - self.raw.read_lock(|| { - let ptr = self.data.get() as *const T; - let inner = unsafe { &*ptr }; - f(inner) - }) - } - - /// Creates a critical section and grants temporary write access to the protected data. - pub fn write_lock(&self, f: impl FnOnce(&mut T) -> U) -> U { - self.raw.write_lock(|| { - let ptr = self.data.get() as *mut T; - let inner = unsafe { &mut *ptr }; - f(inner) - }) - } -} - -impl RwLock { - /// Creates a new read-write lock based on a pre-existing raw read-write lock. - /// - /// This allows creating a read-write lock in a constant context on stable Rust. - #[inline] - pub const fn const_new(raw_rwlock: R, val: T) -> RwLock { - RwLock { - raw: raw_rwlock, - data: UnsafeCell::new(val), - } - } - - /// Consumes this read-write lock, 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 `RwLock` 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 read-write lock that allows borrowing data across executors and interrupts. -/// -/// # Safety -/// -/// This read-write lock is safe to share between different executors and interrupts. -pub type CriticalSectionRwLock = RwLock; - -/// A read-write lock that allows borrowing data in the context of a single executor. -/// -/// # Safety -/// -/// **This Read-Write Lock is only safe within a single executor.** -pub type NoopRwLock = RwLock; - -impl RwLock { - /// 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 RwLock { - /// Borrows the data - #[allow(clippy::should_implement_trait)] - pub fn borrow(&self) -> &T { - let ptr = self.data.get() as *const T; - unsafe { &*ptr } - } -} - -// ThreadModeRwLock does NOT use the generic read-write lock 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 ThreadModeRawRwLock for use with the generic RwLock (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_rwlock::*; -#[cfg(any(cortex_m, feature = "std"))] -mod thread_mode_rwlock { - use super::*; - - /// A "read-write lock" that only allows borrowing from thread mode. - /// - /// # Safety - /// - /// **This Read-Write Lock is only safe on single-core systems.** - /// - /// On multi-core systems, a `ThreadModeRwLock` **is not sufficient** to ensure exclusive access. - pub struct ThreadModeRwLock { - inner: UnsafeCell, - } - - // NOTE: ThreadModeRwLock 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 CriticalSectionRwLock) - unsafe impl Sync for ThreadModeRwLock {} - unsafe impl Send for ThreadModeRwLock {} - - impl ThreadModeRwLock { - /// Creates a new read-write lock - pub const fn new(value: T) -> Self { - ThreadModeRwLock { - inner: UnsafeCell::new(value), - } - } - } - - impl ThreadModeRwLock { - /// Lock the `ThreadModeRwLock` for reading, granting access to the data. - /// - /// # Panics - /// - /// This will panic if not currently running in thread mode. - pub fn read_lock(&self, f: impl FnOnce(&T) -> R) -> R { - f(self.borrow()) - } - - /// Lock the `ThreadModeRwLock` for writing, granting access to the data. - /// - /// # Panics - /// - /// This will panic if not currently running in thread mode. - pub fn write_lock(&self, f: impl FnOnce(&mut T) -> R) -> R { - f(self.borrow_mut()) - } - - /// Borrows the data - /// - /// # Panics - /// - /// This will panic if not currently running in thread mode. - pub fn borrow(&self) -> &T { - assert!( - raw::in_thread_mode(), - "ThreadModeRwLock can only be borrowed from thread mode." - ); - unsafe { &*self.inner.get() } - } - - /// Mutably borrows the data - /// - /// # Panics - /// - /// This will panic if not currently running in thread mode. - pub fn borrow_mut(&self) -> &mut T { - assert!( - raw::in_thread_mode(), - "ThreadModeRwLock can only be borrowed from thread mode." - ); - unsafe { &mut *self.inner.get() } - } - } - - impl Drop for ThreadModeRwLock { - 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`. `ThreadModeRwLock` is Send even if - // T isn't, so without this check a user could create a ThreadModeRwLock 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(), - "ThreadModeRwLock can only be dropped from thread mode." - ); - - // Drop of the inner `T` happens after this. - } - } -} diff --git a/embassy-sync/src/blocking_rwlock/raw.rs b/embassy-sync/src/blocking_rwlock/raw.rs deleted file mode 100644 index 2fb9ce9d1..000000000 --- a/embassy-sync/src/blocking_rwlock/raw.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! Read-Write Lock primitives. -//! -//! This module provides a trait for read-write locks that can be used in different contexts. -use core::cell::RefCell; -use core::marker::PhantomData; - -/// Raw read-write lock trait. -/// -/// This read-write lock is "raw", which means it does not actually contain the protected data, it -/// just implements the read-write lock mechanism. For most uses you should use [`super::RwLock`] instead, -/// which is generic over a RawRwLock and contains the protected data. -/// -/// Note that, unlike other read-write locks, implementations only guarantee no -/// concurrent access from other threads: concurrent access from the current -/// thread is allowed. For example, it's possible to lock the same read-write lock multiple times reentrantly. -/// -/// Therefore, locking a `RawRwLock` is only enough to guarantee safe shared (`&`) access -/// to the data, it is not enough to guarantee exclusive (`&mut`) access. -/// -/// # Safety -/// -/// RawRwLock implementations must ensure that, while locked, no other thread can lock -/// the RawRwLock concurrently. -/// -/// Unsafe code is allowed to rely on this fact, so incorrect implementations will cause undefined behavior. -pub unsafe trait RawRwLock { - /// Create a new `RawRwLock` instance. - /// - /// This is a const instead of a method to allow creating instances in const context. - const INIT: Self; - - /// Lock this `RawRwLock` for reading. - fn read_lock(&self, f: impl FnOnce() -> R) -> R; - - /// Lock this `RawRwLock` for writing. - fn write_lock(&self, f: impl FnOnce() -> R) -> R; -} - -/// A read-write lock that allows borrowing data across executors and interrupts. -/// -/// # Safety -/// -/// This read-write lock is safe to share between different executors and interrupts. -pub struct CriticalSectionRawRwLock { - state: RefCell, -} - -unsafe impl Send for CriticalSectionRawRwLock {} -unsafe impl Sync for CriticalSectionRawRwLock {} - -impl CriticalSectionRawRwLock { - /// Creates a new [`CriticalSectionRawRwLock`]. - pub const fn new() -> Self { - Self { state: RefCell::new(0) } - } - - fn lock_read(&self) { - critical_section::with(|_| { - let mut state = self.state.borrow_mut(); - - while *state & WRITER != 0 { - // Spin until the writer releases the lock - } - *state += 1; - }); - } - - fn unlock_read(&self) { - critical_section::with(|_| { - *self.state.borrow_mut() -= 1; - }); - } - - fn lock_write(&self) { - critical_section::with(|_| { - let mut state = self.state.borrow_mut(); - - while *state != 0 { - // Spin until all readers and writers release the lock - } - *state = WRITER; - }); - } - - fn unlock_write(&self) { - critical_section::with(|_| { - *self.state.borrow_mut() = 0; - }); - } -} - -unsafe impl RawRwLock for CriticalSectionRawRwLock { - const INIT: Self = Self::new(); - - fn read_lock(&self, f: impl FnOnce() -> R) -> R { - self.lock_read(); - let result = f(); - self.unlock_read(); - result - } - - fn write_lock(&self, f: impl FnOnce() -> R) -> R { - self.lock_write(); - let result = f(); - self.unlock_write(); - result - } -} - -const WRITER: isize = -1; - -// ================ - -/// A read-write lock that allows borrowing data in the context of a single executor. -/// -/// # Safety -/// -/// **This Read-Write Lock is only safe within a single executor.** -pub struct NoopRawRwLock { - _phantom: PhantomData<*mut ()>, -} - -unsafe impl Send for NoopRawRwLock {} - -impl NoopRawRwLock { - /// Create a new `NoopRawRwLock`. - pub const fn new() -> Self { - Self { _phantom: PhantomData } - } -} - -unsafe impl RawRwLock for NoopRawRwLock { - const INIT: Self = Self::new(); - fn read_lock(&self, f: impl FnOnce() -> R) -> R { - f() - } - - fn write_lock(&self, f: impl FnOnce() -> R) -> R { - f() - } -} - -// ================ - -#[cfg(any(cortex_m, feature = "std"))] -mod thread_mode { - use super::*; - - /// A "read-write lock" that only allows borrowing from thread mode. - /// - /// # Safety - /// - /// **This Read-Write Lock is only safe on single-core systems.** - /// - /// On multi-core systems, a `ThreadModeRawRwLock` **is not sufficient** to ensure exclusive access. - pub struct ThreadModeRawRwLock { - _phantom: PhantomData<()>, - } - - unsafe impl Send for ThreadModeRawRwLock {} - unsafe impl Sync for ThreadModeRawRwLock {} - - impl ThreadModeRawRwLock { - /// Create a new `ThreadModeRawRwLock`. - pub const fn new() -> Self { - Self { _phantom: PhantomData } - } - } - - unsafe impl RawRwLock for ThreadModeRawRwLock { - const INIT: Self = Self::new(); - fn read_lock(&self, f: impl FnOnce() -> R) -> R { - assert!( - in_thread_mode(), - "ThreadModeRwLock can only be locked from thread mode." - ); - - f() - } - - fn write_lock(&self, f: impl FnOnce() -> R) -> R { - assert!( - in_thread_mode(), - "ThreadModeRwLock can only be locked from thread mode." - ); - - f() - } - } - - impl Drop for ThreadModeRawRwLock { - fn drop(&mut self) { - assert!( - in_thread_mode(), - "ThreadModeRwLock can only be dropped from thread mode." - ); - } - } - - pub(crate) fn in_thread_mode() -> bool { - #[cfg(feature = "std")] - return Some("main") == std::thread::current().name(); - - #[cfg(not(feature = "std"))] - return unsafe { (0xE000ED04 as *const u32).read_volatile() } & 0x1FF == 0; - } -} -#[cfg(any(cortex_m, feature = "std"))] -pub use thread_mode::*; diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index b9ead5f79..5d91b4d9c 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -11,7 +11,6 @@ pub(crate) mod fmt; mod ring_buffer; pub mod blocking_mutex; -pub mod blocking_rwlock; pub mod channel; pub mod lazy_lock; pub mod mutex; diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 0ad5d5864..0e4088c1e 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -7,8 +7,8 @@ use core::future::{poll_fn, Future}; use core::ops::{Deref, DerefMut}; use core::task::Poll; -use crate::blocking_rwlock::raw::RawRwLock; -use crate::blocking_rwlock::RwLock as BlockingRwLock; +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex as BlockingMutex; use crate::waitqueue::WakerRegistration; /// Error returned by [`RwLock::try_read_lock`] and [`RwLock::try_write_lock`] @@ -31,53 +31,48 @@ struct State { /// /// Which implementation you select depends on the context in which you're using the read-write lock. /// -/// Use [`CriticalSectionRawRwLock`](crate::blocking_mutex::raw_rwlock::CriticalSectionRawRwLock) when data can be shared between threads and interrupts. +/// Use [`CriticalSectionMutex`] when data can be shared between threads and interrupts. /// -/// Use [`NoopRawRwLock`](crate::blocking_mutex::raw_rwlock::NoopRawRwLock) when data is only shared between tasks running on the same executor. +/// Use [`NoopMutex`] when data is only shared between tasks running on the same executor. /// -/// Use [`ThreadModeRawRwLock`](crate::blocking_mutex::raw_rwlock::ThreadModeRawRwLock) when data is shared between tasks running on the same executor but you want a singleton. +/// Use [`ThreadModeMutex`] when data is shared between tasks running on the same executor but you want a global singleton. /// -pub struct RwLock + +pub struct RwLock where - R: RawRwLock, + M: RawMutex, T: ?Sized, { - state: BlockingRwLock>, + state: BlockingMutex>, inner: UnsafeCell, } -unsafe impl Send for RwLock {} -unsafe impl Sync for RwLock {} +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} /// Async read-write lock. impl RwLock where - R: RawRwLock, + R: RawMutex, { /// Create a new read-write lock with the given value. pub const fn new(value: T) -> Self { Self { inner: UnsafeCell::new(value), - state: BlockingRwLock::new(RefCell::new(State { + state: BlockingMutex::new(RefCell::new(State { readers: 0, writer: false, waker: WakerRegistration::new(), })), } } -} -impl RwLock -where - R: RawRwLock, - T: ?Sized, -{ /// Lock the read-write lock for reading. /// /// This will wait for the lock to be available if it's already locked for writing. - pub fn read_lock(&self) -> impl Future> { + pub fn read(&self) -> impl Future> { poll_fn(|cx| { - let ready = self.state.write_lock(|s| { + let ready = self.state.lock(|s| { let mut s = s.borrow_mut(); if s.writer { s.waker.register(cx.waker()); @@ -99,11 +94,11 @@ where /// Lock the read-write lock for writing. /// /// This will wait for the lock to be available if it's already locked for reading or writing. - pub fn write_lock(&self) -> impl Future> { + pub fn write(&self) -> impl Future> { poll_fn(|cx| { - let ready = self.state.write_lock(|s| { + let ready = self.state.lock(|s| { let mut s = s.borrow_mut(); - if s.readers > 0 || s.writer { + if s.writer || s.readers > 0 { s.waker.register(cx.waker()); false } else { @@ -119,41 +114,13 @@ where } }) } +} - /// Attempt to immediately lock the read-write lock for reading. - /// - /// If the lock is already locked for writing, this will return an error instead of waiting. - pub fn try_read_lock(&self) -> Result, TryLockError> { - self.state.read_lock(|s| { - let mut s = s.borrow_mut(); - if s.writer { - Err(TryLockError) - } else { - s.readers += 1; - Ok(()) - } - })?; - - Ok(RwLockReadGuard { rwlock: self }) - } - - /// Attempt to immediately lock the read-write lock for writing. - /// - /// If the lock is already locked for reading or writing, this will return an error instead of waiting. - pub fn try_write_lock(&self) -> Result, TryLockError> { - self.state.write_lock(|s| { - let mut s = s.borrow_mut(); - if s.readers > 0 || s.writer { - Err(TryLockError) - } else { - s.writer = true; - Ok(()) - } - })?; - - Ok(RwLockWriteGuard { rwlock: self }) - } - +impl RwLock +where + R: RawMutex, + T: ?Sized, +{ /// Consumes this read-write lock, returning the underlying data. pub fn into_inner(self) -> T where @@ -171,7 +138,7 @@ where } } -impl From for RwLock { +impl From for RwLock { fn from(from: T) -> Self { Self::new(from) } @@ -179,7 +146,7 @@ impl From for RwLock { impl Default for RwLock where - R: RawRwLock, + R: RawMutex, T: Default, { fn default() -> Self { @@ -187,26 +154,6 @@ where } } -impl fmt::Debug for RwLock -where - R: RawRwLock, - T: ?Sized + fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("RwLock"); - match self.try_write_lock() { - Ok(value) => { - d.field("inner", &&*value); - } - Err(TryLockError) => { - d.field("inner", &format_args!("")); - } - } - - d.finish_non_exhaustive() - } -} - /// Async read lock guard. /// /// Owning an instance of this type indicates having @@ -217,19 +164,19 @@ where #[must_use = "if unused the RwLock will immediately unlock"] pub struct RwLockReadGuard<'a, R, T> where - R: RawRwLock, + R: RawMutex, T: ?Sized, { rwlock: &'a RwLock, } -impl<'a, R, T> Drop for RwLockReadGuard<'a, R, T> +impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> where - R: RawRwLock, + M: RawMutex, T: ?Sized, { fn drop(&mut self) { - self.rwlock.state.write_lock(|s| { + self.rwlock.state.lock(|s| { let mut s = unwrap!(s.try_borrow_mut()); s.readers -= 1; if s.readers == 0 { @@ -239,9 +186,9 @@ where } } -impl<'a, R, T> Deref for RwLockReadGuard<'a, R, T> +impl<'a, M, T> Deref for RwLockReadGuard<'a, M, T> where - R: RawRwLock, + M: RawMutex, T: ?Sized, { type Target = T; @@ -252,9 +199,9 @@ where } } -impl<'a, R, T> fmt::Debug for RwLockReadGuard<'a, R, T> +impl<'a, M, T> fmt::Debug for RwLockReadGuard<'a, M, T> where - R: RawRwLock, + M: RawMutex, T: ?Sized + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -262,9 +209,9 @@ where } } -impl<'a, R, T> fmt::Display for RwLockReadGuard<'a, R, T> +impl<'a, M, T> fmt::Display for RwLockReadGuard<'a, M, T> where - R: RawRwLock, + M: RawMutex, T: ?Sized + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -282,7 +229,7 @@ where #[must_use = "if unused the RwLock will immediately unlock"] pub struct RwLockWriteGuard<'a, R, T> where - R: RawRwLock, + R: RawMutex, T: ?Sized, { rwlock: &'a RwLock, @@ -290,11 +237,11 @@ where impl<'a, R, T> Drop for RwLockWriteGuard<'a, R, T> where - R: RawRwLock, + R: RawMutex, T: ?Sized, { fn drop(&mut self) { - self.rwlock.state.write_lock(|s| { + self.rwlock.state.lock(|s| { let mut s = unwrap!(s.try_borrow_mut()); s.writer = false; s.waker.wake(); @@ -304,7 +251,7 @@ where impl<'a, R, T> Deref for RwLockWriteGuard<'a, R, T> where - R: RawRwLock, + R: RawMutex, T: ?Sized, { type Target = T; @@ -317,7 +264,7 @@ where impl<'a, R, T> DerefMut for RwLockWriteGuard<'a, R, T> where - R: RawRwLock, + R: RawMutex, T: ?Sized, { fn deref_mut(&mut self) -> &mut Self::Target { @@ -329,7 +276,7 @@ where impl<'a, R, T> fmt::Debug for RwLockWriteGuard<'a, R, T> where - R: RawRwLock, + R: RawMutex, T: ?Sized + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -339,7 +286,7 @@ where impl<'a, R, T> fmt::Display for RwLockWriteGuard<'a, R, T> where - R: RawRwLock, + R: RawMutex, T: ?Sized + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -349,41 +296,41 @@ where #[cfg(test)] mod tests { - use crate::blocking_rwlock::raw::NoopRawRwLock; + use crate::blocking_mutex::raw::NoopRawMutex; use crate::rwlock::RwLock; #[futures_test::test] async fn read_guard_releases_lock_when_dropped() { - let rwlock: RwLock = RwLock::new([0, 1]); + let rwlock: RwLock = RwLock::new([0, 1]); { - let guard = rwlock.read_lock().await; + let guard = rwlock.read().await; assert_eq!(*guard, [0, 1]); } { - let guard = rwlock.read_lock().await; + let guard = rwlock.read().await; assert_eq!(*guard, [0, 1]); } - assert_eq!(*rwlock.read_lock().await, [0, 1]); + assert_eq!(*rwlock.read().await, [0, 1]); } #[futures_test::test] async fn write_guard_releases_lock_when_dropped() { - let rwlock: RwLock = RwLock::new([0, 1]); + let rwlock: RwLock = RwLock::new([0, 1]); { - let mut guard = rwlock.write_lock().await; + let mut guard = rwlock.write().await; assert_eq!(*guard, [0, 1]); guard[1] = 2; } { - let guard = rwlock.read_lock().await; + let guard = rwlock.read().await; assert_eq!(*guard, [0, 2]); } - assert_eq!(*rwlock.read_lock().await, [0, 2]); + assert_eq!(*rwlock.read().await, [0, 2]); } } -- cgit From 41276de34fdc30ebb1c73d9aa3f1664cd7a26345 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Mon, 17 Mar 2025 19:56:17 +0100 Subject: Refactor RwLock implementation to support try_read and try_write methods, enhancing lock acquisition flexibility --- embassy-sync/src/rwlock.rs | 270 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 253 insertions(+), 17 deletions(-) (limited to 'embassy-sync') diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 0e4088c1e..7f0bbec60 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -2,10 +2,10 @@ //! //! This module provides a read-write lock that can be used to synchronize data between asynchronous tasks. use core::cell::{RefCell, UnsafeCell}; -use core::fmt; use core::future::{poll_fn, Future}; use core::ops::{Deref, DerefMut}; use core::task::Poll; +use core::{fmt, mem}; use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex as BlockingMutex; @@ -31,11 +31,11 @@ struct State { /// /// Which implementation you select depends on the context in which you're using the read-write lock. /// -/// Use [`CriticalSectionMutex`] when data can be shared between threads and interrupts. +/// Use [`CriticalSectionRawMutex`](crate::blocking_mutex::raw::CriticalSectionRawMutex) when data can be shared between threads and interrupts. /// -/// Use [`NoopMutex`] when data is only shared between tasks running on the same executor. +/// Use [`NoopRawMutex`](crate::blocking_mutex::raw::NoopRawMutex) 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. +/// Use [`ThreadModeRawMutex`](crate::blocking_mutex::raw::ThreadModeRawMutex) when data is shared between tasks running on the same executor but you want a singleton. /// pub struct RwLock @@ -51,9 +51,9 @@ unsafe impl Send for RwLock {} unsafe impl Sync for RwLock {} /// Async read-write lock. -impl RwLock +impl RwLock where - R: RawMutex, + M: RawMutex, { /// Create a new read-write lock with the given value. pub const fn new(value: T) -> Self { @@ -66,11 +66,17 @@ where })), } } +} +impl RwLock +where + M: RawMutex, + T: ?Sized, +{ /// Lock the read-write lock for reading. /// /// This will wait for the lock to be available if it's already locked for writing. - pub fn read(&self) -> impl Future> { + pub fn read(&self) -> impl Future> { poll_fn(|cx| { let ready = self.state.lock(|s| { let mut s = s.borrow_mut(); @@ -94,7 +100,7 @@ where /// Lock the read-write lock for writing. /// /// This will wait for the lock to be available if it's already locked for reading or writing. - pub fn write(&self) -> impl Future> { + pub fn write(&self) -> impl Future> { poll_fn(|cx| { let ready = self.state.lock(|s| { let mut s = s.borrow_mut(); @@ -114,13 +120,43 @@ where } }) } -} -impl RwLock -where - R: RawMutex, - T: ?Sized, -{ + /// Attempt to immediately lock the rwlock. + /// + /// If the rwlock is already locked, this will return an error instead of waiting. + pub fn try_read(&self) -> Result, TryLockError> { + self.state + .lock(|s| { + let mut s = s.borrow_mut(); + if s.writer { + return Err(()); + } + s.readers += 1; + Ok(()) + }) + .map_err(|_| TryLockError)?; + + Ok(RwLockReadGuard { rwlock: self }) + } + + /// Attempt to immediately lock the rwlock. + /// + /// If the rwlock is already locked, this will return an error instead of waiting. + pub fn try_write(&self) -> Result, TryLockError> { + self.state + .lock(|s| { + let mut s = s.borrow_mut(); + if s.writer || s.readers > 0 { + return Err(()); + } + s.writer = true; + Ok(()) + }) + .map_err(|_| TryLockError)?; + + Ok(RwLockWriteGuard { rwlock: self }) + } + /// Consumes this read-write lock, returning the underlying data. pub fn into_inner(self) -> T where @@ -138,15 +174,15 @@ where } } -impl From for RwLock { +impl From for RwLock { fn from(from: T) -> Self { Self::new(from) } } -impl Default for RwLock +impl Default for RwLock where - R: RawMutex, + M: RawMutex, T: Default, { fn default() -> Self { @@ -154,6 +190,21 @@ where } } +impl fmt::Debug for RwLock +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("RwLock"); + match self.try_read() { + Ok(guard) => d.field("inner", &&*guard), + Err(TryLockError) => d.field("inner", &"Locked"), + } + .finish_non_exhaustive() + } +} + /// Async read lock guard. /// /// Owning an instance of this type indicates having @@ -170,6 +221,27 @@ where rwlock: &'a RwLock, } +impl<'a, M, T> RwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + /// Map the contents of the `RwLockReadGuard` to a different type. + /// + /// This is useful for calling methods on the contents of the `RwLockReadGuard` without + /// moving out of the guard. + pub fn map(this: Self, fun: impl FnOnce(&T) -> &U) -> MappedRwLockReadGuard<'a, M, U> { + let rwlock = this.rwlock; + let value = fun(unsafe { &mut *this.rwlock.inner.get() }); + + mem::forget(this); + MappedRwLockReadGuard { + state: &rwlock.state, + value, + } + } +} + impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> where M: RawMutex, @@ -294,6 +366,170 @@ where } } +/// 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 MappedRwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + state: &'a BlockingMutex>, + value: *const T, +} + +impl<'a, M, T> Deref for MappedRwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + // Safety: the MappedRwLockReadGuard represents shared access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &*self.value } + } +} + +impl<'a, M, T> Drop for MappedRwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + self.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.readers -= 1; + if s.readers == 0 { + s.waker.wake(); + } + }) + } +} + +unsafe impl<'a, M, T> Send for MappedRwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ +} + +unsafe impl<'a, M, T> Sync for MappedRwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ +} + +impl<'a, M, T> fmt::Debug for MappedRwLockReadGuard<'a, M, T> +where + M: RawMutex, + 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 MappedRwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +/// 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 MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + state: &'a BlockingMutex>, + value: *mut T, +} + +impl<'a, M, T> Deref for MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + // Safety: the MappedRwLockWriteGuard represents exclusive access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &*self.value } + } +} + +impl<'a, M, T> DerefMut for MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: the MappedRwLockWriteGuard represents exclusive access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &mut *self.value } + } +} + +impl<'a, M, T> Drop for MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + self.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.writer = false; + s.waker.wake(); + }) + } +} + +unsafe impl<'a, M, T> Send for MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ +} + +unsafe impl<'a, M, T> Sync for MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ +} + +impl<'a, M, T> fmt::Debug for MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + 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 MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + #[cfg(test)] mod tests { use crate::blocking_mutex::raw::NoopRawMutex; -- cgit From bce15cc813f0fec7eaac1cb709706fb0f4c5e3b8 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Mon, 17 Mar 2025 20:03:36 +0100 Subject: Enhance RwLock documentation and add map methods for read and write guards to improve data access flexibility --- embassy-sync/src/rwlock.rs | 63 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 6 deletions(-) (limited to 'embassy-sync') diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 7f0bbec60..25c575925 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -11,7 +11,7 @@ use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex as BlockingMutex; use crate::waitqueue::WakerRegistration; -/// Error returned by [`RwLock::try_read_lock`] and [`RwLock::try_write_lock`] +/// Error returned by [`RwLock::try_read`] and [`RwLock::try_write`] when the lock is already held. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TryLockError; @@ -24,7 +24,7 @@ struct State { /// Async read-write lock. /// -/// The read-write lock is generic over a blocking [`RawRwLock`](crate::blocking_mutex::raw_rwlock::RawRwLock). +/// The read-write lock is generic over the raw mutex implementation `M` and the data `T` it protects. /// The raw read-write lock is used to guard access to the internal state. It /// is held for very short periods only, while locking and unlocking. It is *not* held /// for the entire time the async RwLock is locked. @@ -307,6 +307,25 @@ where rwlock: &'a RwLock, } +impl<'a, R, T> RwLockWriteGuard<'a, R, T> +where + R: RawMutex, + T: ?Sized, +{ + /// Returns a locked view over a portion of the locked data. + pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedRwLockWriteGuard<'a, R, U> { + let rwlock = this.rwlock; + let value = fun(unsafe { &mut *this.rwlock.inner.get() }); + // Dont run the `drop` method for RwLockWriteGuard. The ownership of the underlying + // locked state is being moved to the returned MappedRwLockWriteGuard. + mem::forget(this); + MappedRwLockWriteGuard { + state: &rwlock.state, + value, + } + } +} + impl<'a, R, T> Drop for RwLockWriteGuard<'a, R, T> where R: RawMutex, @@ -366,8 +385,8 @@ where } } -/// A handle to a held `Mutex` that has had a function applied to it via [`MutexGuard::map`] or -/// [`MappedMutexGuard::map`]. +/// A handle to a held `RwLock` that has had a function applied to it via [`RwLockReadGuard::map`] or +/// [`MappedRwLockReadGuard::map`]. /// /// This can be used to hold a subfield of the protected data. #[clippy::has_significant_drop] @@ -380,6 +399,22 @@ where value: *const T, } +impl<'a, M, T> MappedRwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + /// Returns a locked view over a portion of the locked data. + pub fn map(this: Self, fun: impl FnOnce(&T) -> &U) -> MappedRwLockReadGuard<'a, M, U> { + let rwlock = this.state; + let value = fun(unsafe { &*this.value }); + // Dont run the `drop` method for RwLockReadGuard. The ownership of the underlying + // locked state is being moved to the returned MappedRwLockReadGuard. + mem::forget(this); + MappedRwLockReadGuard { state: rwlock, value } + } +} + impl<'a, M, T> Deref for MappedRwLockReadGuard<'a, M, T> where M: RawMutex, @@ -443,8 +478,8 @@ where } } -/// A handle to a held `Mutex` that has had a function applied to it via [`MutexGuard::map`] or -/// [`MappedMutexGuard::map`]. +/// A handle to a held `RwLock` that has had a function applied to it via [`RwLockWriteGuard::map`] or +/// [`MappedRwLockWriteGuard::map`]. /// /// This can be used to hold a subfield of the protected data. #[clippy::has_significant_drop] @@ -457,6 +492,22 @@ where value: *mut T, } +impl<'a, M, T> MappedRwLockWriteGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + /// Returns a locked view over a portion of the locked data. + pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedRwLockWriteGuard<'a, M, U> { + let rwlock = this.state; + let value = fun(unsafe { &mut *this.value }); + // Dont run the `drop` method for RwLockWriteGuard. The ownership of the underlying + // locked state is being moved to the returned MappedRwLockWriteGuard. + mem::forget(this); + MappedRwLockWriteGuard { state: rwlock, value } + } +} + impl<'a, M, T> Deref for MappedRwLockWriteGuard<'a, M, T> where M: RawMutex, -- cgit From 181a324b4d6ea79a570f020941f0419fbb4530fc Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Sat, 12 Apr 2025 12:26:48 +0200 Subject: Refactor RwLock implementation by removing unused map methods and cleaning up code for improved readability --- embassy-sync/src/rwlock.rs | 238 +-------------------------------------------- 1 file changed, 1 insertion(+), 237 deletions(-) (limited to 'embassy-sync') diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 25c575925..deeadd167 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -2,10 +2,10 @@ //! //! This module provides a read-write lock that can be used to synchronize data between asynchronous tasks. use core::cell::{RefCell, UnsafeCell}; +use core::fmt; use core::future::{poll_fn, Future}; use core::ops::{Deref, DerefMut}; use core::task::Poll; -use core::{fmt, mem}; use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex as BlockingMutex; @@ -221,27 +221,6 @@ where rwlock: &'a RwLock, } -impl<'a, M, T> RwLockReadGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - /// Map the contents of the `RwLockReadGuard` to a different type. - /// - /// This is useful for calling methods on the contents of the `RwLockReadGuard` without - /// moving out of the guard. - pub fn map(this: Self, fun: impl FnOnce(&T) -> &U) -> MappedRwLockReadGuard<'a, M, U> { - let rwlock = this.rwlock; - let value = fun(unsafe { &mut *this.rwlock.inner.get() }); - - mem::forget(this); - MappedRwLockReadGuard { - state: &rwlock.state, - value, - } - } -} - impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> where M: RawMutex, @@ -307,25 +286,6 @@ where rwlock: &'a RwLock, } -impl<'a, R, T> RwLockWriteGuard<'a, R, T> -where - R: RawMutex, - T: ?Sized, -{ - /// Returns a locked view over a portion of the locked data. - pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedRwLockWriteGuard<'a, R, U> { - let rwlock = this.rwlock; - let value = fun(unsafe { &mut *this.rwlock.inner.get() }); - // Dont run the `drop` method for RwLockWriteGuard. The ownership of the underlying - // locked state is being moved to the returned MappedRwLockWriteGuard. - mem::forget(this); - MappedRwLockWriteGuard { - state: &rwlock.state, - value, - } - } -} - impl<'a, R, T> Drop for RwLockWriteGuard<'a, R, T> where R: RawMutex, @@ -385,202 +345,6 @@ where } } -/// A handle to a held `RwLock` that has had a function applied to it via [`RwLockReadGuard::map`] or -/// [`MappedRwLockReadGuard::map`]. -/// -/// This can be used to hold a subfield of the protected data. -#[clippy::has_significant_drop] -pub struct MappedRwLockReadGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - state: &'a BlockingMutex>, - value: *const T, -} - -impl<'a, M, T> MappedRwLockReadGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - /// Returns a locked view over a portion of the locked data. - pub fn map(this: Self, fun: impl FnOnce(&T) -> &U) -> MappedRwLockReadGuard<'a, M, U> { - let rwlock = this.state; - let value = fun(unsafe { &*this.value }); - // Dont run the `drop` method for RwLockReadGuard. The ownership of the underlying - // locked state is being moved to the returned MappedRwLockReadGuard. - mem::forget(this); - MappedRwLockReadGuard { state: rwlock, value } - } -} - -impl<'a, M, T> Deref for MappedRwLockReadGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - type Target = T; - fn deref(&self) -> &Self::Target { - // Safety: the MappedRwLockReadGuard represents shared access to the contents - // of the read-write lock, so it's OK to get it. - unsafe { &*self.value } - } -} - -impl<'a, M, T> Drop for MappedRwLockReadGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - fn drop(&mut self) { - self.state.lock(|s| { - let mut s = unwrap!(s.try_borrow_mut()); - s.readers -= 1; - if s.readers == 0 { - s.waker.wake(); - } - }) - } -} - -unsafe impl<'a, M, T> Send for MappedRwLockReadGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ -} - -unsafe impl<'a, M, T> Sync for MappedRwLockReadGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ -} - -impl<'a, M, T> fmt::Debug for MappedRwLockReadGuard<'a, M, T> -where - M: RawMutex, - 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 MappedRwLockReadGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized + fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -/// A handle to a held `RwLock` that has had a function applied to it via [`RwLockWriteGuard::map`] or -/// [`MappedRwLockWriteGuard::map`]. -/// -/// This can be used to hold a subfield of the protected data. -#[clippy::has_significant_drop] -pub struct MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - state: &'a BlockingMutex>, - value: *mut T, -} - -impl<'a, M, T> MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - /// Returns a locked view over a portion of the locked data. - pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedRwLockWriteGuard<'a, M, U> { - let rwlock = this.state; - let value = fun(unsafe { &mut *this.value }); - // Dont run the `drop` method for RwLockWriteGuard. The ownership of the underlying - // locked state is being moved to the returned MappedRwLockWriteGuard. - mem::forget(this); - MappedRwLockWriteGuard { state: rwlock, value } - } -} - -impl<'a, M, T> Deref for MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - type Target = T; - fn deref(&self) -> &Self::Target { - // Safety: the MappedRwLockWriteGuard represents exclusive access to the contents - // of the read-write lock, so it's OK to get it. - unsafe { &*self.value } - } -} - -impl<'a, M, T> DerefMut for MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - // Safety: the MappedRwLockWriteGuard represents exclusive access to the contents - // of the read-write lock, so it's OK to get it. - unsafe { &mut *self.value } - } -} - -impl<'a, M, T> Drop for MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ - fn drop(&mut self) { - self.state.lock(|s| { - let mut s = unwrap!(s.try_borrow_mut()); - s.writer = false; - s.waker.wake(); - }) - } -} - -unsafe impl<'a, M, T> Send for MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ -} - -unsafe impl<'a, M, T> Sync for MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized, -{ -} - -impl<'a, M, T> fmt::Debug for MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - 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 MappedRwLockWriteGuard<'a, M, T> -where - M: RawMutex, - T: ?Sized + fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - #[cfg(test)] mod tests { use crate::blocking_mutex::raw::NoopRawMutex; -- cgit