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]); } }