From 26acee2902c519075bd3d5f98d2a1a498ee3d1a9 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Tue, 13 Feb 2024 14:50:26 +0100 Subject: Add initial implementation of `MultiSignal` sync primitive --- embassy-sync/src/lib.rs | 1 + embassy-sync/src/multi_signal.rs | 285 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 embassy-sync/src/multi_signal.rs (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index d88c76db5..f02985564 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -12,6 +12,7 @@ mod ring_buffer; pub mod blocking_mutex; pub mod channel; +pub mod multi_signal; pub mod mutex; pub mod pipe; pub mod priority_channel; diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs new file mode 100644 index 000000000..db858f269 --- /dev/null +++ b/embassy-sync/src/multi_signal.rs @@ -0,0 +1,285 @@ +//! A synchronization primitive for passing the latest value to **multiple** tasks. +use core::{ + cell::RefCell, + marker::PhantomData, + ops::{Deref, DerefMut}, + pin::Pin, + task::{Context, Poll}, +}; + +use futures_util::Future; + +use crate::{ + blocking_mutex::{raw::RawMutex, Mutex}, + waitqueue::MultiWakerRegistration, +}; + +/// A `MultiSignal` is a single-slot signaling primitive, which can awake `N` separate [`Receiver`]s. +/// +/// Similar to a [`Signal`](crate::signal::Signal), except `MultiSignal` allows for multiple tasks to +/// `.await` the latest value, and all receive it. +/// +/// This is similar to a [`PubSubChannel`](crate::pubsub::PubSubChannel) with a buffer size of 1, except +/// "sending" to it (calling [`MultiSignal::write`]) will immediately overwrite the previous value instead +/// of waiting for the receivers to pop the previous value. +/// +/// `MultiSignal` is useful when a single task is responsible for updating a value or "state", which multiple other +/// tasks are interested in getting notified about changes to the latest value of. It is therefore fine for +/// [`Receiver`]s to "lose" stale values. +/// +/// Anyone with a reference to the MultiSignal can update or peek the value. MultiSignals are generally declared +/// as `static`s and then borrowed as required to either [`MultiSignal::peek`] the value or obtain a [`Receiver`] +/// with [`MultiSignal::receiver`] which has async methods. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::multi_signal::MultiSignal; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); +/// +/// // Obtain Receivers +/// let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); +/// let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); +/// assert!(SOME_SIGNAL.receiver().is_err()); +/// +/// SOME_SIGNAL.write(10); +/// +/// // Receive the new value +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// SOME_SIGNAL.write(20); +/// +/// // Receive new value with predicate +/// assert_eq!(rcv0.changed_and(|x|x>&10).await, 20); +/// assert_eq!(rcv1.try_changed_and(|x|x>&30), None); +/// +/// // Anyone can peek the current value +/// assert_eq!(rcv0.peek(), 20); +/// assert_eq!(rcv1.peek(), 20); +/// assert_eq!(SOME_SIGNAL.peek(), 20); +/// assert_eq!(SOME_SIGNAL.peek_and(|x|x>&30), None); +/// }; +/// block_on(f); +/// ``` +pub struct MultiSignal<'a, M: RawMutex, T: Clone, const N: usize> { + mutex: Mutex>>, + _phantom: PhantomData<&'a ()>, +} + +struct MultiSignalState { + data: T, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +#[derive(Debug)] +/// An error that can occur when a `MultiSignal` returns a `Result`. +pub enum Error { + /// The maximum number of [`Receiver`](crate::multi_signal::Receiver) has been reached. + MaximumReceiversReached, +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { + /// Create a new `MultiSignal` initialized with the given value. + pub const fn new(init: T) -> Self { + Self { + mutex: Mutex::new(RefCell::new(MultiSignalState { + data: init, + current_id: 1, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + _phantom: PhantomData, + } + } + + /// Get a [`Receiver`] for the `MultiSignal`. + pub fn receiver(&'a self) -> Result, Error> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Ok(Receiver(Rcv::new(self))) + } else { + Err(Error::MaximumReceiversReached) + } + }) + } + + /// Update the value of the `MultiSignal`. + pub fn write(&self, data: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = data; + s.current_id += 1; + s.wakers.wake(); + }) + } + + /// Peek the current value of the `MultiSignal`. + pub fn peek(&self) -> T { + self.mutex.lock(|state| state.borrow().data.clone()) + } + + /// Peek the current value of the `MultiSignal` and check if it satisfies the predicate `f`. + pub fn peek_and(&self, f: fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if f(&s.data) { + Some(s.data.clone()) + } else { + None + } + }) + } + + /// Get the ID of the current value of the `MultiSignal`. + /// This method is mostly for testing purposes. + #[allow(dead_code)] + fn get_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Poll the `MultiSignal` with an optional context. + fn get_with_context(&'a self, waker: &mut Rcv<'a, M, T, N>, cx: Option<&mut Context>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (s.current_id > waker.at_id, waker.predicate) { + (true, None) => { + waker.at_id = s.current_id; + Poll::Ready(s.data.clone()) + } + (true, Some(f)) if f(&s.data) => { + waker.at_id = s.current_id; + Poll::Ready(s.data.clone()) + } + _ => { + if let Some(cx) = cx { + s.wakers.register(cx.waker()); + } + Poll::Pending + } + } + }) + } +} + +/// A receiver is able to `.await` a changed `MultiSignal` value. +pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { + multi_sig: &'a MultiSignal<'a, M, T, N>, + predicate: Option bool>, + at_id: u64, +} + +// f: Option bool> +impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { + /// Create a new `Receiver` with a reference the given `MultiSignal`. + fn new(multi_sig: &'a MultiSignal<'a, M, T, N>) -> Self { + Self { + multi_sig, + predicate: None, + at_id: 0, + } + } + + /// Wait for a change to the value of the corresponding `MultiSignal`. + pub fn changed<'s>(&'s mut self) -> ReceiverFuture<'s, 'a, M, T, N> { + self.predicate = None; + ReceiverFuture { subscriber: self } + } + + /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. + pub fn changed_and<'s>(&'s mut self, f: fn(&T) -> bool) -> ReceiverFuture<'s, 'a, M, T, N> { + self.predicate = Some(f); + ReceiverFuture { subscriber: self } + } + + /// Try to get a changed value of the corresponding `MultiSignal`. + pub fn try_changed(&mut self) -> Option { + self.multi_sig.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > self.at_id { + true => { + self.at_id = s.current_id; + Some(s.data.clone()) + } + false => None, + } + }) + } + + /// Try to get a changed value of the corresponding `MultiSignal` which matches the predicate `f`. + pub fn try_changed_and(&mut self, f: fn(&T) -> bool) -> Option { + self.multi_sig.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > self.at_id && f(&s.data) { + true => { + self.at_id = s.current_id; + Some(s.data.clone()) + } + false => None, + } + }) + } + + /// Peek the current value of the corresponding `MultiSignal`. + pub fn peek(&self) -> T { + self.multi_sig.peek() + } + + /// Peek the current value of the corresponding `MultiSignal` and check if it satisfies the predicate `f`. + pub fn peek_and(&self, f: fn(&T) -> bool) -> Option { + self.multi_sig.peek_and(f) + } + + /// Check if the value of the corresponding `MultiSignal` has changed. + pub fn has_changed(&mut self) -> bool { + self.multi_sig + .mutex + .lock(|state| state.borrow().current_id > self.at_id) + } +} + +/// A `Receiver` is able to `.await` a change to the corresponding [`MultiSignal`] value. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, M, T, N>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, M, T, N>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Future for the `Receiver` wait action +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiverFuture<'s, 'a, M: RawMutex, T: Clone, const N: usize> { + subscriber: &'s mut Rcv<'a, M, T, N>, +} + +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Future for ReceiverFuture<'s, 'a, M, T, N> { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.subscriber + .multi_sig + .get_with_context(&mut self.subscriber, Some(cx)) + } +} + +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Unpin for ReceiverFuture<'s, 'a, M, T, N> {} -- cgit From 410c2d440afa2a500ef1398b5b48e746f77815bd Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Tue, 13 Feb 2024 15:37:07 +0100 Subject: Change import formatting --- embassy-sync/src/multi_signal.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs index db858f269..bff7ad048 100644 --- a/embassy-sync/src/multi_signal.rs +++ b/embassy-sync/src/multi_signal.rs @@ -1,18 +1,15 @@ //! A synchronization primitive for passing the latest value to **multiple** tasks. -use core::{ - cell::RefCell, - marker::PhantomData, - ops::{Deref, DerefMut}, - pin::Pin, - task::{Context, Poll}, -}; +use core::cell::RefCell; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::task::{Context, Poll}; use futures_util::Future; -use crate::{ - blocking_mutex::{raw::RawMutex, Mutex}, - waitqueue::MultiWakerRegistration, -}; +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; /// A `MultiSignal` is a single-slot signaling primitive, which can awake `N` separate [`Receiver`]s. /// -- cgit From 37f1c9ac27b0542fdf404392e9bb265fa8ec41d3 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Tue, 13 Feb 2024 23:14:16 +0100 Subject: Removed unused lifetime, change most fn -> FnMut --- embassy-sync/src/multi_signal.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs index bff7ad048..5f724c76b 100644 --- a/embassy-sync/src/multi_signal.rs +++ b/embassy-sync/src/multi_signal.rs @@ -1,6 +1,5 @@ //! A synchronization primitive for passing the latest value to **multiple** tasks. use core::cell::RefCell; -use core::marker::PhantomData; use core::ops::{Deref, DerefMut}; use core::pin::Pin; use core::task::{Context, Poll}; @@ -66,9 +65,8 @@ use crate::waitqueue::MultiWakerRegistration; /// }; /// block_on(f); /// ``` -pub struct MultiSignal<'a, M: RawMutex, T: Clone, const N: usize> { +pub struct MultiSignal { mutex: Mutex>>, - _phantom: PhantomData<&'a ()>, } struct MultiSignalState { @@ -85,7 +83,7 @@ pub enum Error { MaximumReceiversReached, } -impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { +impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal { /// Create a new `MultiSignal` initialized with the given value. pub const fn new(init: T) -> Self { Self { @@ -95,7 +93,6 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { wakers: MultiWakerRegistration::new(), receiver_count: 0, })), - _phantom: PhantomData, } } @@ -128,7 +125,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { } /// Peek the current value of the `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, f: fn(&T) -> bool) -> Option { + pub fn peek_and(&self, mut f: impl FnMut(&T) -> bool) -> Option { self.mutex.lock(|state| { let s = state.borrow(); if f(&s.data) { @@ -147,16 +144,16 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { } /// Poll the `MultiSignal` with an optional context. - fn get_with_context(&'a self, waker: &mut Rcv<'a, M, T, N>, cx: Option<&mut Context>) -> Poll { + fn get_with_context(&self, rcv: &mut Rcv<'a, M, T, N>, cx: Option<&mut Context>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); - match (s.current_id > waker.at_id, waker.predicate) { + match (s.current_id > rcv.at_id, rcv.predicate) { (true, None) => { - waker.at_id = s.current_id; + rcv.at_id = s.current_id; Poll::Ready(s.data.clone()) } (true, Some(f)) if f(&s.data) => { - waker.at_id = s.current_id; + rcv.at_id = s.current_id; Poll::Ready(s.data.clone()) } _ => { @@ -172,7 +169,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { /// A receiver is able to `.await` a changed `MultiSignal` value. pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { - multi_sig: &'a MultiSignal<'a, M, T, N>, + multi_sig: &'a MultiSignal, predicate: Option bool>, at_id: u64, } @@ -180,7 +177,7 @@ pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { // f: Option bool> impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { /// Create a new `Receiver` with a reference the given `MultiSignal`. - fn new(multi_sig: &'a MultiSignal<'a, M, T, N>) -> Self { + fn new(multi_sig: &'a MultiSignal) -> Self { Self { multi_sig, predicate: None, @@ -195,6 +192,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. + // TODO: How do we make this work with a FnMut closure? pub fn changed_and<'s>(&'s mut self, f: fn(&T) -> bool) -> ReceiverFuture<'s, 'a, M, T, N> { self.predicate = Some(f); ReceiverFuture { subscriber: self } @@ -215,7 +213,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Try to get a changed value of the corresponding `MultiSignal` which matches the predicate `f`. - pub fn try_changed_and(&mut self, f: fn(&T) -> bool) -> Option { + pub fn try_changed_and(&mut self, mut f: impl FnMut(&T) -> bool) -> Option { self.multi_sig.mutex.lock(|state| { let s = state.borrow(); match s.current_id > self.at_id && f(&s.data) { @@ -234,7 +232,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Peek the current value of the corresponding `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, f: fn(&T) -> bool) -> Option { + pub fn peek_and(&self, f: impl FnMut(&T) -> bool) -> Option { self.multi_sig.peek_and(f) } -- cgit From 24a4379832d387754d407b77ff7aac5e55401eb3 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Wed, 14 Feb 2024 01:23:11 +0100 Subject: Got closures to work in async, added bunch of tests --- embassy-sync/src/multi_signal.rs | 340 +++++++++++++++++++++++++++++++++------ 1 file changed, 292 insertions(+), 48 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs index 5f724c76b..1481dc8f8 100644 --- a/embassy-sync/src/multi_signal.rs +++ b/embassy-sync/src/multi_signal.rs @@ -97,7 +97,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal { } /// Get a [`Receiver`] for the `MultiSignal`. - pub fn receiver(&'a self) -> Result, Error> { + pub fn receiver<'s>(&'a self) -> Result, Error> { self.mutex.lock(|state| { let mut s = state.borrow_mut(); if s.receiver_count < N { @@ -142,60 +142,36 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal { fn get_id(&self) -> u64 { self.mutex.lock(|state| state.borrow().current_id) } - - /// Poll the `MultiSignal` with an optional context. - fn get_with_context(&self, rcv: &mut Rcv<'a, M, T, N>, cx: Option<&mut Context>) -> Poll { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match (s.current_id > rcv.at_id, rcv.predicate) { - (true, None) => { - rcv.at_id = s.current_id; - Poll::Ready(s.data.clone()) - } - (true, Some(f)) if f(&s.data) => { - rcv.at_id = s.current_id; - Poll::Ready(s.data.clone()) - } - _ => { - if let Some(cx) = cx { - s.wakers.register(cx.waker()); - } - Poll::Pending - } - } - }) - } } /// A receiver is able to `.await` a changed `MultiSignal` value. pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { multi_sig: &'a MultiSignal, - predicate: Option bool>, at_id: u64, } -// f: Option bool> -impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { /// Create a new `Receiver` with a reference the given `MultiSignal`. fn new(multi_sig: &'a MultiSignal) -> Self { - Self { - multi_sig, - predicate: None, - at_id: 0, - } + Self { multi_sig, at_id: 0 } } /// Wait for a change to the value of the corresponding `MultiSignal`. - pub fn changed<'s>(&'s mut self) -> ReceiverFuture<'s, 'a, M, T, N> { - self.predicate = None; - ReceiverFuture { subscriber: self } + pub async fn changed(&mut self) -> T { + ReceiverWaitFuture { subscriber: self }.await } /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. // TODO: How do we make this work with a FnMut closure? - pub fn changed_and<'s>(&'s mut self, f: fn(&T) -> bool) -> ReceiverFuture<'s, 'a, M, T, N> { - self.predicate = Some(f); - ReceiverFuture { subscriber: self } + pub async fn changed_and(&mut self, f: F) -> T + where + F: FnMut(&T) -> bool, + { + ReceiverPredFuture { + subscriber: self, + predicate: f, + } + .await } /// Try to get a changed value of the corresponding `MultiSignal`. @@ -213,7 +189,10 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Try to get a changed value of the corresponding `MultiSignal` which matches the predicate `f`. - pub fn try_changed_and(&mut self, mut f: impl FnMut(&T) -> bool) -> Option { + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: FnMut(&T) -> bool, + { self.multi_sig.mutex.lock(|state| { let s = state.borrow(); match s.current_id > self.at_id && f(&s.data) { @@ -232,7 +211,10 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Peek the current value of the corresponding `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, f: impl FnMut(&T) -> bool) -> Option { + pub fn peek_and(&self, f: F) -> Option + where + F: FnMut(&T) -> bool, + { self.multi_sig.peek_and(f) } @@ -247,7 +229,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { /// A `Receiver` is able to `.await` a change to the corresponding [`MultiSignal`] value. pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, M, T, N>); -impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { type Target = Rcv<'a, M, T, N>; fn deref(&self) -> &Self::Target { @@ -255,7 +237,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> } } -impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -263,18 +245,280 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, /// Future for the `Receiver` wait action #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct ReceiverFuture<'s, 'a, M: RawMutex, T: Clone, const N: usize> { +pub struct ReceiverWaitFuture<'s, 'a, M: RawMutex, T: Clone, const N: usize> { subscriber: &'s mut Rcv<'a, M, T, N>, } -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Future for ReceiverFuture<'s, 'a, M, T, N> { +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Unpin for ReceiverWaitFuture<'s, 'a, M, T, N> {} +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Future for ReceiverWaitFuture<'s, 'a, M, T, N> { type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.subscriber - .multi_sig - .get_with_context(&mut self.subscriber, Some(cx)) + self.get_with_context(Some(cx)) + } +} + +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> ReceiverWaitFuture<'s, 'a, M, T, N> { + /// Poll the `MultiSignal` with an optional context. + fn get_with_context(&mut self, cx: Option<&mut Context>) -> Poll { + self.subscriber.multi_sig.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.current_id > self.subscriber.at_id { + true => { + self.subscriber.at_id = s.current_id; + Poll::Ready(s.data.clone()) + } + _ => { + if let Some(cx) = cx { + s.wakers.register(cx.waker()); + } + Poll::Pending + } + } + }) + } +} + +/// Future for the `Receiver` wait action, with the ability to filter the value with a predicate. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiverPredFuture<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&'a T) -> bool, const N: usize> { + subscriber: &'s mut Rcv<'a, M, T, N>, + predicate: F, +} + +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Unpin for ReceiverPredFuture<'s, 'a, M, T, F, N> {} +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Future for ReceiverPredFuture<'s, 'a, M, T, F, N>{ + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.get_with_context_pred(Some(cx)) + } +} + +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> ReceiverPredFuture<'s, 'a, M, T, F, N> { + /// Poll the `MultiSignal` with an optional context. + fn get_with_context_pred(&mut self, cx: Option<&mut Context>) -> Poll { + self.subscriber.multi_sig.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.current_id > self.subscriber.at_id { + true if (self.predicate)(&s.data) => { + self.subscriber.at_id = s.current_id; + Poll::Ready(s.data.clone()) + } + _ => { + if let Some(cx) = cx { + s.wakers.register(cx.waker()); + } + Poll::Pending + } + } + }) } } -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Unpin for ReceiverFuture<'s, 'a, M, T, N> {} +#[cfg(test)] +mod tests { + use super::*; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + use futures_executor::block_on; + + #[test] + fn multiple_writes() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(10); + + // Receive the new value + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + + // No update + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + }; + block_on(f); + } + + #[test] + fn max_receivers() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let _ = SOME_SIGNAL.receiver().unwrap(); + let _ = SOME_SIGNAL.receiver().unwrap(); + assert!(SOME_SIGNAL.receiver().is_err()); + }; + block_on(f); + } + + // Really weird edge case, but it's possible to have a receiver that never gets a value. + #[test] + fn receive_initial() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn count_ids() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(10); + + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + SOME_SIGNAL.write(20); + SOME_SIGNAL.write(20); + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + assert_eq!(SOME_SIGNAL.get_id(), 5); + }; + block_on(f); + } + + #[test] + fn peek_still_await() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(10); + + assert_eq!(rcv0.peek(), 10); + assert_eq!(rcv1.peek(), 10); + + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + }; + block_on(f); + } + + #[test] + fn predicate() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.changed_and(|x| x > &10).await, 20); + assert_eq!(rcv1.try_changed_and(|x| x > &30), None); + }; + block_on(f); + } + + #[test] + fn mutable_predicate() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(10); + + let mut largest = 0; + let mut predicate = |x: &u8| { + if *x > largest { + largest = *x; + } + true + }; + + assert_eq!(rcv.changed_and(&mut predicate).await, 10); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv.changed_and(&mut predicate).await, 20); + + SOME_SIGNAL.write(5); + + assert_eq!(rcv.changed_and(&mut predicate).await, 5); + + assert_eq!(largest, 20) + }; + block_on(f); + } + + #[test] + fn peek_and() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.peek_and(|x| x > &10), Some(20)); + assert_eq!(rcv1.peek_and(|x| x > &30), None); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + }; + block_on(f); + } + + #[test] + fn peek_with_static() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let rcv0 = SOME_SIGNAL.receiver().unwrap(); + let rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.peek(), 20); + assert_eq!(rcv1.peek(), 20); + assert_eq!(SOME_SIGNAL.peek(), 20); + assert_eq!(SOME_SIGNAL.peek_and(|x| x > &30), None); + }; + block_on(f); + } +} -- cgit From 2f58d1968a7310335a0dac4d947c6972a7707ed5 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Wed, 14 Feb 2024 01:27:48 +0100 Subject: Updated formatting --- embassy-sync/src/multi_signal.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs index 1481dc8f8..ff9f72f2e 100644 --- a/embassy-sync/src/multi_signal.rs +++ b/embassy-sync/src/multi_signal.rs @@ -162,7 +162,6 @@ impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. - // TODO: How do we make this work with a FnMut closure? pub async fn changed_and(&mut self, f: F) -> T where F: FnMut(&T) -> bool, @@ -286,8 +285,13 @@ pub struct ReceiverPredFuture<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&'a T) -> predicate: F, } -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Unpin for ReceiverPredFuture<'s, 'a, M, T, F, N> {} -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Future for ReceiverPredFuture<'s, 'a, M, T, F, N>{ +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Unpin + for ReceiverPredFuture<'s, 'a, M, T, F, N> +{ +} +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Future + for ReceiverPredFuture<'s, 'a, M, T, F, N> +{ type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -318,9 +322,10 @@ impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Receiv #[cfg(test)] mod tests { + use futures_executor::block_on; + use super::*; use crate::blocking_mutex::raw::CriticalSectionRawMutex; - use futures_executor::block_on; #[test] fn multiple_writes() { -- cgit From 6defb4fed98432dee948634f3b2001cb4ea7ec5b Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Wed, 28 Feb 2024 20:59:38 +0100 Subject: Changed name to `Watch`, added `DynReceiver`, `get`-method and more reworks. --- embassy-sync/src/lib.rs | 2 +- embassy-sync/src/multi_signal.rs | 529 --------------------------------------- embassy-sync/src/watch.rs | 515 +++++++++++++++++++++++++++++++++++++ 3 files changed, 516 insertions(+), 530 deletions(-) delete mode 100644 embassy-sync/src/multi_signal.rs create mode 100644 embassy-sync/src/watch.rs (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index f02985564..8a69541a5 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -12,11 +12,11 @@ mod ring_buffer; pub mod blocking_mutex; pub mod channel; -pub mod multi_signal; pub mod mutex; pub mod pipe; pub mod priority_channel; pub mod pubsub; pub mod signal; pub mod waitqueue; +pub mod watch; pub mod zerocopy_channel; diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs deleted file mode 100644 index ff9f72f2e..000000000 --- a/embassy-sync/src/multi_signal.rs +++ /dev/null @@ -1,529 +0,0 @@ -//! A synchronization primitive for passing the latest value to **multiple** tasks. -use core::cell::RefCell; -use core::ops::{Deref, DerefMut}; -use core::pin::Pin; -use core::task::{Context, Poll}; - -use futures_util::Future; - -use crate::blocking_mutex::raw::RawMutex; -use crate::blocking_mutex::Mutex; -use crate::waitqueue::MultiWakerRegistration; - -/// A `MultiSignal` is a single-slot signaling primitive, which can awake `N` separate [`Receiver`]s. -/// -/// Similar to a [`Signal`](crate::signal::Signal), except `MultiSignal` allows for multiple tasks to -/// `.await` the latest value, and all receive it. -/// -/// This is similar to a [`PubSubChannel`](crate::pubsub::PubSubChannel) with a buffer size of 1, except -/// "sending" to it (calling [`MultiSignal::write`]) will immediately overwrite the previous value instead -/// of waiting for the receivers to pop the previous value. -/// -/// `MultiSignal` is useful when a single task is responsible for updating a value or "state", which multiple other -/// tasks are interested in getting notified about changes to the latest value of. It is therefore fine for -/// [`Receiver`]s to "lose" stale values. -/// -/// Anyone with a reference to the MultiSignal can update or peek the value. MultiSignals are generally declared -/// as `static`s and then borrowed as required to either [`MultiSignal::peek`] the value or obtain a [`Receiver`] -/// with [`MultiSignal::receiver`] which has async methods. -/// ``` -/// -/// use futures_executor::block_on; -/// use embassy_sync::multi_signal::MultiSignal; -/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -/// -/// let f = async { -/// -/// static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); -/// -/// // Obtain Receivers -/// let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); -/// let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); -/// assert!(SOME_SIGNAL.receiver().is_err()); -/// -/// SOME_SIGNAL.write(10); -/// -/// // Receive the new value -/// assert_eq!(rcv0.changed().await, 10); -/// assert_eq!(rcv1.try_changed(), Some(10)); -/// -/// // No update -/// assert_eq!(rcv0.try_changed(), None); -/// assert_eq!(rcv1.try_changed(), None); -/// -/// SOME_SIGNAL.write(20); -/// -/// // Receive new value with predicate -/// assert_eq!(rcv0.changed_and(|x|x>&10).await, 20); -/// assert_eq!(rcv1.try_changed_and(|x|x>&30), None); -/// -/// // Anyone can peek the current value -/// assert_eq!(rcv0.peek(), 20); -/// assert_eq!(rcv1.peek(), 20); -/// assert_eq!(SOME_SIGNAL.peek(), 20); -/// assert_eq!(SOME_SIGNAL.peek_and(|x|x>&30), None); -/// }; -/// block_on(f); -/// ``` -pub struct MultiSignal { - mutex: Mutex>>, -} - -struct MultiSignalState { - data: T, - current_id: u64, - wakers: MultiWakerRegistration, - receiver_count: usize, -} - -#[derive(Debug)] -/// An error that can occur when a `MultiSignal` returns a `Result`. -pub enum Error { - /// The maximum number of [`Receiver`](crate::multi_signal::Receiver) has been reached. - MaximumReceiversReached, -} - -impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal { - /// Create a new `MultiSignal` initialized with the given value. - pub const fn new(init: T) -> Self { - Self { - mutex: Mutex::new(RefCell::new(MultiSignalState { - data: init, - current_id: 1, - wakers: MultiWakerRegistration::new(), - receiver_count: 0, - })), - } - } - - /// Get a [`Receiver`] for the `MultiSignal`. - pub fn receiver<'s>(&'a self) -> Result, Error> { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - if s.receiver_count < N { - s.receiver_count += 1; - Ok(Receiver(Rcv::new(self))) - } else { - Err(Error::MaximumReceiversReached) - } - }) - } - - /// Update the value of the `MultiSignal`. - pub fn write(&self, data: T) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = data; - s.current_id += 1; - s.wakers.wake(); - }) - } - - /// Peek the current value of the `MultiSignal`. - pub fn peek(&self) -> T { - self.mutex.lock(|state| state.borrow().data.clone()) - } - - /// Peek the current value of the `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, mut f: impl FnMut(&T) -> bool) -> Option { - self.mutex.lock(|state| { - let s = state.borrow(); - if f(&s.data) { - Some(s.data.clone()) - } else { - None - } - }) - } - - /// Get the ID of the current value of the `MultiSignal`. - /// This method is mostly for testing purposes. - #[allow(dead_code)] - fn get_id(&self) -> u64 { - self.mutex.lock(|state| state.borrow().current_id) - } -} - -/// A receiver is able to `.await` a changed `MultiSignal` value. -pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { - multi_sig: &'a MultiSignal, - at_id: u64, -} - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { - /// Create a new `Receiver` with a reference the given `MultiSignal`. - fn new(multi_sig: &'a MultiSignal) -> Self { - Self { multi_sig, at_id: 0 } - } - - /// Wait for a change to the value of the corresponding `MultiSignal`. - pub async fn changed(&mut self) -> T { - ReceiverWaitFuture { subscriber: self }.await - } - - /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. - pub async fn changed_and(&mut self, f: F) -> T - where - F: FnMut(&T) -> bool, - { - ReceiverPredFuture { - subscriber: self, - predicate: f, - } - .await - } - - /// Try to get a changed value of the corresponding `MultiSignal`. - pub fn try_changed(&mut self) -> Option { - self.multi_sig.mutex.lock(|state| { - let s = state.borrow(); - match s.current_id > self.at_id { - true => { - self.at_id = s.current_id; - Some(s.data.clone()) - } - false => None, - } - }) - } - - /// Try to get a changed value of the corresponding `MultiSignal` which matches the predicate `f`. - pub fn try_changed_and(&mut self, mut f: F) -> Option - where - F: FnMut(&T) -> bool, - { - self.multi_sig.mutex.lock(|state| { - let s = state.borrow(); - match s.current_id > self.at_id && f(&s.data) { - true => { - self.at_id = s.current_id; - Some(s.data.clone()) - } - false => None, - } - }) - } - - /// Peek the current value of the corresponding `MultiSignal`. - pub fn peek(&self) -> T { - self.multi_sig.peek() - } - - /// Peek the current value of the corresponding `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, f: F) -> Option - where - F: FnMut(&T) -> bool, - { - self.multi_sig.peek_and(f) - } - - /// Check if the value of the corresponding `MultiSignal` has changed. - pub fn has_changed(&mut self) -> bool { - self.multi_sig - .mutex - .lock(|state| state.borrow().current_id > self.at_id) - } -} - -/// A `Receiver` is able to `.await` a change to the corresponding [`MultiSignal`] value. -pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, M, T, N>); - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { - type Target = Rcv<'a, M, T, N>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Future for the `Receiver` wait action -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct ReceiverWaitFuture<'s, 'a, M: RawMutex, T: Clone, const N: usize> { - subscriber: &'s mut Rcv<'a, M, T, N>, -} - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Unpin for ReceiverWaitFuture<'s, 'a, M, T, N> {} -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Future for ReceiverWaitFuture<'s, 'a, M, T, N> { - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.get_with_context(Some(cx)) - } -} - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> ReceiverWaitFuture<'s, 'a, M, T, N> { - /// Poll the `MultiSignal` with an optional context. - fn get_with_context(&mut self, cx: Option<&mut Context>) -> Poll { - self.subscriber.multi_sig.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match s.current_id > self.subscriber.at_id { - true => { - self.subscriber.at_id = s.current_id; - Poll::Ready(s.data.clone()) - } - _ => { - if let Some(cx) = cx { - s.wakers.register(cx.waker()); - } - Poll::Pending - } - } - }) - } -} - -/// Future for the `Receiver` wait action, with the ability to filter the value with a predicate. -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct ReceiverPredFuture<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&'a T) -> bool, const N: usize> { - subscriber: &'s mut Rcv<'a, M, T, N>, - predicate: F, -} - -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Unpin - for ReceiverPredFuture<'s, 'a, M, T, F, N> -{ -} -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Future - for ReceiverPredFuture<'s, 'a, M, T, F, N> -{ - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.get_with_context_pred(Some(cx)) - } -} - -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> ReceiverPredFuture<'s, 'a, M, T, F, N> { - /// Poll the `MultiSignal` with an optional context. - fn get_with_context_pred(&mut self, cx: Option<&mut Context>) -> Poll { - self.subscriber.multi_sig.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match s.current_id > self.subscriber.at_id { - true if (self.predicate)(&s.data) => { - self.subscriber.at_id = s.current_id; - Poll::Ready(s.data.clone()) - } - _ => { - if let Some(cx) = cx { - s.wakers.register(cx.waker()); - } - Poll::Pending - } - } - }) - } -} - -#[cfg(test)] -mod tests { - use futures_executor::block_on; - - use super::*; - use crate::blocking_mutex::raw::CriticalSectionRawMutex; - - #[test] - fn multiple_writes() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(10); - - // Receive the new value - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); - - // No update - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); - }; - block_on(f); - } - - #[test] - fn max_receivers() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let _ = SOME_SIGNAL.receiver().unwrap(); - let _ = SOME_SIGNAL.receiver().unwrap(); - assert!(SOME_SIGNAL.receiver().is_err()); - }; - block_on(f); - } - - // Really weird edge case, but it's possible to have a receiver that never gets a value. - #[test] - fn receive_initial() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - assert_eq!(rcv0.try_changed(), Some(0)); - assert_eq!(rcv1.try_changed(), Some(0)); - - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - }; - block_on(f); - } - - #[test] - fn count_ids() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(10); - - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); - - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - SOME_SIGNAL.write(20); - SOME_SIGNAL.write(20); - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); - - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - assert_eq!(SOME_SIGNAL.get_id(), 5); - }; - block_on(f); - } - - #[test] - fn peek_still_await() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(10); - - assert_eq!(rcv0.peek(), 10); - assert_eq!(rcv1.peek(), 10); - - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); - }; - block_on(f); - } - - #[test] - fn predicate() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.changed_and(|x| x > &10).await, 20); - assert_eq!(rcv1.try_changed_and(|x| x > &30), None); - }; - block_on(f); - } - - #[test] - fn mutable_predicate() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(10); - - let mut largest = 0; - let mut predicate = |x: &u8| { - if *x > largest { - largest = *x; - } - true - }; - - assert_eq!(rcv.changed_and(&mut predicate).await, 10); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv.changed_and(&mut predicate).await, 20); - - SOME_SIGNAL.write(5); - - assert_eq!(rcv.changed_and(&mut predicate).await, 5); - - assert_eq!(largest, 20) - }; - block_on(f); - } - - #[test] - fn peek_and() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.peek_and(|x| x > &10), Some(20)); - assert_eq!(rcv1.peek_and(|x| x > &30), None); - - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); - }; - block_on(f); - } - - #[test] - fn peek_with_static() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let rcv0 = SOME_SIGNAL.receiver().unwrap(); - let rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.peek(), 20); - assert_eq!(rcv1.peek(), 20); - assert_eq!(SOME_SIGNAL.peek(), 20); - assert_eq!(SOME_SIGNAL.peek_and(|x| x > &30), None); - }; - block_on(f); - } -} diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs new file mode 100644 index 000000000..7e5e92741 --- /dev/null +++ b/embassy-sync/src/watch.rs @@ -0,0 +1,515 @@ +//! A synchronization primitive for passing the latest value to **multiple** tasks. + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +/// A `Watch` is a single-slot signaling primitive, which can awake `N` up to separate [`Receiver`]s. +/// +/// Similar to a [`Signal`](crate::signal::Signal), except `Watch` allows for multiple tasks to +/// `.await` the latest value, and all receive it. +/// +/// This is similar to a [`PubSubChannel`](crate::pubsub::PubSubChannel) with a buffer size of 1, except +/// "sending" to it (calling [`Watch::write`]) will immediately overwrite the previous value instead +/// of waiting for the receivers to pop the previous value. +/// +/// `Watch` is useful when a single task is responsible for updating a value or "state", which multiple other +/// tasks are interested in getting notified about changes to the latest value of. It is therefore fine for +/// [`Receiver`]s to "lose" stale values. +/// +/// Anyone with a reference to the Watch can update or peek the value. Watches are generally declared +/// as `static`s and then borrowed as required to either [`Watch::peek`] the value or obtain a [`Receiver`] +/// with [`Watch::receiver`] which has async methods. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::watch::Watch; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static WATCH: Watch = Watch::new(); +/// +/// // Obtain Receivers +/// let mut rcv0 = WATCH.receiver().unwrap(); +/// let mut rcv1 = WATCH.receiver().unwrap(); +/// assert!(WATCH.receiver().is_err()); +/// +/// assert_eq!(rcv1.try_changed(), None); +/// +/// WATCH.write(10); +/// assert_eq!(WATCH.try_peek(), Some(10)); +/// +/// +/// // Receive the new value +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// WATCH.write(20); +/// +/// // Defference `between` peek `get`. +/// assert_eq!(rcv0.peek().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// +/// assert_eq!(rcv0.try_changed(), Some(20)); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// }; +/// block_on(f); +/// ``` +pub struct Watch { + mutex: Mutex>>, +} + +struct WatchState { + data: Option, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +/// A trait representing the 'inner' behavior of the `Watch`. +pub trait WatchBehavior { + /// Poll the `Watch` for the current value, **without** making it as seen. + fn inner_poll_peek(&self, cx: &mut Context<'_>) -> Poll; + + /// Tries to peek the value of the `Watch`, **without** marking it as seen. + fn inner_try_peek(&self) -> Option; + + /// Poll the `Watch` for the current value, making it as seen. + fn inner_poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to get the value of the `Watch`, marking it as seen. + fn inner_try_get(&self, id: &mut u64) -> Option; + + /// Poll the `Watch` for a changed value, marking it as seen. + fn inner_poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. + fn inner_try_changed(&self, id: &mut u64) -> Option; + + /// Checks if the `Watch` is been initialized with a value. + fn inner_contains_value(&self) -> bool; +} + +impl WatchBehavior for Watch { + fn inner_poll_peek(&self, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => Poll::Ready(data.clone()), + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn inner_try_peek(&self) -> Option { + self.mutex.lock(|state| state.borrow().data.clone()) + } + + fn inner_poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn inner_try_get(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + *id = s.current_id; + state.borrow().data.clone() + }) + } + + fn inner_poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn inner_try_changed(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > *id { + true => { + *id = s.current_id; + state.borrow().data.clone() + } + false => None, + } + }) + } + + fn inner_contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + +#[derive(Debug)] +/// An error that can occur when a `Watch` returns a `Result::Err(_)`. +pub enum Error { + /// The maximum number of [`Receiver`](crate::watch::Receiver)/[`DynReceiver`](crate::watch::DynReceiver) has been reached. + MaximumReceiversReached, +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { + /// Create a new `Watch` channel. + pub const fn new() -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: None, + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Write a new value to the `Watch`. + pub fn write(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + /// Create a new [`Receiver`] for the `Watch`. + pub fn receiver(&self) -> Result, Error> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Ok(Receiver(Rcv::new(self))) + } else { + Err(Error::MaximumReceiversReached) + } + }) + } + + /// Create a new [`DynReceiver`] for the `Watch`. + pub fn dyn_receiver(&self) -> Result, Error> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Ok(DynReceiver(Rcv::new(self))) + } else { + Err(Error::MaximumReceiversReached) + } + }) + } + + /// Tries to retrieve the value of the `Watch`. + pub fn try_peek(&self) -> Option { + self.inner_try_peek() + } + + /// Returns true if the `Watch` contains a value. + pub fn contains_value(&self) -> bool { + self.inner_contains_value() + } + + /// Clears the value of the `Watch`. This will cause calls to [`Rcv::get`] and [`Rcv::peek`] to be pending. + pub fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + at_id: 0, + _phantom: PhantomData, + } + } + + /// Returns the current value of the `Watch` if it is initialized, **without** marking it as seen. + pub async fn peek(&self) -> T { + poll_fn(|cx| self.watch.inner_poll_peek(cx)).await + } + + /// Tries to peek the current value of the `Watch` without waiting, and **without** marking it as seen. + pub fn try_peek(&self) -> Option { + self.watch.inner_try_peek() + } + + /// Returns the current value of the `Watch` if it is initialized, marking it as seen. + pub async fn get(&mut self) -> T { + poll_fn(|cx| self.watch.inner_poll_get(&mut self.at_id, cx)).await + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.inner_try_get(&mut self.at_id) + } + + /// Waits for the `Watch` to change and returns the new value, marking it as seen. + pub async fn changed(&mut self) -> T { + poll_fn(|cx| self.watch.inner_poll_changed(&mut self.at_id, cx)).await + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.inner_try_changed(&mut self.at_id) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] and [`Rcv::peek`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.inner_contains_value() + } +} + +/// A receiver of a `Watch` channel. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); + +/// A receiver which holds a **reference** to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a, T: Clone> Deref for DynReceiver<'a, T> { + type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + use futures_executor::block_on; + + use super::*; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + + #[test] + fn multiple_writes() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.dyn_receiver().unwrap(); + + WATCH.write(10); + + // Receive the new value + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + + // No update + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + WATCH.write(20); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + }; + block_on(f); + } + + #[test] + fn max_receivers() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let _ = WATCH.receiver().unwrap(); + let _ = WATCH.receiver().unwrap(); + assert!(WATCH.receiver().is_err()); + }; + block_on(f); + } + + #[test] + fn receive_initial() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.receiver().unwrap(); + + assert_eq!(rcv0.contains_value(), false); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + WATCH.write(0); + + assert_eq!(rcv0.contains_value(), true); + + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + }; + block_on(f); + } + + #[test] + fn peek_get_changed() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + + WATCH.write(10); + + // Ensure peek does not mark as seen + assert_eq!(rcv0.peek().await, 10); + assert_eq!(rcv0.try_changed(), Some(10)); + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv0.peek().await, 10); + + WATCH.write(20); + + // Ensure get does mark as seen + assert_eq!(rcv0.get().await, 20); + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv0.try_get(), Some(20)); + }; + block_on(f); + } + + #[test] + fn count_ids() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.receiver().unwrap(); + + let get_id = || WATCH.mutex.lock(|state| state.borrow().current_id); + + WATCH.write(10); + + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + WATCH.write(20); + WATCH.write(20); + WATCH.write(20); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + assert_eq!(get_id(), 4); + }; + block_on(f); + } + + #[test] + fn peek_still_await() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.receiver().unwrap(); + + WATCH.write(10); + + assert_eq!(rcv0.peek().await, 10); + assert_eq!(rcv1.try_peek(), Some(10)); + + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + }; + block_on(f); + } + + #[test] + fn peek_with_static() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let rcv0 = WATCH.receiver().unwrap(); + let rcv1 = WATCH.receiver().unwrap(); + + WATCH.write(20); + + assert_eq!(rcv0.peek().await, 20); + assert_eq!(rcv1.peek().await, 20); + assert_eq!(WATCH.try_peek(), Some(20)); + }; + block_on(f); + } +} -- cgit From ae2f10992149279884ea564b00eb18c8bf1f464e Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 29 Feb 2024 16:45:44 +0100 Subject: Added sender types, support for dropping receivers, converting to dyn-types, revised tests. --- embassy-sync/src/watch.rs | 521 +++++++++++++++++++++++++++++++++------------- 1 file changed, 374 insertions(+), 147 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 7e5e92741..3e22b1e7b 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -1,4 +1,4 @@ -//! A synchronization primitive for passing the latest value to **multiple** tasks. +//! A synchronization primitive for passing the latest value to **multiple** receivers. use core::cell::RefCell; use core::future::poll_fn; @@ -10,22 +10,17 @@ use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; use crate::waitqueue::MultiWakerRegistration; -/// A `Watch` is a single-slot signaling primitive, which can awake `N` up to separate [`Receiver`]s. +/// The `Watch` is a single-slot signaling primitive that allows multiple receivers to concurrently await +/// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, +/// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous +/// value when a new one is sent, without waiting for all receivers to read the previous value. /// -/// Similar to a [`Signal`](crate::signal::Signal), except `Watch` allows for multiple tasks to -/// `.await` the latest value, and all receive it. +/// This makes `Watch` particularly useful when a single task updates a value or "state", and multiple other tasks +/// need to be notified about changes to this value asynchronously. Receivers may "lose" stale values, as they are +/// always provided with the latest value. /// -/// This is similar to a [`PubSubChannel`](crate::pubsub::PubSubChannel) with a buffer size of 1, except -/// "sending" to it (calling [`Watch::write`]) will immediately overwrite the previous value instead -/// of waiting for the receivers to pop the previous value. -/// -/// `Watch` is useful when a single task is responsible for updating a value or "state", which multiple other -/// tasks are interested in getting notified about changes to the latest value of. It is therefore fine for -/// [`Receiver`]s to "lose" stale values. -/// -/// Anyone with a reference to the Watch can update or peek the value. Watches are generally declared -/// as `static`s and then borrowed as required to either [`Watch::peek`] the value or obtain a [`Receiver`] -/// with [`Watch::receiver`] which has async methods. +/// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained and passed to the relevant parts of the program. /// ``` /// /// use futures_executor::block_on; @@ -36,18 +31,18 @@ use crate::waitqueue::MultiWakerRegistration; /// /// static WATCH: Watch = Watch::new(); /// -/// // Obtain Receivers +/// // Obtain receivers and sender /// let mut rcv0 = WATCH.receiver().unwrap(); -/// let mut rcv1 = WATCH.receiver().unwrap(); -/// assert!(WATCH.receiver().is_err()); +/// let mut rcv1 = WATCH.dyn_receiver().unwrap(); +/// let mut snd = WATCH.sender(); /// +/// // No more receivers, and no update +/// assert!(WATCH.receiver().is_err()); /// assert_eq!(rcv1.try_changed(), None); /// -/// WATCH.write(10); -/// assert_eq!(WATCH.try_peek(), Some(10)); -/// +/// snd.send(10); /// -/// // Receive the new value +/// // Receive the new value (async or try) /// assert_eq!(rcv0.changed().await, 10); /// assert_eq!(rcv1.try_changed(), Some(10)); /// @@ -55,13 +50,14 @@ use crate::waitqueue::MultiWakerRegistration; /// assert_eq!(rcv0.try_changed(), None); /// assert_eq!(rcv1.try_changed(), None); /// -/// WATCH.write(20); +/// snd.send(20); /// -/// // Defference `between` peek `get`. +/// // Peek does not mark the value as seen /// assert_eq!(rcv0.peek().await, 20); -/// assert_eq!(rcv1.get().await, 20); -/// /// assert_eq!(rcv0.try_changed(), Some(20)); +/// +/// // Get marks the value as seen +/// assert_eq!(rcv1.get().await, 20); /// assert_eq!(rcv1.try_changed(), None); /// /// }; @@ -80,30 +76,57 @@ struct WatchState { /// A trait representing the 'inner' behavior of the `Watch`. pub trait WatchBehavior { + /// Sends a new value to the `Watch`. + fn send(&self, val: T); + + /// Clears the value of the `Watch`. + fn clear(&self); + /// Poll the `Watch` for the current value, **without** making it as seen. - fn inner_poll_peek(&self, cx: &mut Context<'_>) -> Poll; + fn poll_peek(&self, cx: &mut Context<'_>) -> Poll; /// Tries to peek the value of the `Watch`, **without** marking it as seen. - fn inner_try_peek(&self) -> Option; + fn try_peek(&self) -> Option; /// Poll the `Watch` for the current value, making it as seen. - fn inner_poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to get the value of the `Watch`, marking it as seen. - fn inner_try_get(&self, id: &mut u64) -> Option; + fn try_get(&self, id: &mut u64) -> Option; /// Poll the `Watch` for a changed value, marking it as seen. - fn inner_poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. - fn inner_try_changed(&self, id: &mut u64) -> Option; + fn try_changed(&self, id: &mut u64) -> Option; /// Checks if the `Watch` is been initialized with a value. - fn inner_contains_value(&self) -> bool; + fn contains_value(&self) -> bool; + + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); } impl WatchBehavior for Watch { - fn inner_poll_peek(&self, cx: &mut Context<'_>) -> Poll { + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn poll_peek(&self, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); match &s.data { @@ -116,11 +139,11 @@ impl WatchBehavior for Watch }) } - fn inner_try_peek(&self) -> Option { + fn try_peek(&self) -> Option { self.mutex.lock(|state| state.borrow().data.clone()) } - fn inner_poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); match &s.data { @@ -136,7 +159,7 @@ impl WatchBehavior for Watch }) } - fn inner_try_get(&self, id: &mut u64) -> Option { + fn try_get(&self, id: &mut u64) -> Option { self.mutex.lock(|state| { let s = state.borrow(); *id = s.current_id; @@ -144,7 +167,7 @@ impl WatchBehavior for Watch }) } - fn inner_poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); match (&s.data, s.current_id > *id) { @@ -160,7 +183,7 @@ impl WatchBehavior for Watch }) } - fn inner_try_changed(&self, id: &mut u64) -> Option { + fn try_changed(&self, id: &mut u64) -> Option { self.mutex.lock(|state| { let s = state.borrow(); match s.current_id > *id { @@ -173,9 +196,16 @@ impl WatchBehavior for Watch }) } - fn inner_contains_value(&self) -> bool { + fn contains_value(&self) -> bool { self.mutex.lock(|state| state.borrow().data.is_some()) } + + fn drop_receiver(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.receiver_count -= 1; + }) + } } #[derive(Debug)] @@ -198,14 +228,14 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { } } - /// Write a new value to the `Watch`. - pub fn write(&self, val: T) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = Some(val); - s.current_id += 1; - s.wakers.wake(); - }) + /// Create a new [`Receiver`] for the `Watch`. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender(Snd::new(self)) + } + + /// Create a new [`DynReceiver`] for the `Watch`. + pub fn dyn_sender(&self) -> DynSender<'_, T> { + DynSender(Snd::new(self)) } /// Create a new [`Receiver`] for the `Watch`. @@ -214,7 +244,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { let mut s = state.borrow_mut(); if s.receiver_count < N { s.receiver_count += 1; - Ok(Receiver(Rcv::new(self))) + Ok(Receiver(Rcv::new(self, 0))) } else { Err(Error::MaximumReceiversReached) } @@ -227,29 +257,121 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { let mut s = state.borrow_mut(); if s.receiver_count < N { s.receiver_count += 1; - Ok(DynReceiver(Rcv::new(self))) + Ok(DynReceiver(Rcv::new(self, 0))) } else { Err(Error::MaximumReceiversReached) } }) } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Snd<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Clone for Snd<'a, T, W> { + fn clone(&self) -> Self { + Self { + watch: self.watch, + _phantom: PhantomData, + } + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + _phantom: PhantomData, + } + } + + /// Sends a new value to the `Watch`. + pub fn send(&self, val: T) { + self.watch.send(val) + } + + /// Clears the value of the `Watch`. + /// This will cause calls to [`Rcv::get`] and [`Rcv::peek`] to be pending. + pub fn clear(&self) { + self.watch.clear() + } /// Tries to retrieve the value of the `Watch`. pub fn try_peek(&self) -> Option { - self.inner_try_peek() + self.watch.try_peek() } /// Returns true if the `Watch` contains a value. pub fn contains_value(&self) -> bool { - self.inner_contains_value() + self.watch.contains_value() } +} - /// Clears the value of the `Watch`. This will cause calls to [`Rcv::get`] and [`Rcv::peek`] to be pending. - pub fn clear(&self) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = None; - }) +/// A sender of a `Watch` channel. +/// +/// For a simpler type definition, consider [`DynSender`] at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct Sender<'a, M: RawMutex, T: Clone, const N: usize>(Snd<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Clone for Sender<'a, M, T, N> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Sender<'a, M, T, N> { + /// Converts the `Sender` into a [`DynSender`]. + pub fn as_dyn(self) -> DynSender<'a, T> { + DynSender(Snd::new(self.watch)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Sender<'a, M, T, N> { + fn into(self) -> DynSender<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Sender<'a, M, T, N> { + type Target = Snd<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Sender<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A sender which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Sender`] with a simpler type definition, +pub struct DynSender<'a, T: Clone>(Snd<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Clone for DynSender<'a, T> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, T: Clone> Deref for DynSender<'a, T> { + type Target = Snd<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSender<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } @@ -262,59 +384,83 @@ pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { /// Creates a new `Receiver` with a reference to the `Watch`. - fn new(watch: &'a W) -> Self { + fn new(watch: &'a W, at_id: u64) -> Self { Self { watch, - at_id: 0, + at_id, _phantom: PhantomData, } } - /// Returns the current value of the `Watch` if it is initialized, **without** marking it as seen. + /// Returns the current value of the `Watch` once it is initialized, **without** marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. pub async fn peek(&self) -> T { - poll_fn(|cx| self.watch.inner_poll_peek(cx)).await + poll_fn(|cx| self.watch.poll_peek(cx)).await } /// Tries to peek the current value of the `Watch` without waiting, and **without** marking it as seen. pub fn try_peek(&self) -> Option { - self.watch.inner_try_peek() + self.watch.try_peek() } - /// Returns the current value of the `Watch` if it is initialized, marking it as seen. + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. pub async fn get(&mut self) -> T { - poll_fn(|cx| self.watch.inner_poll_get(&mut self.at_id, cx)).await + poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)).await } /// Tries to get the current value of the `Watch` without waiting, marking it as seen. pub fn try_get(&mut self) -> Option { - self.watch.inner_try_get(&mut self.at_id) + self.watch.try_get(&mut self.at_id) } /// Waits for the `Watch` to change and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. pub async fn changed(&mut self) -> T { - poll_fn(|cx| self.watch.inner_poll_changed(&mut self.at_id, cx)).await + poll_fn(|cx| self.watch.poll_changed(&mut self.at_id, cx)).await } /// Tries to get the new value of the watch without waiting, marking it as seen. pub fn try_changed(&mut self) -> Option { - self.watch.inner_try_changed(&mut self.at_id) + self.watch.try_changed(&mut self.at_id) } /// Checks if the `Watch` contains a value. If this returns true, /// then awaiting [`Rcv::get`] and [`Rcv::peek`] will return immediately. pub fn contains_value(&self) -> bool { - self.watch.inner_contains_value() + self.watch.contains_value() + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { + fn drop(&mut self) { + self.watch.drop_receiver(); } } /// A receiver of a `Watch` channel. pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); -/// A receiver which holds a **reference** to a `Watch` channel. -/// -/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of -/// some runtime performance due to dynamic dispatch. -pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); +impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynReceiver<'a, T> { + // We need to increment the receiver count since the original + // receiver is being dropped, which decrements the count. + self.watch.mutex.lock(|state| { + state.borrow_mut().receiver_count += 1; + }); + DynReceiver(Rcv::new(self.0.watch, self.at_id)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Receiver<'a, M, T, N> { + fn into(self) -> DynReceiver<'a, T> { + self.as_dyn() + } +} impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { type Target = Rcv<'a, T, Watch>; @@ -330,6 +476,12 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, } } +/// A receiver which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + impl<'a, T: Clone> Deref for DynReceiver<'a, T> { type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; @@ -348,167 +500,242 @@ impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { mod tests { use futures_executor::block_on; - use super::*; + use super::Watch; use crate::blocking_mutex::raw::CriticalSectionRawMutex; #[test] - fn multiple_writes() { + fn multiple_sends() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(); - // Obtain Receivers - let mut rcv0 = WATCH.receiver().unwrap(); - let mut rcv1 = WATCH.dyn_receiver().unwrap(); + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); - WATCH.write(10); + // Not initialized + assert_eq!(rcv.try_changed(), None); // Receive the new value - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); + snd.send(10); + assert_eq!(rcv.changed().await, 10); + + // Receive another value + snd.send(20); + assert_eq!(rcv.try_changed(), Some(20)); // No update - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } - WATCH.write(20); + #[test] + fn receive_after_create() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain sender and send value + let snd = WATCH.sender(); + snd.send(10); - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); + // Obtain receiver and receive value + let mut rcv = WATCH.receiver().unwrap(); + assert_eq!(rcv.try_changed(), Some(10)); }; block_on(f); } #[test] - fn max_receivers() { + fn max_receivers_drop() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let _ = WATCH.receiver().unwrap(); - let _ = WATCH.receiver().unwrap(); - assert!(WATCH.receiver().is_err()); + // Try to create 3 receivers (only 2 can exist at once) + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_ok()); + assert!(rcv1.is_ok()); + assert!(rcv2.is_err()); + + // Drop the first receiver + drop(rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + assert!(rcv3.is_ok()); }; block_on(f); } #[test] - fn receive_initial() { + fn multiple_receivers() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers + // Obtain receivers and sender let mut rcv0 = WATCH.receiver().unwrap(); let mut rcv1 = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); - assert_eq!(rcv0.contains_value(), false); - + // No update for both assert_eq!(rcv0.try_changed(), None); assert_eq!(rcv1.try_changed(), None); - WATCH.write(0); - - assert_eq!(rcv0.contains_value(), true); + // Send a new value + snd.send(0); + // Both receivers receive the new value assert_eq!(rcv0.try_changed(), Some(0)); assert_eq!(rcv1.try_changed(), Some(0)); }; block_on(f); } + #[test] + fn clone_senders() { + let f = async { + // Obtain different ways to send + static WATCH: Watch = Watch::new(); + let snd0 = WATCH.sender(); + let snd1 = snd0.clone(); + + // Obtain Receiver + let mut rcv = WATCH.receiver().unwrap().as_dyn(); + + // Send a value from first sender + snd0.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Send a value from second sender + snd1.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + }; + block_on(f); + } + #[test] fn peek_get_changed() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let mut rcv0 = WATCH.receiver().unwrap(); + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); - WATCH.write(10); + // Send a value + snd.send(10); // Ensure peek does not mark as seen - assert_eq!(rcv0.peek().await, 10); - assert_eq!(rcv0.try_changed(), Some(10)); - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv0.peek().await, 10); + assert_eq!(rcv.peek().await, 10); + assert_eq!(rcv.try_changed(), Some(10)); + assert_eq!(rcv.try_changed(), None); + assert_eq!(rcv.try_peek(), Some(10)); - WATCH.write(20); + // Send a value + snd.send(20); // Ensure get does mark as seen - assert_eq!(rcv0.get().await, 20); - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv0.try_get(), Some(20)); + assert_eq!(rcv.get().await, 20); + assert_eq!(rcv.try_changed(), None); + assert_eq!(rcv.try_get(), Some(20)); }; block_on(f); } #[test] - fn count_ids() { + fn use_dynamics() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let mut rcv0 = WATCH.receiver().unwrap(); - let mut rcv1 = WATCH.receiver().unwrap(); + // Obtain receiver and sender + let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); + let dyn_snd = WATCH.dyn_sender(); - let get_id = || WATCH.mutex.lock(|state| state.borrow().current_id); + // Send a value + dyn_snd.send(10); - WATCH.write(10); - - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); + #[test] + fn convert_to_dyn() { + let f = async { + static WATCH: Watch = Watch::new(); - WATCH.write(20); - WATCH.write(20); - WATCH.write(20); + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); + // Convert to dynamic + let mut dyn_rcv = rcv.as_dyn(); + let dyn_snd = snd.as_dyn(); - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); + // Send a value + dyn_snd.send(10); - assert_eq!(get_id(), 4); + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); }; block_on(f); } #[test] - fn peek_still_await() { + fn dynamic_receiver_count() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let mut rcv0 = WATCH.receiver().unwrap(); - let mut rcv1 = WATCH.receiver().unwrap(); + // Obtain receiver and sender + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); - WATCH.write(10); + // Ensure the first two are successful and the third is not + assert!(rcv0.is_ok()); + assert!(rcv1.is_ok()); + assert!(rcv2.is_err()); - assert_eq!(rcv0.peek().await, 10); - assert_eq!(rcv1.try_peek(), Some(10)); + // Convert to dynamic + let dyn_rcv0 = rcv0.unwrap().as_dyn(); - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); + // Drop the (now dynamic) receiver + drop(dyn_rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + let rcv4 = WATCH.receiver(); + assert!(rcv3.is_ok()); + assert!(rcv4.is_err()); }; block_on(f); } #[test] - fn peek_with_static() { + fn contains_value() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let rcv0 = WATCH.receiver().unwrap(); - let rcv1 = WATCH.receiver().unwrap(); + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), false); + assert_eq!(snd.contains_value(), false); - WATCH.write(20); + // Send a value + snd.send(10); - assert_eq!(rcv0.peek().await, 20); - assert_eq!(rcv1.peek().await, 20); - assert_eq!(WATCH.try_peek(), Some(20)); + // check if the watch contains a value + assert_eq!(rcv.contains_value(), true); + assert_eq!(snd.contains_value(), true); }; block_on(f); } -- cgit From 3208e0fec4717ff1580d1cc0cf93e18cbba2db91 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 29 Feb 2024 16:56:52 +0100 Subject: Use Option instead of Result for receiver creation since it is the only way it can fail. --- embassy-sync/src/watch.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 3e22b1e7b..2bba93915 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -208,14 +208,7 @@ impl WatchBehavior for Watch } } -#[derive(Debug)] -/// An error that can occur when a `Watch` returns a `Result::Err(_)`. -pub enum Error { - /// The maximum number of [`Receiver`](crate::watch::Receiver)/[`DynReceiver`](crate::watch::DynReceiver) has been reached. - MaximumReceiversReached, -} - -impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { +impl Watch { /// Create a new `Watch` channel. pub const fn new() -> Self { Self { @@ -238,28 +231,30 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { DynSender(Snd::new(self)) } - /// Create a new [`Receiver`] for the `Watch`. - pub fn receiver(&self) -> Result, Error> { + /// Try to create a new [`Receiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn receiver(&self) -> Option> { self.mutex.lock(|state| { let mut s = state.borrow_mut(); if s.receiver_count < N { s.receiver_count += 1; - Ok(Receiver(Rcv::new(self, 0))) + Some(Receiver(Rcv::new(self, 0))) } else { - Err(Error::MaximumReceiversReached) + None } }) } - /// Create a new [`DynReceiver`] for the `Watch`. - pub fn dyn_receiver(&self) -> Result, Error> { + /// Try to create a new [`DynReceiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn dyn_receiver(&self) -> Option> { self.mutex.lock(|state| { let mut s = state.borrow_mut(); if s.receiver_count < N { s.receiver_count += 1; - Ok(DynReceiver(Rcv::new(self, 0))) + Some(DynReceiver(Rcv::new(self, 0))) } else { - Err(Error::MaximumReceiversReached) + None } }) } -- cgit From c08e75057a4282a0dfee13a6e181a2077944c1b0 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 29 Feb 2024 16:58:21 +0100 Subject: Update tests to reflect changes in previous commit --- embassy-sync/src/watch.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 2bba93915..1301eb817 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -551,16 +551,16 @@ mod tests { let rcv2 = WATCH.receiver(); // Ensure the first two are successful and the third is not - assert!(rcv0.is_ok()); - assert!(rcv1.is_ok()); - assert!(rcv2.is_err()); + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); // Drop the first receiver drop(rcv0); // Create another receiver and ensure it is successful let rcv3 = WATCH.receiver(); - assert!(rcv3.is_ok()); + assert!(rcv3.is_some()); }; block_on(f); } @@ -693,9 +693,9 @@ mod tests { let rcv2 = WATCH.receiver(); // Ensure the first two are successful and the third is not - assert!(rcv0.is_ok()); - assert!(rcv1.is_ok()); - assert!(rcv2.is_err()); + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); // Convert to dynamic let dyn_rcv0 = rcv0.unwrap().as_dyn(); @@ -706,8 +706,8 @@ mod tests { // Create another receiver and ensure it is successful let rcv3 = WATCH.receiver(); let rcv4 = WATCH.receiver(); - assert!(rcv3.is_ok()); - assert!(rcv4.is_err()); + assert!(rcv3.is_some()); + assert!(rcv4.is_none()); }; block_on(f); } -- cgit From df282aa23d00b2f3116081be2b07ba0c9f810fc3 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 29 Feb 2024 16:59:58 +0100 Subject: Forgot to update doc comment --- embassy-sync/src/watch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 1301eb817..01d82def4 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -37,7 +37,7 @@ use crate::waitqueue::MultiWakerRegistration; /// let mut snd = WATCH.sender(); /// /// // No more receivers, and no update -/// assert!(WATCH.receiver().is_err()); +/// assert!(WATCH.receiver().is_none()); /// assert_eq!(rcv1.try_changed(), None); /// /// snd.send(10); -- cgit From 311ab07a9af0029060813779038220481d1bf1c5 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Sat, 2 Mar 2024 00:14:11 +0100 Subject: Reintroduce predicate methods. Add ability for sender to modify value in-place. --- embassy-sync/src/watch.rs | 267 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 260 insertions(+), 7 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 01d82def4..520696f7d 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -88,21 +88,48 @@ pub trait WatchBehavior { /// Tries to peek the value of the `Watch`, **without** marking it as seen. fn try_peek(&self) -> Option; + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, **without** making it as seen. + fn poll_peek_and(&self, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to peek the value of the `Watch` if it matches the predicate function `f`, **without** marking it as seen. + fn try_peek_and(&self, f: &mut dyn Fn(&T) -> bool) -> Option; + /// Poll the `Watch` for the current value, making it as seen. fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to get the value of the `Watch`, marking it as seen. fn try_get(&self, id: &mut u64) -> Option; + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, making it as seen. + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + /// Poll the `Watch` for a changed value, marking it as seen. fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. fn try_changed(&self, id: &mut u64) -> Option; + /// Poll the `Watch` for a changed value that matches the predicate function + /// `f`, marking it as seen. + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed and matches the + /// predicate function `f`, marking it as seen. + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + /// Checks if the `Watch` is been initialized with a value. fn contains_value(&self) -> bool; + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn modify(&self, f: &mut dyn Fn(&mut Option)); + /// Used when a receiver is dropped to decrement the receiver count. /// /// ## This method should not be called by the user. @@ -143,6 +170,29 @@ impl WatchBehavior for Watch self.mutex.lock(|state| state.borrow().data.clone()) } + fn poll_peek_and(&self, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => Poll::Ready(data.clone()), + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_peek_and(&self, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => Some(data.clone()), + _ => None, + } + }) + } + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -167,6 +217,35 @@ impl WatchBehavior for Watch }) } + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Some(data.clone()) + } + _ => None, + } + }) + } + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -189,13 +268,42 @@ impl WatchBehavior for Watch match s.current_id > *id { true => { *id = s.current_id; - state.borrow().data.clone() + s.data.clone() } false => None, } }) } + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + s.data.clone() + } + _ => None, + } + }) + } + fn contains_value(&self) -> bool { self.mutex.lock(|state| state.borrow().data.is_some()) } @@ -206,6 +314,15 @@ impl WatchBehavior for Watch s.receiver_count -= 1; }) } + + fn modify(&self, f: &mut dyn Fn(&mut Option)) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + f(&mut s.data); + s.current_id += 1; + s.wakers.wake(); + }) + } } impl Watch { @@ -300,10 +417,27 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { self.watch.try_peek() } + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f`. + pub fn try_peek_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_peek_and(&mut f) + } + /// Returns true if the `Watch` contains a value. pub fn contains_value(&self) -> bool { self.watch.contains_value() } + + /// Modify the value of the `Watch` using a closure. + pub fn modify(&self, mut f: F) + where + F: Fn(&mut Option), + { + self.watch.modify(&mut f) + } } /// A sender of a `Watch` channel. @@ -399,6 +533,26 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { self.watch.try_peek() } + /// Returns the current value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, **without** marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn peek_and(&self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_peek_and(&mut f, cx)).await + } + + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, and **without** marking it as seen. + pub fn try_peek_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_peek_and(&mut f) + } + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. /// /// **Note**: Futures do nothing unless you `.await` or poll them. @@ -411,6 +565,26 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { self.watch.try_get(&mut self.at_id) } + /// Returns the value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_get_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(&mut self.at_id, &mut f) + } + /// Waits for the `Watch` to change and returns the new value, marking it as seen. /// /// **Note**: Futures do nothing unless you `.await` or poll them. @@ -423,6 +597,26 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { self.watch.try_changed(&mut self.at_id) } + /// Waits for the `Watch` to change to a value which satisfies the predicate + /// function `f` and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_changed_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + /// Checks if the `Watch` contains a value. If this returns true, /// then awaiting [`Rcv::get`] and [`Rcv::peek`] will return immediately. pub fn contains_value(&self) -> bool { @@ -442,12 +636,9 @@ pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch< impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { /// Converts the `Receiver` into a [`DynReceiver`]. pub fn as_dyn(self) -> DynReceiver<'a, T> { - // We need to increment the receiver count since the original - // receiver is being dropped, which decrements the count. - self.watch.mutex.lock(|state| { - state.borrow_mut().receiver_count += 1; - }); - DynReceiver(Rcv::new(self.0.watch, self.at_id)) + let rcv = DynReceiver(Rcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv } } @@ -524,6 +715,68 @@ mod tests { block_on(f); } + #[test] + fn sender_modify() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Modify the value inplace + snd.modify(|opt|{ + if let Some(inner) = opt { + *inner += 5; + } + }); + + // Get the modified value + assert_eq!(rcv.try_changed(), Some(15)); + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn predicate_fn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + snd.send(10); + assert_eq!(rcv.try_peek_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_peek_and(|x| x < &5), None); + assert!(rcv.try_changed().is_some()); + + snd.send(15); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert!(rcv.try_changed().is_none()); + + snd.send(20); + assert_eq!(rcv.try_changed_and(|x| x > &5), Some(20)); + assert_eq!(rcv.try_changed_and(|x| x > &5), None); + + snd.send(25); + assert_eq!(rcv.try_changed_and(|x| x < &5), None); + assert_eq!(rcv.try_changed(), Some(25)); + + snd.send(30); + assert_eq!(rcv.changed_and(|x| x > &5).await, 30); + assert_eq!(rcv.peek_and(|x| x > &5).await, 30); + assert_eq!(rcv.get_and(|x| x > &5).await, 30); + }; + block_on(f); + } + #[test] fn receive_after_create() { let f = async { -- cgit From e02a987bafd4f0fcf9d80e7c4f6e1504b8b02cec Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Sat, 2 Mar 2024 00:16:17 +0100 Subject: This one is for cargo fmt --- embassy-sync/src/watch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 520696f7d..298c09d43 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -729,7 +729,7 @@ mod tests { assert_eq!(rcv.try_changed(), Some(10)); // Modify the value inplace - snd.modify(|opt|{ + snd.modify(|opt| { if let Some(inner) = opt { *inner += 5; } -- cgit From 893b8d79e8bab8dae0f46a0182443df9160592b5 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Thu, 19 Sep 2024 08:17:33 -0400 Subject: embassy_sync/pubsub: fix PubSubBehavior visibility https://github.com/embassy-rs/embassy/pull/2969 appears to have broken direct `publish_immediate()` on `pubsub::Channel`, as it functionally made `PubSubBehavior` private and didn't delegate this method to the new (private) `SealedPubSubBehavior`. This change moves `publish_immediate`, `capacity`, and `is_full` from `SealedPubSubBehavior` to `PubSubBehavior` in order to restore them to `pub` visibility. --- embassy-sync/src/pubsub/mod.rs | 56 +++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 28 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index a97eb7d5b..812302e2b 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -194,6 +194,25 @@ impl crate::pubsub::PubSubBehavior + for PubSubChannel +{ + fn publish_immediate(&self, message: T) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.publish_immediate(message) + }) + } + + fn capacity(&self) -> usize { + self.capacity() + } + + fn is_full(&self) -> bool { + self.is_full() + } +} + impl SealedPubSubBehavior for PubSubChannel { @@ -246,13 +265,6 @@ impl usize { - self.capacity() - } - fn free_capacity(&self) -> usize { self.free_capacity() } @@ -286,10 +294,6 @@ impl bool { self.is_empty() } - - fn is_full(&self) -> bool { - self.is_full() - } } /// Internal state for the PubSub channel @@ -445,8 +449,6 @@ pub enum Error { MaximumPublishersReached, } -/// 'Middle level' behaviour of the pubsub channel. -/// This trait is used so that Sub and Pub can be generic over the channel. trait SealedPubSubBehavior { /// Try to get a message from the queue with the given message id. /// @@ -462,12 +464,6 @@ trait SealedPubSubBehavior { /// If the queue is full and a context is given, then its waker is registered in the publisher wakers. fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T>; - /// Publish a message immediately - fn publish_immediate(&self, message: T); - - /// Returns the maximum number of elements the channel can hold. - fn capacity(&self) -> usize; - /// Returns the free capacity of the channel. /// /// This is equivalent to `capacity() - len()` @@ -482,9 +478,6 @@ trait SealedPubSubBehavior { /// Returns whether the channel is empty. fn is_empty(&self) -> bool; - /// Returns whether the channel is full. - fn is_full(&self) -> bool; - /// Let the channel know that a subscriber has dropped fn unregister_subscriber(&self, subscriber_next_message_id: u64); @@ -495,9 +488,16 @@ trait SealedPubSubBehavior { /// 'Middle level' behaviour of the pubsub channel. /// This trait is used so that Sub and Pub can be generic over the channel. #[allow(private_bounds)] -pub trait PubSubBehavior: SealedPubSubBehavior {} +pub trait PubSubBehavior: SealedPubSubBehavior { + /// Publish a message immediately + fn publish_immediate(&self, message: T); -impl> PubSubBehavior for C {} + /// Returns the maximum number of elements the channel can hold. + fn capacity(&self) -> usize; + + /// Returns whether the channel is full. + fn is_full(&self) -> bool; +} /// The result of the subscriber wait procedure #[derive(Debug, Clone, PartialEq, Eq)] -- cgit From 89bad07e817dec482d385f765da5be3b6d2d0e4c Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Fri, 20 Sep 2024 01:52:59 -0400 Subject: embassy_sync: `Sink` adapter for `pubsub::Pub` Corresponding to the `Stream` impl for `pubsub::Sub`. Notable difference is that we need a separate adapter type to store the pending item, i.e. we can't `impl Sink for Pub` directly. Instead a method `Pub::sink(&self)` is exposed, which constructs a `PubSink`. --- embassy-sync/src/pubsub/mod.rs | 26 ++++++++++++++ embassy-sync/src/pubsub/publisher.rs | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index 812302e2b..1c0f68bd0 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -755,4 +755,30 @@ mod tests { assert_eq!(1, sub0.try_next_message_pure().unwrap().0); assert_eq!(0, sub1.try_next_message_pure().unwrap().0); } + + #[futures_test::test] + async fn publisher_sink() { + use futures_util::{SinkExt, StreamExt}; + + let channel = PubSubChannel::::new(); + + let mut sub = channel.subscriber().unwrap(); + + let publ = channel.publisher().unwrap(); + let mut sink = publ.sink(); + + sink.send(0).await.unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + + sink.send(1).await.unwrap(); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + + sink.send_all(&mut futures_util::stream::iter(0..4).map(Ok)) + .await + .unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + assert_eq!(2, sub.try_next_message_pure().unwrap()); + assert_eq!(3, sub.try_next_message_pure().unwrap()); + } } diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs index e66b3b1db..7a1ab66de 100644 --- a/embassy-sync/src/pubsub/publisher.rs +++ b/embassy-sync/src/pubsub/publisher.rs @@ -74,6 +74,12 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { pub fn is_full(&self) -> bool { self.channel.is_full() } + + /// Create a [`futures::Sink`] adapter for this publisher. + #[inline] + pub const fn sink(&self) -> PubSink<'a, '_, PSB, T> { + PubSink { publ: self, fut: None } + } } impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Pub<'a, PSB, T> { @@ -221,6 +227,67 @@ impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: } } +#[must_use = "Sinks do nothing unless polled"] +/// [`futures_sink::Sink`] adapter for [`Pub`]. +pub struct PubSink<'a, 'p, PSB, T> +where + T: Clone, + PSB: PubSubBehavior + ?Sized, +{ + publ: &'p Pub<'a, PSB, T>, + fut: Option>, +} + +impl<'a, 'p, PSB, T> PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + /// Try to make progress on the pending future if we have one. + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let Some(mut fut) = self.fut.take() else { + return Poll::Ready(()); + }; + + if Pin::new(&mut fut).poll(cx).is_pending() { + self.fut = Some(fut); + return Poll::Pending; + } + + Poll::Ready(()) + } +} + +impl<'a, 'p, PSB, T> futures_sink::Sink for PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + type Error = core::convert::Infallible; + + #[inline] + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + self.fut = Some(self.publ.publish(item)); + + Ok(()) + } + + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } +} + /// Future for the publisher wait action #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct PublisherWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { -- cgit From a669611d7c8fb17666f35086ea20c476b6029854 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Mon, 23 Sep 2024 20:09:35 +0200 Subject: Discontinue peek, add AnonReceiver --- embassy-sync/src/watch.rs | 440 ++++++++++++++++++++++++++++++---------------- 1 file changed, 289 insertions(+), 151 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 298c09d43..1b4a8b589 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -20,7 +20,9 @@ use crate::waitqueue::MultiWakerRegistration; /// always provided with the latest value. /// /// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] -/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained and passed to the relevant parts of the program. +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained where relevant. An [`AnonReceiver`] +/// and [`DynAnonReceiver`] are also available, which do not increase the receiver count for the +/// channel, and unwrapping is therefore not required, but it is not possible to `.await` the channel. /// ``` /// /// use futures_executor::block_on; @@ -41,25 +43,25 @@ use crate::waitqueue::MultiWakerRegistration; /// assert_eq!(rcv1.try_changed(), None); /// /// snd.send(10); -/// +/// /// // Receive the new value (async or try) /// assert_eq!(rcv0.changed().await, 10); /// assert_eq!(rcv1.try_changed(), Some(10)); -/// +/// /// // No update /// assert_eq!(rcv0.try_changed(), None); /// assert_eq!(rcv1.try_changed(), None); /// /// snd.send(20); /// -/// // Peek does not mark the value as seen -/// assert_eq!(rcv0.peek().await, 20); -/// assert_eq!(rcv0.try_changed(), Some(20)); -/// -/// // Get marks the value as seen +/// // Using `get` marks the value as seen /// assert_eq!(rcv1.get().await, 20); /// assert_eq!(rcv1.try_changed(), None); /// +/// // But `get` also returns when unchanged +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// /// }; /// block_on(f); /// ``` @@ -82,24 +84,11 @@ pub trait WatchBehavior { /// Clears the value of the `Watch`. fn clear(&self); - /// Poll the `Watch` for the current value, **without** making it as seen. - fn poll_peek(&self, cx: &mut Context<'_>) -> Poll; - - /// Tries to peek the value of the `Watch`, **without** marking it as seen. - fn try_peek(&self) -> Option; - - /// Poll the `Watch` for the value if it matches the predicate function - /// `f`, **without** making it as seen. - fn poll_peek_and(&self, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; - - /// Tries to peek the value of the `Watch` if it matches the predicate function `f`, **without** marking it as seen. - fn try_peek_and(&self, f: &mut dyn Fn(&T) -> bool) -> Option; - /// Poll the `Watch` for the current value, making it as seen. fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; - /// Tries to get the value of the `Watch`, marking it as seen. - fn try_get(&self, id: &mut u64) -> Option; + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; /// Poll the `Watch` for the value if it matches the predicate function /// `f`, making it as seen. @@ -107,9 +96,9 @@ pub trait WatchBehavior { /// Tries to get the value of the `Watch` if it matches the predicate function /// `f`, marking it as seen. - fn try_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; - /// Poll the `Watch` for a changed value, marking it as seen. + /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. @@ -128,7 +117,11 @@ pub trait WatchBehavior { /// Modify the value of the `Watch` using a closure. Returns `false` if the /// `Watch` does not already contain a value. - fn modify(&self, f: &mut dyn Fn(&mut Option)); + fn send_modify(&self, f: &mut dyn Fn(&mut Option)); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); /// Used when a receiver is dropped to decrement the receiver count. /// @@ -153,46 +146,6 @@ impl WatchBehavior for Watch }) } - fn poll_peek(&self, cx: &mut Context<'_>) -> Poll { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match &s.data { - Some(data) => Poll::Ready(data.clone()), - None => { - s.wakers.register(cx.waker()); - Poll::Pending - } - } - }) - } - - fn try_peek(&self) -> Option { - self.mutex.lock(|state| state.borrow().data.clone()) - } - - fn poll_peek_and(&self, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match s.data { - Some(ref data) if f(data) => Poll::Ready(data.clone()), - _ => { - s.wakers.register(cx.waker()); - Poll::Pending - } - } - }) - } - - fn try_peek_and(&self, f: &mut dyn Fn(&T) -> bool) -> Option { - self.mutex.lock(|state| { - let s = state.borrow(); - match s.data { - Some(ref data) if f(data) => Some(data.clone()), - _ => None, - } - }) - } - fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -209,11 +162,13 @@ impl WatchBehavior for Watch }) } - fn try_get(&self, id: &mut u64) -> Option { + fn try_get(&self, id: Option<&mut u64>) -> Option { self.mutex.lock(|state| { let s = state.borrow(); - *id = s.current_id; - state.borrow().data.clone() + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() }) } @@ -233,12 +188,14 @@ impl WatchBehavior for Watch }) } - fn try_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { self.mutex.lock(|state| { let s = state.borrow(); match s.data { Some(ref data) if f(data) => { - *id = s.current_id; + if let Some(id) = id { + *id = s.current_id; + } Some(data.clone()) } _ => None, @@ -315,7 +272,7 @@ impl WatchBehavior for Watch }) } - fn modify(&self, f: &mut dyn Fn(&mut Option)) { + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { self.mutex.lock(|state| { let mut s = state.borrow_mut(); f(&mut s.data); @@ -323,6 +280,16 @@ impl WatchBehavior for Watch s.wakers.wake(); }) } + + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if f(&mut s.data) { + s.current_id += 1; + s.wakers.wake(); + } + }) + } } impl Watch { @@ -375,6 +342,60 @@ impl Watch { } }) } + + /// Try to create a new [`AnonReceiver`] for the `Watch`. + pub fn anon_receiver(&self) -> AnonReceiver<'_, M, T, N> { + AnonReceiver(AnonRcv::new(self, 0)) + } + + /// Try to create a new [`DynAnonReceiver`] for the `Watch`. + pub fn dyn_anon_receiver(&self) -> DynAnonReceiver<'_, T> { + DynAnonReceiver(AnonRcv::new(self, 0)) + } + + /// Returns the message ID of the latest message sent to the `Watch`. + /// + /// This counter is monotonic, and is incremented every time a new message is sent. + pub fn get_msg_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Waits for the `Watch` to be initialized with a value using a busy-wait mechanism. + /// + /// This is useful for initialization code where receivers may only be interested in + /// awaiting the value once in the lifetime of the program. It is therefore a temporaryily + /// CPU-inefficient operation, while being more memory efficient than using a `Receiver`. + /// + /// **Note** Be careful about using this within an InterruptExecutor, as it will starve + /// tasks in lower-priority executors. + pub async fn spin_get(&self) -> T { + poll_fn(|cx| { + self.mutex.lock(|state| { + let s = state.borrow(); + match &s.data { + Some(data) => Poll::Ready(data.clone()), + None => { + cx.waker().wake_by_ref(); + Poll::Pending + } + } + }) + }) + .await + } + + /// Tries to get the value of the `Watch`. + pub fn try_get(&self) -> Option { + WatchBehavior::try_get(self, None) + } + + /// Tries to get the value of the `Watch` if it matches the predicate function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + WatchBehavior::try_get_and(self, None, &mut f) + } } /// A receiver can `.await` a change in the `Watch` value. @@ -407,23 +428,23 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { } /// Clears the value of the `Watch`. - /// This will cause calls to [`Rcv::get`] and [`Rcv::peek`] to be pending. + /// This will cause calls to [`Rcv::get`] to be pending. pub fn clear(&self) { self.watch.clear() } /// Tries to retrieve the value of the `Watch`. - pub fn try_peek(&self) -> Option { - self.watch.try_peek() + pub fn try_get(&self) -> Option { + self.watch.try_get(None) } /// Tries to peek the current value of the `Watch` if it matches the predicate /// function `f`. - pub fn try_peek_and(&self, mut f: F) -> Option + pub fn try_get_and(&self, mut f: F) -> Option where F: Fn(&T) -> bool, { - self.watch.try_peek_and(&mut f) + self.watch.try_get_and(None, &mut f) } /// Returns true if the `Watch` contains a value. @@ -432,11 +453,20 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { } /// Modify the value of the `Watch` using a closure. - pub fn modify(&self, mut f: F) + pub fn send_modify(&self, mut f: F) where F: Fn(&mut Option), { - self.watch.modify(&mut f) + self.watch.send_modify(&mut f) + } + + /// Modify the value of the `Watch` using a closure. The closure must return + /// `true` if the value was modified, which notifies all receivers. + pub fn send_if_modified(&self, mut f: F) + where + F: Fn(&mut Option) -> bool, + { + self.watch.send_if_modified(&mut f) } } @@ -521,38 +551,6 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { } } - /// Returns the current value of the `Watch` once it is initialized, **without** marking it as seen. - /// - /// **Note**: Futures do nothing unless you `.await` or poll them. - pub async fn peek(&self) -> T { - poll_fn(|cx| self.watch.poll_peek(cx)).await - } - - /// Tries to peek the current value of the `Watch` without waiting, and **without** marking it as seen. - pub fn try_peek(&self) -> Option { - self.watch.try_peek() - } - - /// Returns the current value of the `Watch` if it matches the predicate function `f`, - /// or waits for it to match, **without** marking it as seen. - /// - /// **Note**: Futures do nothing unless you `.await` or poll them. - pub async fn peek_and(&self, mut f: F) -> T - where - F: Fn(&T) -> bool, - { - poll_fn(|cx| self.watch.poll_peek_and(&mut f, cx)).await - } - - /// Tries to peek the current value of the `Watch` if it matches the predicate - /// function `f` without waiting, and **without** marking it as seen. - pub fn try_peek_and(&self, mut f: F) -> Option - where - F: Fn(&T) -> bool, - { - self.watch.try_peek_and(&mut f) - } - /// Returns the current value of the `Watch` once it is initialized, marking it as seen. /// /// **Note**: Futures do nothing unless you `.await` or poll them. @@ -562,7 +560,7 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { /// Tries to get the current value of the `Watch` without waiting, marking it as seen. pub fn try_get(&mut self) -> Option { - self.watch.try_get(&mut self.at_id) + self.watch.try_get(Some(&mut self.at_id)) } /// Returns the value of the `Watch` if it matches the predicate function `f`, @@ -582,7 +580,7 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { where F: Fn(&T) -> bool, { - self.watch.try_get_and(&mut self.at_id, &mut f) + self.watch.try_get_and(Some(&mut self.at_id), &mut f) } /// Waits for the `Watch` to change and returns the new value, marking it as seen. @@ -618,7 +616,7 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { } /// Checks if the `Watch` contains a value. If this returns true, - /// then awaiting [`Rcv::get`] and [`Rcv::peek`] will return immediately. + /// then awaiting [`Rcv::get`] will return immediately. pub fn contains_value(&self) -> bool { self.watch.contains_value() } @@ -630,6 +628,58 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { } } +/// A anonymous receiver can NOT `.await` a change in the `Watch` value. +pub struct AnonRcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> AnonRcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + /// A receiver of a `Watch` channel. pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); @@ -682,6 +732,58 @@ impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { } } +/// A receiver of a `Watch` channel that cannot `.await` values. +pub struct AnonReceiver<'a, M: RawMutex, T: Clone, const N: usize>(AnonRcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> AnonReceiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynAnonReceiver<'a, T> { + let rcv = DynAnonReceiver(AnonRcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for AnonReceiver<'a, M, T, N> { + fn into(self) -> DynAnonReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for AnonReceiver<'a, M, T, N> { + type Target = AnonRcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for AnonReceiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver that cannot `.await` value, which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`AnonReceiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynAnonReceiver<'a, T: Clone>(AnonRcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynAnonReceiver<'a, T> { + type Target = AnonRcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynAnonReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[cfg(test)] mod tests { use futures_executor::block_on; @@ -715,6 +817,72 @@ mod tests { block_on(f); } + #[test] + fn all_try_get() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(WATCH.try_get(), None); + assert_eq!(rcv.try_get(), None); + assert_eq!(snd.try_get(), None); + + // Receive the new value + snd.send(10); + assert_eq!(WATCH.try_get(), Some(10)); + assert_eq!(rcv.try_get(), Some(10)); + assert_eq!(snd.try_get(), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(10)); + assert_eq!(snd.try_get_and(|x| x > &5), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x < &5), None); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert_eq!(snd.try_get_and(|x| x < &5), None); + }; + block_on(f); + } + + #[test] + fn once_lock_like() { + let f = async { + static CONFIG0: u8 = 10; + static CONFIG1: u8 = 20; + + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(&CONFIG0); + let rcv0 = rcv.changed().await; + assert_eq!(rcv0, &10); + + // Receive another value + snd.send(&CONFIG1); + let rcv1 = rcv.try_changed(); + assert_eq!(rcv1, Some(&20)); + + // No update + assert_eq!(rcv.try_changed(), None); + + // Ensure similarity with original static + assert_eq!(rcv0, &CONFIG0); + assert_eq!(rcv1, Some(&CONFIG1)); + }; + block_on(f); + } + #[test] fn sender_modify() { let f = async { @@ -729,7 +897,7 @@ mod tests { assert_eq!(rcv.try_changed(), Some(10)); // Modify the value inplace - snd.modify(|opt| { + snd.send_modify(|opt| { if let Some(inner) = opt { *inner += 5; } @@ -751,11 +919,6 @@ mod tests { let mut rcv = WATCH.receiver().unwrap(); let snd = WATCH.sender(); - snd.send(10); - assert_eq!(rcv.try_peek_and(|x| x > &5), Some(10)); - assert_eq!(rcv.try_peek_and(|x| x < &5), None); - assert!(rcv.try_changed().is_some()); - snd.send(15); assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); assert_eq!(rcv.try_get_and(|x| x < &5), None); @@ -771,7 +934,6 @@ mod tests { snd.send(30); assert_eq!(rcv.changed_and(|x| x > &5).await, 30); - assert_eq!(rcv.peek_and(|x| x > &5).await, 30); assert_eq!(rcv.get_and(|x| x > &5).await, 30); }; block_on(f); @@ -825,7 +987,7 @@ mod tests { // Obtain receivers and sender let mut rcv0 = WATCH.receiver().unwrap(); - let mut rcv1 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.anon_receiver(); let snd = WATCH.sender(); // No update for both @@ -864,41 +1026,13 @@ mod tests { block_on(f); } - #[test] - fn peek_get_changed() { - let f = async { - static WATCH: Watch = Watch::new(); - - // Obtain receiver and sender - let mut rcv = WATCH.receiver().unwrap(); - let snd = WATCH.sender(); - - // Send a value - snd.send(10); - - // Ensure peek does not mark as seen - assert_eq!(rcv.peek().await, 10); - assert_eq!(rcv.try_changed(), Some(10)); - assert_eq!(rcv.try_changed(), None); - assert_eq!(rcv.try_peek(), Some(10)); - - // Send a value - snd.send(20); - - // Ensure get does mark as seen - assert_eq!(rcv.get().await, 20); - assert_eq!(rcv.try_changed(), None); - assert_eq!(rcv.try_get(), Some(20)); - }; - block_on(f); - } - #[test] fn use_dynamics() { let f = async { static WATCH: Watch = Watch::new(); // Obtain receiver and sender + let mut anon_rcv = WATCH.dyn_anon_receiver(); let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); let dyn_snd = WATCH.dyn_sender(); @@ -906,6 +1040,7 @@ mod tests { dyn_snd.send(10); // Ensure the dynamic receiver receives the value + assert_eq!(anon_rcv.try_changed(), Some(10)); assert_eq!(dyn_rcv.try_changed(), Some(10)); assert_eq!(dyn_rcv.try_changed(), None); }; @@ -918,10 +1053,12 @@ mod tests { static WATCH: Watch = Watch::new(); // Obtain receiver and sender + let anon_rcv = WATCH.anon_receiver(); let rcv = WATCH.receiver().unwrap(); let snd = WATCH.sender(); // Convert to dynamic + let mut dyn_anon_rcv = anon_rcv.as_dyn(); let mut dyn_rcv = rcv.as_dyn(); let dyn_snd = snd.as_dyn(); @@ -929,6 +1066,7 @@ mod tests { dyn_snd.send(10); // Ensure the dynamic receiver receives the value + assert_eq!(dyn_anon_rcv.try_changed(), Some(10)); assert_eq!(dyn_rcv.try_changed(), Some(10)); assert_eq!(dyn_rcv.try_changed(), None); }; -- cgit From 999807f226623669a9cfc8ca218d3c81f0c04a77 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Mon, 23 Sep 2024 20:29:50 +0200 Subject: Added SealedWatchBehavior to limit access to core functions --- embassy-sync/src/watch.rs | 137 ++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 66 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 1b4a8b589..4b7ffa5fc 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -76,28 +76,14 @@ struct WatchState { receiver_count: usize, } -/// A trait representing the 'inner' behavior of the `Watch`. -pub trait WatchBehavior { - /// Sends a new value to the `Watch`. - fn send(&self, val: T); - - /// Clears the value of the `Watch`. - fn clear(&self); - +trait SealedWatchBehavior { /// Poll the `Watch` for the current value, making it as seen. fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; - /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. - fn try_get(&self, id: Option<&mut u64>) -> Option; - /// Poll the `Watch` for the value if it matches the predicate function /// `f`, making it as seen. fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; - /// Tries to get the value of the `Watch` if it matches the predicate function - /// `f`, marking it as seen. - fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; - /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; @@ -112,8 +98,16 @@ pub trait WatchBehavior { /// predicate function `f`, marking it as seen. fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; - /// Checks if the `Watch` is been initialized with a value. - fn contains_value(&self) -> bool; + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); + + /// Clears the value of the `Watch`. + fn clear(&self); + + /// Sends a new value to the `Watch`. + fn send(&self, val: T); /// Modify the value of the `Watch` using a closure. Returns `false` if the /// `Watch` does not already contain a value. @@ -122,30 +116,23 @@ pub trait WatchBehavior { /// Modify the value of the `Watch` using a closure. Returns `false` if the /// `Watch` does not already contain a value. fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); - - /// Used when a receiver is dropped to decrement the receiver count. - /// - /// ## This method should not be called by the user. - fn drop_receiver(&self); } -impl WatchBehavior for Watch { - fn send(&self, val: T) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = Some(val); - s.current_id += 1; - s.wakers.wake(); - }) - } +/// A trait representing the 'inner' behavior of the `Watch`. +#[allow(private_bounds)] +pub trait WatchBehavior: SealedWatchBehavior { + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; - fn clear(&self) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = None; - }) - } + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Checks if the `Watch` is been initialized with a value. + fn contains_value(&self) -> bool; +} +impl SealedWatchBehavior for Watch { fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -162,16 +149,6 @@ impl WatchBehavior for Watch }) } - fn try_get(&self, id: Option<&mut u64>) -> Option { - self.mutex.lock(|state| { - let s = state.borrow(); - if let Some(id) = id { - *id = s.current_id; - } - s.data.clone() - }) - } - fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -188,21 +165,6 @@ impl WatchBehavior for Watch }) } - fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { - self.mutex.lock(|state| { - let s = state.borrow(); - match s.data { - Some(ref data) if f(data) => { - if let Some(id) = id { - *id = s.current_id; - } - Some(data.clone()) - } - _ => None, - } - }) - } - fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -261,10 +223,6 @@ impl WatchBehavior for Watch }) } - fn contains_value(&self) -> bool { - self.mutex.lock(|state| state.borrow().data.is_some()) - } - fn drop_receiver(&self) { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -272,6 +230,22 @@ impl WatchBehavior for Watch }) } + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -292,6 +266,37 @@ impl WatchBehavior for Watch } } +impl WatchBehavior for Watch { + fn try_get(&self, id: Option<&mut u64>) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() + }) + } + + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + if let Some(id) = id { + *id = s.current_id; + } + Some(data.clone()) + } + _ => None, + } + }) + } + + fn contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + impl Watch { /// Create a new `Watch` channel. pub const fn new() -> Self { -- cgit From 5e1912a2d3adea920039dae3622643f34289290b Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Tue, 24 Sep 2024 12:37:32 +0200 Subject: Reverse generics order, remove spin_get --- embassy-sync/src/watch.rs | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 4b7ffa5fc..336e64ba9 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -66,10 +66,10 @@ use crate::waitqueue::MultiWakerRegistration; /// block_on(f); /// ``` pub struct Watch { - mutex: Mutex>>, + mutex: Mutex>>, } -struct WatchState { +struct WatchState { data: Option, current_id: u64, wakers: MultiWakerRegistration, @@ -365,30 +365,6 @@ impl Watch { self.mutex.lock(|state| state.borrow().current_id) } - /// Waits for the `Watch` to be initialized with a value using a busy-wait mechanism. - /// - /// This is useful for initialization code where receivers may only be interested in - /// awaiting the value once in the lifetime of the program. It is therefore a temporaryily - /// CPU-inefficient operation, while being more memory efficient than using a `Receiver`. - /// - /// **Note** Be careful about using this within an InterruptExecutor, as it will starve - /// tasks in lower-priority executors. - pub async fn spin_get(&self) -> T { - poll_fn(|cx| { - self.mutex.lock(|state| { - let s = state.borrow(); - match &s.data { - Some(data) => Poll::Ready(data.clone()), - None => { - cx.waker().wake_by_ref(); - Poll::Pending - } - } - }) - }) - .await - } - /// Tries to get the value of the `Watch`. pub fn try_get(&self) -> Option { WatchBehavior::try_get(self, None) -- cgit From 383ad72b63b11ed1fc50ad5803534ac69996aff6 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Sat, 5 Oct 2024 13:39:27 +0200 Subject: embassy-sync: add clear, len, is_empty and is_full functions to zerocopy_channel --- embassy-sync/src/zerocopy_channel.rs | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index cfce9a571..a2c763294 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -70,6 +70,28 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { (Sender { channel: self }, Receiver { channel: self }) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.state.lock(|s| s.borrow().is_full()) + } } /// Send-only access to a [`Channel`]. @@ -130,6 +152,28 @@ impl<'a, M: RawMutex, T> Sender<'a, M, T> { pub fn send_done(&mut self) { self.channel.state.lock(|s| s.borrow_mut().push_done()) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } } /// Receive-only access to a [`Channel`]. @@ -190,6 +234,28 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { pub fn receive_done(&mut self) { self.channel.state.lock(|s| s.borrow_mut().pop_done()) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } } struct State { @@ -217,6 +283,16 @@ impl State { } } + fn clear(&mut self) { + self.front = 0; + self.back = 0; + self.full = false; + } + + fn len(&self) -> usize { + self.len + } + fn is_full(&self) -> bool { self.full } -- cgit From 67836f955af133fa2ea7427172bbf352c51e4ab2 Mon Sep 17 00:00:00 2001 From: Chris Maniewski Date: Sat, 5 Oct 2024 14:16:00 +0200 Subject: docs: fix Sender/Receiver typo --- embassy-sync/src/watch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 336e64ba9..59798d04f 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -310,12 +310,12 @@ impl Watch { } } - /// Create a new [`Receiver`] for the `Watch`. + /// Create a new [`Sender`] for the `Watch`. pub fn sender(&self) -> Sender<'_, M, T, N> { Sender(Snd::new(self)) } - /// Create a new [`DynReceiver`] for the `Watch`. + /// Create a new [`DynSender`] for the `Watch`. pub fn dyn_sender(&self) -> DynSender<'_, T> { DynSender(Snd::new(self)) } -- cgit From f3ed0c60265c84ddcc11e4dea980bdc0b8343985 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Sun, 6 Oct 2024 17:39:47 +0200 Subject: embassy-sync: fix len calculation for zerocopy_channel --- embassy-sync/src/zerocopy_channel.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index a2c763294..a669cbd09 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -290,7 +290,15 @@ impl State { } fn len(&self) -> usize { - self.len + if !self.full { + if self.back >= self.front { + self.back - self.front + } else { + self.len + self.back - self.front + } + } else { + self.len + } } fn is_full(&self) -> bool { -- cgit From 12e6add058b1bbe69660717bdef3d414a04b8b19 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Sun, 6 Oct 2024 17:45:03 +0200 Subject: embassy-sync: renamed field len to capacity on zerocopy_channel state --- embassy-sync/src/zerocopy_channel.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index a669cbd09..fabb69bf6 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -53,7 +53,7 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { buf: buf.as_mut_ptr(), phantom: PhantomData, state: Mutex::new(RefCell::new(State { - len, + capacity: len, front: 0, back: 0, full: false, @@ -259,7 +259,8 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { } struct State { - len: usize, + /// Maximum number of elements the channel can hold. + capacity: usize, /// Front index. Always 0..=(N-1) front: usize, @@ -276,7 +277,7 @@ struct State { impl State { fn increment(&self, i: usize) -> usize { - if i + 1 == self.len { + if i + 1 == self.capacity { 0 } else { i + 1 @@ -294,10 +295,10 @@ impl State { if self.back >= self.front { self.back - self.front } else { - self.len + self.back - self.front + self.capacity + self.back - self.front } } else { - self.len + self.capacity } } -- cgit From baef775f6b33b4fd45dd3a4bb1122a52c6d22c67 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 13:30:46 +0200 Subject: Add capacity, free_capacity, clear, len, is_empty and is_full functions to Channel::{Sender, Receiver} --- embassy-sync/src/channel.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 55ac5fb66..18b053111 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -72,6 +72,48 @@ where pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.channel.poll_ready_to_send(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } /// Send-only access to a [`Channel`] without knowing channel size. @@ -179,6 +221,48 @@ where pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.channel.poll_receive(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } /// Receive-only access to a [`Channel`] without knowing channel size. -- cgit From 2704ac3d289650173b60e1a29d70e8903bea4cf1 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 17:35:11 +0200 Subject: Add capacity, free_capacity, clear, len, is_empty and is_full functions to priority_channel::{Sender, Receiver} --- embassy-sync/src/priority_channel.rs | 84 ++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 24c6c5a7f..1f4d8667c 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -71,6 +71,48 @@ where pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.channel.poll_ready_to_send(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } impl<'ch, M, T, K, const N: usize> From> for DynamicSender<'ch, T> @@ -146,6 +188,48 @@ where pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.channel.poll_receive(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } impl<'ch, M, T, K, const N: usize> From> for DynamicReceiver<'ch, T> -- cgit From bf60b239e87faa0b9905a42013d8ae9a9f4162ea Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 18:05:15 +0200 Subject: embassy-sync: fixed some clippy warnings --- embassy-sync/src/blocking_mutex/mod.rs | 1 + embassy-sync/src/mutex.rs | 2 +- embassy-sync/src/pubsub/mod.rs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/blocking_mutex/mod.rs b/embassy-sync/src/blocking_mutex/mod.rs index 8a4a4c642..beafdb43d 100644 --- a/embassy-sync/src/blocking_mutex/mod.rs +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -104,6 +104,7 @@ impl Mutex { impl Mutex { /// Borrows the data + #[allow(clippy::should_implement_trait)] pub fn borrow(&self) -> &T { let ptr = self.data.get() as *const T; unsafe { &*ptr } diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 8c3a3af9f..08f66e374 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -138,7 +138,7 @@ impl From for Mutex { impl Default for Mutex where M: RawMutex, - T: ?Sized + Default, + T: Default, { fn default() -> Self { Self::new(Default::default()) diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index 812302e2b..ae5951829 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -27,8 +27,8 @@ pub use subscriber::{DynSubscriber, Subscriber}; /// /// - With [Pub::publish()] the publisher has to wait until there is space in the internal message queue. /// - With [Pub::publish_immediate()] the publisher doesn't await and instead lets the oldest message -/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive -/// an error to indicate that it has lagged. +/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive +/// an error to indicate that it has lagged. /// /// ## Example /// -- cgit From f9a1511de4d3972da0fd4f7367043a65c673c593 Mon Sep 17 00:00:00 2001 From: Bronson Date: Sun, 10 Nov 2024 12:50:11 +1030 Subject: add default data to watch new() --- embassy-sync/src/watch.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 59798d04f..e0f5e14f9 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -31,7 +31,7 @@ use crate::waitqueue::MultiWakerRegistration; /// /// let f = async { /// -/// static WATCH: Watch = Watch::new(); +/// static WATCH: Watch = Watch::new(None); /// /// // Obtain receivers and sender /// let mut rcv0 = WATCH.receiver().unwrap(); @@ -299,10 +299,10 @@ impl WatchBehavior for Watch impl Watch { /// Create a new `Watch` channel. - pub const fn new() -> Self { + pub const fn new(data: Option) -> Self { Self { mutex: Mutex::new(RefCell::new(WatchState { - data: None, + data, current_id: 0, wakers: MultiWakerRegistration::new(), receiver_count: 0, @@ -775,7 +775,7 @@ mod tests { #[test] fn multiple_sends() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -801,7 +801,7 @@ mod tests { #[test] fn all_try_get() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -835,7 +835,7 @@ mod tests { static CONFIG0: u8 = 10; static CONFIG1: u8 = 20; - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -867,7 +867,7 @@ mod tests { #[test] fn sender_modify() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -894,7 +894,7 @@ mod tests { #[test] fn predicate_fn() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -923,7 +923,7 @@ mod tests { #[test] fn receive_after_create() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain sender and send value let snd = WATCH.sender(); @@ -939,7 +939,7 @@ mod tests { #[test] fn max_receivers_drop() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Try to create 3 receivers (only 2 can exist at once) let rcv0 = WATCH.receiver(); @@ -964,7 +964,7 @@ mod tests { #[test] fn multiple_receivers() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receivers and sender let mut rcv0 = WATCH.receiver().unwrap(); @@ -989,7 +989,7 @@ mod tests { fn clone_senders() { let f = async { // Obtain different ways to send - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); let snd0 = WATCH.sender(); let snd1 = snd0.clone(); @@ -1010,7 +1010,7 @@ mod tests { #[test] fn use_dynamics() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let mut anon_rcv = WATCH.dyn_anon_receiver(); @@ -1031,7 +1031,7 @@ mod tests { #[test] fn convert_to_dyn() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let anon_rcv = WATCH.anon_receiver(); @@ -1057,7 +1057,7 @@ mod tests { #[test] fn dynamic_receiver_count() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let rcv0 = WATCH.receiver(); @@ -1087,7 +1087,7 @@ mod tests { #[test] fn contains_value() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(None); // Obtain receiver and sender let rcv = WATCH.receiver().unwrap(); -- cgit From 8d0882991ee88ce4af52d32bbaebecf40fa57914 Mon Sep 17 00:00:00 2001 From: Bronson Date: Sun, 10 Nov 2024 12:54:37 +1030 Subject: added watch new_with() --- embassy-sync/src/watch.rs | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index e0f5e14f9..90dcf97ef 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -31,7 +31,7 @@ use crate::waitqueue::MultiWakerRegistration; /// /// let f = async { /// -/// static WATCH: Watch = Watch::new(None); +/// static WATCH: Watch = Watch::new(); /// /// // Obtain receivers and sender /// let mut rcv0 = WATCH.receiver().unwrap(); @@ -299,7 +299,19 @@ impl WatchBehavior for Watch impl Watch { /// Create a new `Watch` channel. - pub const fn new(data: Option) -> Self { + pub const fn new() -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: None, + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } +} + + /// Create a new `Watch` channel. + pub const fn new_with(data: Option) -> Self { Self { mutex: Mutex::new(RefCell::new(WatchState { data, @@ -775,7 +787,7 @@ mod tests { #[test] fn multiple_sends() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -801,7 +813,7 @@ mod tests { #[test] fn all_try_get() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -835,7 +847,7 @@ mod tests { static CONFIG0: u8 = 10; static CONFIG1: u8 = 20; - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -867,7 +879,7 @@ mod tests { #[test] fn sender_modify() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -894,7 +906,7 @@ mod tests { #[test] fn predicate_fn() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let mut rcv = WATCH.receiver().unwrap(); @@ -923,7 +935,7 @@ mod tests { #[test] fn receive_after_create() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain sender and send value let snd = WATCH.sender(); @@ -939,7 +951,7 @@ mod tests { #[test] fn max_receivers_drop() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Try to create 3 receivers (only 2 can exist at once) let rcv0 = WATCH.receiver(); @@ -964,7 +976,7 @@ mod tests { #[test] fn multiple_receivers() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receivers and sender let mut rcv0 = WATCH.receiver().unwrap(); @@ -989,7 +1001,7 @@ mod tests { fn clone_senders() { let f = async { // Obtain different ways to send - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); let snd0 = WATCH.sender(); let snd1 = snd0.clone(); @@ -1010,7 +1022,7 @@ mod tests { #[test] fn use_dynamics() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let mut anon_rcv = WATCH.dyn_anon_receiver(); @@ -1031,7 +1043,7 @@ mod tests { #[test] fn convert_to_dyn() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let anon_rcv = WATCH.anon_receiver(); @@ -1057,7 +1069,7 @@ mod tests { #[test] fn dynamic_receiver_count() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let rcv0 = WATCH.receiver(); @@ -1087,7 +1099,7 @@ mod tests { #[test] fn contains_value() { let f = async { - static WATCH: Watch = Watch::new(None); + static WATCH: Watch = Watch::new(); // Obtain receiver and sender let rcv = WATCH.receiver().unwrap(); -- cgit From 32f0cde1cc6f277fb3f91d9fc0cc754c09267376 Mon Sep 17 00:00:00 2001 From: Bronson Date: Sun, 10 Nov 2024 13:15:41 +1030 Subject: fix formatting --- embassy-sync/src/watch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 90dcf97ef..d645ffe17 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -308,7 +308,7 @@ impl Watch { receiver_count: 0, })), } -} + } /// Create a new `Watch` channel. pub const fn new_with(data: Option) -> Self { -- cgit From 730dde9ba901d89040d3d943cf36b4217ac52bf9 Mon Sep 17 00:00:00 2001 From: Bronson Date: Sun, 10 Nov 2024 21:09:24 +1030 Subject: remove option --- embassy-sync/src/watch.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index d645ffe17..404e31714 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -310,11 +310,11 @@ impl Watch { } } - /// Create a new `Watch` channel. - pub const fn new_with(data: Option) -> Self { + /// Create a new `Watch` channel with default data. + pub const fn new_with(data: T) -> Self { Self { mutex: Mutex::new(RefCell::new(WatchState { - data, + data: Some(data), current_id: 0, wakers: MultiWakerRegistration::new(), receiver_count: 0, -- cgit From e05f6da2692f2b61986ef2b77789e27d68e4e74a Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Fri, 22 Nov 2024 09:21:44 +0100 Subject: Generalize AtomicWaker --- embassy-sync/src/waitqueue/atomic_waker.rs | 42 +++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/waitqueue/atomic_waker.rs b/embassy-sync/src/waitqueue/atomic_waker.rs index 63fe04a6e..231902c5a 100644 --- a/embassy-sync/src/waitqueue/atomic_waker.rs +++ b/embassy-sync/src/waitqueue/atomic_waker.rs @@ -1,26 +1,25 @@ use core::cell::Cell; use core::task::Waker; -use crate::blocking_mutex::raw::CriticalSectionRawMutex; +use crate::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex}; use crate::blocking_mutex::Mutex; /// Utility struct to register and wake a waker. -pub struct AtomicWaker { - waker: Mutex>>, +pub struct GenericAtomicWaker { + waker: Mutex>>, } -impl AtomicWaker { +impl GenericAtomicWaker { /// Create a new `AtomicWaker`. - pub const fn new() -> Self { + pub const fn new(mutex: M) -> Self { Self { - waker: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), + waker: Mutex::const_new(mutex, Cell::new(None)), } } /// Register a waker. Overwrites the previous waker, if any. pub fn register(&self, w: &Waker) { - critical_section::with(|cs| { - let cell = self.waker.borrow(cs); + self.waker.lock(|cell| { cell.set(match cell.replace(None) { Some(w2) if (w2.will_wake(w)) => Some(w2), _ => Some(w.clone()), @@ -30,8 +29,7 @@ impl AtomicWaker { /// Wake the registered waker, if any. pub fn wake(&self) { - critical_section::with(|cs| { - let cell = self.waker.borrow(cs); + self.waker.lock(|cell| { if let Some(w) = cell.replace(None) { w.wake_by_ref(); cell.set(Some(w)); @@ -39,3 +37,27 @@ impl AtomicWaker { }) } } + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: GenericAtomicWaker, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new() -> Self { + Self { + waker: GenericAtomicWaker::new(CriticalSectionRawMutex::new()), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.register(w); + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + self.waker.wake(); + } +} -- cgit From 44217aa0924e7590aa0afabdf17babd5c2ea5b82 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 30 Dec 2024 12:13:13 +0100 Subject: Desugar some async fns --- embassy-sync/src/mutex.rs | 5 ++--- embassy-sync/src/once_lock.rs | 5 ++--- embassy-sync/src/watch.rs | 6 +++--- embassy-sync/src/zerocopy_channel.rs | 24 +++++++++++++----------- 4 files changed, 20 insertions(+), 20 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 08f66e374..f25f74336 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -2,7 +2,7 @@ //! //! This module provides a mutex that can be used to synchronize data between asynchronous tasks. use core::cell::{RefCell, UnsafeCell}; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::ops::{Deref, DerefMut}; use core::task::Poll; use core::{fmt, mem}; @@ -73,7 +73,7 @@ where /// Lock the mutex. /// /// This will wait for the mutex to be unlocked if it's already locked. - pub async fn lock(&self) -> MutexGuard<'_, M, T> { + pub fn lock(&self) -> impl Future> { poll_fn(|cx| { let ready = self.state.lock(|s| { let mut s = s.borrow_mut(); @@ -92,7 +92,6 @@ where Poll::Pending } }) - .await } /// Attempt to immediately lock the mutex. diff --git a/embassy-sync/src/once_lock.rs b/embassy-sync/src/once_lock.rs index 55608ba32..cd05b986d 100644 --- a/embassy-sync/src/once_lock.rs +++ b/embassy-sync/src/once_lock.rs @@ -1,7 +1,7 @@ //! Synchronization primitive for initializing a value once, allowing others to await a reference to the value. use core::cell::Cell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::mem::MaybeUninit; use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; @@ -55,7 +55,7 @@ impl OnceLock { /// Get a reference to the underlying value, waiting for it to be set. /// If the value is already set, this will return immediately. - pub async fn get(&self) -> &T { + pub fn get(&self) -> impl Future { poll_fn(|cx| match self.try_get() { Some(data) => Poll::Ready(data), None => { @@ -63,7 +63,6 @@ impl OnceLock { Poll::Pending } }) - .await } /// Try to get a reference to the underlying value if it exists. diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 404e31714..e76646c0b 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -1,7 +1,7 @@ //! A synchronization primitive for passing the latest value to **multiple** receivers. use core::cell::RefCell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::marker::PhantomData; use core::ops::{Deref, DerefMut}; use core::task::{Context, Poll}; @@ -547,8 +547,8 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { /// Returns the current value of the `Watch` once it is initialized, marking it as seen. /// /// **Note**: Futures do nothing unless you `.await` or poll them. - pub async fn get(&mut self) -> T { - poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)).await + pub fn get(&mut self) -> impl Future + '_ { + poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)) } /// Tries to get the current value of the `Watch` without waiting, marking it as seen. diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index fabb69bf6..56433cd8a 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -15,7 +15,7 @@ //! another message will result in an error being returned. use core::cell::RefCell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::marker::PhantomData; use core::task::{Context, Poll}; @@ -131,12 +131,15 @@ impl<'a, M: RawMutex, T> Sender<'a, M, T> { } /// Asynchronously send a value over the channel. - pub async fn send(&mut self) -> &mut T { - let i = poll_fn(|cx| { + pub fn send(&mut self) -> impl Future { + poll_fn(|cx| { self.channel.state.lock(|s| { let s = &mut *s.borrow_mut(); match s.push_index() { - Some(i) => Poll::Ready(i), + Some(i) => { + let r = unsafe { &mut *self.channel.buf.add(i) }; + Poll::Ready(r) + } None => { s.receive_waker.register(cx.waker()); Poll::Pending @@ -144,8 +147,6 @@ impl<'a, M: RawMutex, T> Sender<'a, M, T> { } }) }) - .await; - unsafe { &mut *self.channel.buf.add(i) } } /// Notify the channel that the sending of the value has been finalized. @@ -213,12 +214,15 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { } /// Asynchronously receive a value over the channel. - pub async fn receive(&mut self) -> &mut T { - let i = poll_fn(|cx| { + pub fn receive(&mut self) -> impl Future { + poll_fn(|cx| { self.channel.state.lock(|s| { let s = &mut *s.borrow_mut(); match s.pop_index() { - Some(i) => Poll::Ready(i), + Some(i) => { + let r = unsafe { &mut *self.channel.buf.add(i) }; + Poll::Ready(r) + } None => { s.send_waker.register(cx.waker()); Poll::Pending @@ -226,8 +230,6 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { } }) }) - .await; - unsafe { &mut *self.channel.buf.add(i) } } /// Notify the channel that the receiving of the value has been finalized. -- cgit From c06862eeaf44eabd291194ed29f9e12558d1abf4 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 15 Jan 2025 11:37:00 +0100 Subject: feat: add dynamic dispatch variants of pipe --- embassy-sync/src/pipe.rs | 273 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index cd5b8ed75..2598652d2 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -532,6 +532,250 @@ impl embedded_io_async::Write for Writer<'_, M, N> } } +// +// Type-erased variants +// + +pub(crate) trait DynamicPipe { + fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a>; + fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a>; + + fn try_read(&self, buf: &mut [u8]) -> Result; + fn try_write(&self, buf: &[u8]) -> Result; + + fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result; + fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result; + + fn consume(&self, amt: usize); + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError>; +} + +impl DynamicPipe for Pipe +where + M: RawMutex, +{ + fn consume(&self, amt: usize) { + Pipe::consume(self, amt) + } + + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError> { + Pipe::try_fill_buf_with_context(self, cx) + } + + fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a> { + Pipe::write(self, buf).into() + } + + fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a> { + Pipe::read(self, buf).into() + } + + fn try_read(&self, buf: &mut [u8]) -> Result { + Pipe::try_read(self, buf) + } + + fn try_write(&self, buf: &[u8]) -> Result { + Pipe::try_write(self, buf) + } + + fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result { + Pipe::try_write_with_context(self, cx, buf) + } + + fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result { + Pipe::try_read_with_context(self, cx, buf) + } +} + +/// Write-only access to a [`DynamicPipe`]. +pub struct DynamicWriter<'p> { + pipe: &'p dyn DynamicPipe, +} + +impl<'p> Clone for DynamicWriter<'p> { + fn clone(&self) -> Self { + *self + } +} + +impl<'p> Copy for DynamicWriter<'p> {} + +impl<'p> DynamicWriter<'p> { + /// Write some bytes to the pipe. + /// + /// See [`Pipe::write()`] + pub fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a> { + self.pipe.write(buf) + } + + /// Attempt to immediately write some bytes to the pipe. + /// + /// See [`Pipe::try_write()`] + pub fn try_write(&self, buf: &[u8]) -> Result { + self.pipe.try_write(buf) + } +} + +impl<'p, M, const N: usize> From> for DynamicWriter<'p> +where + M: RawMutex, +{ + fn from(value: Writer<'p, M, N>) -> Self { + Self { pipe: value.pipe } + } +} + +/// Future returned by [`DynamicWriter::write`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicWriteFuture<'p> { + pipe: &'p dyn DynamicPipe, + buf: &'p [u8], +} + +impl<'p> Future for DynamicWriteFuture<'p> { + type Output = usize; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_write_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryWriteError::Full) => Poll::Pending, + } + } +} + +impl<'p> Unpin for DynamicWriteFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicWriteFuture<'p> +where + M: RawMutex, +{ + fn from(value: WriteFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe, + buf: value.buf, + } + } +} + +/// Read-only access to a [`DynamicPipe`]. +pub struct DynamicReader<'p> { + pipe: &'p dyn DynamicPipe, +} + +impl<'p> DynamicReader<'p> { + /// Read some bytes from the pipe. + /// + /// See [`Pipe::read()`] + pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a> { + self.pipe.read(buf) + } + + /// Attempt to immediately read some bytes from the pipe. + /// + /// See [`Pipe::try_read()`] + pub fn try_read(&self, buf: &mut [u8]) -> Result { + self.pipe.try_read(buf) + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn fill_buf(&mut self) -> DynamicFillBufFuture<'_> { + DynamicFillBufFuture { pipe: Some(self.pipe) } + } + + /// Try returning contents of the internal buffer. + /// + /// If no bytes are currently available to read, this function returns `Err(TryReadError::Empty)`. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn try_fill_buf(&mut self) -> Result<&[u8], TryReadError> { + unsafe { self.pipe.try_fill_buf_with_context(None) } + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.pipe.consume(amt) + } +} + +impl<'p, M, const N: usize> From> for DynamicReader<'p> +where + M: RawMutex, +{ + fn from(value: Reader<'p, M, N>) -> Self { + Self { pipe: value.pipe } + } +} + +/// Future returned by [`Pipe::read`] and [`Reader::read`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicReadFuture<'p> { + pipe: &'p dyn DynamicPipe, + buf: &'p mut [u8], +} + +impl<'p> Future for DynamicReadFuture<'p> { + type Output = usize; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_read_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryReadError::Empty) => Poll::Pending, + } + } +} + +impl<'p> Unpin for DynamicReadFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicReadFuture<'p> +where + M: RawMutex, +{ + fn from(value: ReadFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe, + buf: value.buf, + } + } +} + +/// Future returned by [`DynamicPipe::fill_buf`] and [`DynamicReader::fill_buf`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicFillBufFuture<'p> { + pipe: Option<&'p dyn DynamicPipe>, +} + +impl<'p> Future for DynamicFillBufFuture<'p> { + type Output = &'p [u8]; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pipe = self.pipe.take().unwrap(); + match unsafe { pipe.try_fill_buf_with_context(Some(cx)) } { + Ok(buf) => Poll::Ready(buf), + Err(TryReadError::Empty) => { + self.pipe = Some(pipe); + Poll::Pending + } + } + } +} + +impl<'p> Unpin for DynamicFillBufFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicFillBufFuture<'p> +where + M: RawMutex, +{ + fn from(value: FillBufFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe.map(|p| p as &dyn DynamicPipe), + } + } +} + #[cfg(test)] mod tests { use futures_executor::ThreadPool; @@ -619,6 +863,35 @@ mod tests { let _ = w.clone(); } + #[test] + fn dynamic_dispatch_pipe() { + let mut c = Pipe::::new(); + let (r, w) = c.split(); + let (mut r, w): (DynamicReader<'_>, DynamicWriter<'_>) = (r.into(), w.into()); + + assert!(w.try_write(&[42, 43]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[43]); + r.consume(1); + assert_eq!(r.try_fill_buf(), Err(TryReadError::Empty)); + assert_eq!(w.try_write(&[44, 45, 46]), Ok(1)); + assert_eq!(w.try_write(&[45, 46]), Ok(2)); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[44]); // only one byte due to wraparound. + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46]); + assert!(w.try_write(&[47]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46, 47]); + r.consume(3); + } + #[futures_test::test] async fn receiver_receives_given_try_write_async() { let executor = ThreadPool::new().unwrap(); -- cgit From 9f451c7f411d742d8e7c1c3a00c0fb864f14d46c Mon Sep 17 00:00:00 2001 From: Bronson Date: Mon, 11 Nov 2024 19:22:37 +1030 Subject: added remove_if to priority channel --- embassy-sync/src/priority_channel.rs | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 1f4d8667c..a4eda7fbc 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -72,6 +72,17 @@ where self.channel.poll_ready_to_send(cx) } + /// Removes the elements from the channel that satisfy the predicate. + /// + /// See [`PriorityChannel::remove_if()`] + pub fn remove_if(&self, predicate: F) + where + F: Fn(&T) -> bool, + T: Clone, + { + self.channel.remove_if(predicate) + } + /// Returns the maximum number of elements the channel can hold. /// /// See [`PriorityChannel::capacity()`] @@ -189,6 +200,17 @@ where self.channel.poll_receive(cx) } + /// Removes the elements from the channel that satisfy the predicate. + /// + /// See [`PriorityChannel::remove_if()`] + pub fn remove_if(&self, predicate: F) + where + F: Fn(&T) -> bool, + T: Clone, + { + self.channel.remove_if(predicate) + } + /// Returns the maximum number of elements the channel can hold. /// /// See [`PriorityChannel::capacity()`] @@ -534,6 +556,26 @@ where self.lock(|c| c.try_receive()) } + /// Removes elements from the channel based on the given predicate. + pub fn remove_if(&self, predicate: F) + where + F: Fn(&T) -> bool, + T: Clone, + { + self.lock(|c| { + let mut new_heap = BinaryHeap::::new(); + for item in c.queue.iter() { + if !predicate(item) { + match new_heap.push(item.clone()) { + Ok(_) => (), + Err(_) => panic!("Error pushing item to heap"), + } + } + } + c.queue = new_heap; + }); + } + /// Returns the maximum number of elements the channel can hold. pub const fn capacity(&self) -> usize { N -- cgit From c65b6db318da7ecbe888a0a66b85d9ffb28106f0 Mon Sep 17 00:00:00 2001 From: Bronson Date: Sat, 14 Dec 2024 14:14:59 +1030 Subject: remove from sender --- embassy-sync/src/priority_channel.rs | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index a4eda7fbc..d466a22ff 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -72,17 +72,6 @@ where self.channel.poll_ready_to_send(cx) } - /// Removes the elements from the channel that satisfy the predicate. - /// - /// See [`PriorityChannel::remove_if()`] - pub fn remove_if(&self, predicate: F) - where - F: Fn(&T) -> bool, - T: Clone, - { - self.channel.remove_if(predicate) - } - /// Returns the maximum number of elements the channel can hold. /// /// See [`PriorityChannel::capacity()`] -- cgit From 269dec938015c387d419afebb8402f5bee633ca9 Mon Sep 17 00:00:00 2001 From: Rex Magana Date: Wed, 22 Jan 2025 11:26:08 -0700 Subject: add stream impl --- embassy-sync/src/channel.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 18b053111..b3b087bf6 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -317,6 +317,17 @@ where } } +impl<'ch, M, T, const N: usize> futures_util::Stream for Receiver<'ch, M, T, N> +where + M: RawMutex, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.channel.poll_receive(cx).map(Some) + } +} + /// Future returned by [`Channel::receive`] and [`Receiver::receive`]. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct ReceiveFuture<'ch, M, T, const N: usize> @@ -768,6 +779,17 @@ where } } +impl futures_util::Stream for Channel +where + M: RawMutex, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_receive(cx).map(Some) + } +} + #[cfg(test)] mod tests { use core::time::Duration; -- cgit From e2ddba92f7f3f9e64da10e8351e335989f388109 Mon Sep 17 00:00:00 2001 From: ibuki2003 Date: Sun, 26 Jan 2025 17:23:41 +0900 Subject: embassy-sync: fix clear() to wake senders --- embassy-sync/src/channel.rs | 3 +++ embassy-sync/src/priority_channel.rs | 3 +++ embassy-sync/src/pubsub/mod.rs | 3 +++ embassy-sync/src/zerocopy_channel.rs | 3 +++ 4 files changed, 12 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 18b053111..9a7d2fa2f 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -562,6 +562,9 @@ impl ChannelState { } fn clear(&mut self) { + if self.queue.is_full() { + self.senders_waker.wake(); + } self.queue.clear(); } diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index d466a22ff..36959204f 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -411,6 +411,9 @@ where } fn clear(&mut self) { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } self.queue.clear(); } diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index a2360a1d8..606efff0a 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -421,6 +421,9 @@ impl PubSubSta } fn clear(&mut self) { + if self.is_full() { + self.publisher_wakers.wake(); + } self.queue.clear(); } diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index 56433cd8a..15914578e 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -287,6 +287,9 @@ impl State { } fn clear(&mut self) { + if self.full { + self.receive_waker.wake(); + } self.front = 0; self.back = 0; self.full = false; -- cgit From 7d66f1ca192bb5de83626cdb91f82c1caf265d73 Mon Sep 17 00:00:00 2001 From: lsartory Date: Sat, 1 Feb 2025 16:43:41 +0100 Subject: Fix issue #3828 Zero-copy channels could not be used between interrupts and thread-mode tasks because the internal buffer is stored as a raw pointer. A wrapper struct implementing the Sync trait fixes this. --- embassy-sync/src/zerocopy_channel.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index 15914578e..ad6fe74c5 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -35,7 +35,7 @@ use crate::waitqueue::WakerRegistration; /// The channel requires a buffer of recyclable elements. Writing to the channel is done through /// an `&mut T`. pub struct Channel<'a, M: RawMutex, T> { - buf: *mut T, + buf: BufferPtr, phantom: PhantomData<&'a mut T>, state: Mutex>, } @@ -50,7 +50,7 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { assert!(len != 0); Self { - buf: buf.as_mut_ptr(), + buf: BufferPtr(buf.as_mut_ptr()), phantom: PhantomData, state: Mutex::new(RefCell::new(State { capacity: len, @@ -94,6 +94,18 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { } } +#[repr(transparent)] +struct BufferPtr(*mut T); + +impl BufferPtr { + unsafe fn add(&self, count: usize) -> *mut T { + self.0.add(count) + } +} + +unsafe impl Send for BufferPtr {} +unsafe impl Sync for BufferPtr {} + /// Send-only access to a [`Channel`]. pub struct Sender<'a, M: RawMutex, T> { channel: &'a Channel<'a, M, T>, -- cgit From 0f048783010a6ca582b23cc06bcb47bd6ebf6fe8 Mon Sep 17 00:00:00 2001 From: wackazong Date: Tue, 4 Feb 2025 17:12:26 +0100 Subject: Add must_use to MutexGuard --- embassy-sync/src/mutex.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index f25f74336..7528a9f68 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -171,6 +171,7 @@ where /// /// Dropping it unlocks the mutex. #[clippy::has_significant_drop] +#[must_use = "if unused the Mutex will immediately unlock"] pub struct MutexGuard<'a, M, T> where M: RawMutex, -- cgit 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 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 embassy-sync/src/rwlock.rs (limited to 'embassy-sync/src') 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() + } +} -- 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/src') 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/src') 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 + 4 files changed, 381 insertions(+), 86 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 (limited to 'embassy-sync/src') 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; -- 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/src') 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/src') 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/src') 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/src') 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/src') 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/src') 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/src') 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/src') 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/src') 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 f3b9be7beede167295e8dc431e417bea77bb2455 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Wed, 19 Mar 2025 19:32:05 +0100 Subject: embassy-sync: add lock_mut to blocking_mutex::Mutex --- embassy-sync/src/blocking_mutex/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/blocking_mutex/mod.rs b/embassy-sync/src/blocking_mutex/mod.rs index beafdb43d..5b0e4144a 100644 --- a/embassy-sync/src/blocking_mutex/mod.rs +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -50,6 +50,20 @@ impl Mutex { f(inner) }) } + + /// Creates a critical section and grants temporary mutable access to the protected data. + /// + /// # Safety + /// This method is unsafe because calling this method when the mutex is already locked, + /// either using this method or `lock`, violates Rust's aliasing rules. + pub unsafe fn lock_mut(&self, f: impl FnOnce(&mut T) -> U) -> U { + self.raw.lock(|| { + let ptr = self.data.get() as *mut T; + // Safety: we have exclusive access to the data, as long as this mutex is not locked re-entrantly + let inner = unsafe { &mut *ptr }; + f(inner) + }) + } } impl Mutex { -- cgit From 7a031eed66ef27e83b8582e7c1e7ca00d16ccf64 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Fri, 21 Mar 2025 23:02:04 +0100 Subject: Add note about RefCell alternative --- embassy-sync/src/blocking_mutex/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/blocking_mutex/mod.rs b/embassy-sync/src/blocking_mutex/mod.rs index 5b0e4144a..a41bc3569 100644 --- a/embassy-sync/src/blocking_mutex/mod.rs +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -54,8 +54,11 @@ impl Mutex { /// Creates a critical section and grants temporary mutable access to the protected data. /// /// # Safety - /// This method is unsafe because calling this method when the mutex is already locked, - /// either using this method or `lock`, violates Rust's aliasing rules. + /// + /// This method is marked unsafe because calling this method re-entrantly, i.e. within + /// another `lock_mut` or `lock` closure, violates Rust's aliasing rules. Calling this + /// method at the same time from different tasks is safe. For a safe alternative with + /// mutable access that never causes UB, use a `RefCell` in a `Mutex`. pub unsafe fn lock_mut(&self, f: impl FnOnce(&mut T) -> U) -> U { self.raw.lock(|| { let ptr = self.data.get() as *mut T; -- cgit From f396579dff18eea80555ed56a4905ce9d9fab574 Mon Sep 17 00:00:00 2001 From: Cyril Marpaud <9333398+cyril-marpaud@users.noreply.github.com> Date: Mon, 31 Mar 2025 13:07:03 +0200 Subject: docs: fix a typo --- embassy-sync/src/zerocopy_channel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index ad6fe74c5..e3e5b2538 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -195,7 +195,7 @@ pub struct Receiver<'a, M: RawMutex, T> { } impl<'a, M: RawMutex, T> Receiver<'a, M, T> { - /// Creates one further [`Sender`] over the same channel. + /// Creates one further [`Receiver`] over the same channel. pub fn borrow(&mut self) -> Receiver<'_, M, T> { Receiver { channel: self.channel } } -- 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/src') 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 From c2173591aa77ab7aa0a1b3d921883667fb9881f4 Mon Sep 17 00:00:00 2001 From: ckrenslehner Date: Sat, 26 Apr 2025 20:07:30 +0200 Subject: docs: extend the waker documentation --- embassy-sync/src/waitqueue/atomic_waker.rs | 3 +++ embassy-sync/src/waitqueue/atomic_waker_turbo.rs | 3 +++ embassy-sync/src/waitqueue/multi_waker.rs | 2 ++ embassy-sync/src/waitqueue/waker_registration.rs | 4 ++++ 4 files changed, 12 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/waitqueue/atomic_waker.rs b/embassy-sync/src/waitqueue/atomic_waker.rs index 231902c5a..5a9910e7f 100644 --- a/embassy-sync/src/waitqueue/atomic_waker.rs +++ b/embassy-sync/src/waitqueue/atomic_waker.rs @@ -5,6 +5,9 @@ use crate::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex}; use crate::blocking_mutex::Mutex; /// Utility struct to register and wake a waker. +/// If a waker is registered, registering another waker will replace the previous one without waking it. +/// Intended to wake a task from an interrupt. Therefore, it is generally not expected, +/// that multiple tasks register try to register a waker simultaneously. pub struct GenericAtomicWaker { waker: Mutex>>, } diff --git a/embassy-sync/src/waitqueue/atomic_waker_turbo.rs b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs index 5c6a96ec8..c06b83056 100644 --- a/embassy-sync/src/waitqueue/atomic_waker_turbo.rs +++ b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs @@ -4,6 +4,9 @@ use core::sync::atomic::{AtomicPtr, Ordering}; use core::task::Waker; /// Utility struct to register and wake a waker. +/// If a waker is registered, registering another waker will replace the previous one without waking it. +/// The intended use case is to wake tasks from interrupts. Therefore, it is generally not expected, +/// that multiple tasks register try to register a waker simultaneously. pub struct AtomicWaker { waker: AtomicPtr<()>, } diff --git a/embassy-sync/src/waitqueue/multi_waker.rs b/embassy-sync/src/waitqueue/multi_waker.rs index 0e520bf40..0384d6bed 100644 --- a/embassy-sync/src/waitqueue/multi_waker.rs +++ b/embassy-sync/src/waitqueue/multi_waker.rs @@ -3,6 +3,8 @@ use core::task::Waker; use heapless::Vec; /// Utility struct to register and wake multiple wakers. +/// Queue of wakers with a maximum length of `N`. +/// Intended for waking multiple tasks. pub struct MultiWakerRegistration { wakers: Vec, } diff --git a/embassy-sync/src/waitqueue/waker_registration.rs b/embassy-sync/src/waitqueue/waker_registration.rs index 9b666e7c4..7f24f8fb6 100644 --- a/embassy-sync/src/waitqueue/waker_registration.rs +++ b/embassy-sync/src/waitqueue/waker_registration.rs @@ -2,6 +2,10 @@ use core::mem; use core::task::Waker; /// Utility struct to register and wake a waker. +/// If a waker is registered, registering another waker will replace the previous one. +/// The previous waker will be woken in this case, giving it a chance to reregister itself. +/// Although it is possible to wake multiple tasks this way, +/// this will cause them to wake each other in a loop registering themselves. #[derive(Debug, Default)] pub struct WakerRegistration { waker: Option, -- cgit From 723ebfcb4f17e48f2a614f0e1e124e8da9c75b70 Mon Sep 17 00:00:00 2001 From: JuliDi <20155974+JuliDi@users.noreply.github.com> Date: Sun, 4 May 2025 16:31:26 +0200 Subject: clarify docs for signal and watch --- embassy-sync/src/signal.rs | 5 +++-- embassy-sync/src/watch.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs index a0f4b5a74..e7095401e 100644 --- a/embassy-sync/src/signal.rs +++ b/embassy-sync/src/signal.rs @@ -6,7 +6,7 @@ use core::task::{Context, Poll, Waker}; use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; -/// Single-slot signaling primitive. +/// Single-slot signaling primitive for a _single_ consumer. /// /// This is similar to a [`Channel`](crate::channel::Channel) with a buffer size of 1, except /// "sending" to it (calling [`Signal::signal`]) when full will overwrite the previous value instead @@ -17,6 +17,7 @@ use crate::blocking_mutex::Mutex; /// updates. /// /// For more advanced use cases, you might want to use [`Channel`](crate::channel::Channel) instead. +/// For multiple consumers, use [`Watch`](crate::watch::Watch) instead. /// /// Signals are generally declared as `static`s and then borrowed as required. /// @@ -106,7 +107,7 @@ where }) } - /// Future that completes when this Signal has been signaled. + /// Future that completes when this Signal has been signaled, taking the value out of the signal. pub fn wait(&self) -> impl Future + '_ { poll_fn(move |cx| self.poll_wait(cx)) } diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index e76646c0b..08d6a833d 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -10,7 +10,7 @@ use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; use crate::waitqueue::MultiWakerRegistration; -/// The `Watch` is a single-slot signaling primitive that allows multiple receivers to concurrently await +/// The `Watch` is a single-slot signaling primitive that allows _multiple_ (`N`) receivers to concurrently await /// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, /// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous /// value when a new one is sent, without waiting for all receivers to read the previous value. @@ -298,7 +298,7 @@ impl WatchBehavior for Watch } impl Watch { - /// Create a new `Watch` channel. + /// Create a new `Watch` channel for `N` receivers. pub const fn new() -> Self { Self { mutex: Mutex::new(RefCell::new(WatchState { -- cgit From 042abc805a84d09231174e41edb0e498baaf7295 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 28 May 2025 10:36:05 +0200 Subject: feat: add support for channel peek Add support for peeking into the front of the channel if the value implements Clone. This can be useful in single-receiver situations where you don't want to remove the item from the queue until you've successfully processed it. --- embassy-sync/src/channel.rs | 78 ++++++++++++++++++++++++++++++++++++ embassy-sync/src/priority_channel.rs | 64 +++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 4d1fa9e39..a229c52e7 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -208,6 +208,16 @@ where self.channel.try_receive() } + /// Peek at the next value without removing it from the queue. + /// + /// See [`Channel::try_peek()`] + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.channel.try_peek() + } + /// Allows a poll_fn to poll until the channel is ready to receive /// /// See [`Channel::poll_ready_to_receive()`] @@ -293,6 +303,16 @@ impl<'ch, T> DynamicReceiver<'ch, T> { self.channel.try_receive_with_context(None) } + /// Peek at the next value without removing it from the queue. + /// + /// See [`Channel::try_peek()`] + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.channel.try_peek_with_context(None) + } + /// Allows a poll_fn to poll until the channel is ready to receive /// /// See [`Channel::poll_ready_to_receive()`] @@ -463,6 +483,10 @@ pub(crate) trait DynamicChannel { fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result; + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone; + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()>; fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()>; @@ -505,6 +529,31 @@ impl ChannelState { self.try_receive_with_context(None) } + fn try_peek(&mut self) -> Result + where + T: Clone, + { + self.try_peek_with_context(None) + } + + fn try_peek_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + if self.queue.is_full() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.front() { + Ok(message.clone()) + } else { + if let Some(cx) = cx { + self.receiver_waker.register(cx.waker()); + } + Err(TryReceiveError::Empty) + } + } + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { if self.queue.is_full() { self.senders_waker.wake(); @@ -634,6 +683,13 @@ where self.lock(|c| c.try_receive_with_context(cx)) } + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + self.lock(|c| c.try_peek_with_context(cx)) + } + /// Poll the channel for the next message pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.lock(|c| c.poll_receive(cx)) @@ -722,6 +778,17 @@ where self.lock(|c| c.try_receive()) } + /// Peek at the next value without removing it from the queue. + /// + /// This method will either receive a copy of the message from the channel immediately or return + /// an error if the channel is empty. + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.lock(|c| c.try_peek()) + } + /// Returns the maximum number of elements the channel can hold. pub const fn capacity(&self) -> usize { N @@ -769,6 +836,13 @@ where Channel::try_receive_with_context(self, cx) } + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + Channel::try_peek_with_context(self, cx) + } + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { Channel::poll_ready_to_send(self, cx) } @@ -851,6 +925,8 @@ mod tests { fn simple_send_and_receive() { let c = Channel::::new(); assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_peek().unwrap(), 1); + assert_eq!(c.try_peek().unwrap(), 1); assert_eq!(c.try_receive().unwrap(), 1); } @@ -881,6 +957,8 @@ mod tests { let r = c.dyn_receiver(); assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_peek().unwrap(), 1); + assert_eq!(r.try_peek().unwrap(), 1); assert_eq!(r.try_receive().unwrap(), 1); } diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 36959204f..623c52993 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -175,6 +175,16 @@ where self.channel.try_receive() } + /// Peek at the next value without removing it from the queue. + /// + /// See [`PriorityChannel::try_peek()`] + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.channel.try_peek_with_context(None) + } + /// Allows a poll_fn to poll until the channel is ready to receive /// /// See [`PriorityChannel::poll_ready_to_receive()`] @@ -343,6 +353,31 @@ where self.try_receive_with_context(None) } + fn try_peek(&mut self) -> Result + where + T: Clone, + { + self.try_peek_with_context(None) + } + + fn try_peek_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.peek() { + Ok(message.clone()) + } else { + if let Some(cx) = cx { + self.receiver_waker.register(cx.waker()); + } + Err(TryReceiveError::Empty) + } + } + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { if self.queue.len() == self.queue.capacity() { self.senders_waker.wake(); @@ -478,6 +513,13 @@ where self.lock(|c| c.try_receive_with_context(cx)) } + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + self.lock(|c| c.try_peek_with_context(cx)) + } + /// Poll the channel for the next message pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.lock(|c| c.poll_receive(cx)) @@ -548,6 +590,17 @@ where self.lock(|c| c.try_receive()) } + /// Peek at the next value without removing it from the queue. + /// + /// This method will either receive a copy of the message from the channel immediately or return + /// an error if the channel is empty. + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.lock(|c| c.try_peek()) + } + /// Removes elements from the channel based on the given predicate. pub fn remove_if(&self, predicate: F) where @@ -617,6 +670,13 @@ where PriorityChannel::try_receive_with_context(self, cx) } + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + PriorityChannel::try_peek_with_context(self, cx) + } + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { PriorityChannel::poll_ready_to_send(self, cx) } @@ -705,6 +765,8 @@ mod tests { fn simple_send_and_receive() { let c = PriorityChannel::::new(); assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_peek().unwrap(), 1); + assert_eq!(c.try_peek().unwrap(), 1); assert_eq!(c.try_receive().unwrap(), 1); } @@ -725,6 +787,8 @@ mod tests { let r: DynamicReceiver<'_, u32> = c.receiver().into(); assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_peek().unwrap(), 1); + assert_eq!(r.try_peek().unwrap(), 1); assert_eq!(r.try_receive().unwrap(), 1); } -- cgit From 277f6f7331684a0008930a43b6705ba52873d1f5 Mon Sep 17 00:00:00 2001 From: Corey Schuhen Date: Wed, 28 May 2025 18:15:15 +1000 Subject: Make Sync capable versions of DynamicSender and DynamicReceiver. DynamicSender and DynamicReceiver, just seem to be a fat pointer to a Channel which is already protected by it's own Mutex already. In fact, you can share the Channel already betwen threads and create Dynamic*er's in the target threads. It should be safe to share the Dynamic*er's directly. Can only be used when Mutex M of channel supoorts Sync. --- embassy-sync/src/channel.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 4d1fa9e39..1b053ecad 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -164,6 +164,57 @@ impl<'ch, T> DynamicSender<'ch, T> { } } +/// Send-only access to a [`Channel`] without knowing channel size. +/// This version can be sent between threads but can only be created if the underlying mutex is Sync. +pub struct SendDynamicSender<'ch, T> { + pub(crate) channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Clone for SendDynamicSender<'ch, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, T> Copy for SendDynamicSender<'ch, T> {} +unsafe impl<'ch, T: Send> Send for SendDynamicSender<'ch, T> {} +unsafe impl<'ch, T: Send> Sync for SendDynamicSender<'ch, T> {} + +impl<'ch, M, T, const N: usize> From> for SendDynamicSender<'ch, T> +where + M: RawMutex + Sync + Send, +{ + fn from(s: Sender<'ch, M, T, N>) -> Self { + Self { channel: s.channel } + } +} + +impl<'ch, T> SendDynamicSender<'ch, T> { + /// Sends a value. + /// + /// See [`Channel::send()`] + pub fn send(&self, message: T) -> DynamicSendFuture<'ch, T> { + DynamicSendFuture { + channel: self.channel, + message: Some(message), + } + } + + /// Attempt to immediately send a message. + /// + /// See [`Channel::send()`] + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.channel.try_send_with_context(message, None) + } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`Channel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } +} + /// Receive-only access to a [`Channel`]. pub struct Receiver<'ch, M, T, const N: usize> where @@ -317,6 +368,61 @@ where } } +/// Receive-only access to a [`Channel`] without knowing channel size. +/// This version can be sent between threads but can only be created if the underlying mutex is Sync. +pub struct SendableDynamicReceiver<'ch, T> { + pub(crate) channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Clone for SendableDynamicReceiver<'ch, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, T> Copy for SendableDynamicReceiver<'ch, T> {} +unsafe impl<'ch, T: Send> Send for SendableDynamicReceiver<'ch, T> {} +unsafe impl<'ch, T: Send> Sync for SendableDynamicReceiver<'ch, T> {} + +impl<'ch, T> SendableDynamicReceiver<'ch, T> { + /// Receive the next value. + /// + /// See [`Channel::receive()`]. + pub fn receive(&self) -> DynamicReceiveFuture<'_, T> { + DynamicReceiveFuture { channel: self.channel } + } + + /// Attempt to immediately receive the next value. + /// + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive_with_context(None) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } +} + +impl<'ch, M, T, const N: usize> From> for SendableDynamicReceiver<'ch, T> +where + M: RawMutex + Sync + Send, +{ + fn from(s: Receiver<'ch, M, T, N>) -> Self { + Self { channel: s.channel } + } +} + impl<'ch, M, T, const N: usize> futures_util::Stream for Receiver<'ch, M, T, N> where M: RawMutex, -- cgit From 5730b57094d6e2da3645326596532a091b47ec86 Mon Sep 17 00:00:00 2001 From: Corey Schuhen Date: Thu, 29 May 2025 08:30:21 +1000 Subject: Rename SendableDynamicReceiver to SendDynamicReceiver --- embassy-sync/src/channel.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index f97cb2b8e..856551417 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -390,21 +390,21 @@ where /// Receive-only access to a [`Channel`] without knowing channel size. /// This version can be sent between threads but can only be created if the underlying mutex is Sync. -pub struct SendableDynamicReceiver<'ch, T> { +pub struct SendDynamicReceiver<'ch, T> { pub(crate) channel: &'ch dyn DynamicChannel, } -impl<'ch, T> Clone for SendableDynamicReceiver<'ch, T> { +impl<'ch, T> Clone for SendDynamicReceiver<'ch, T> { fn clone(&self) -> Self { *self } } -impl<'ch, T> Copy for SendableDynamicReceiver<'ch, T> {} -unsafe impl<'ch, T: Send> Send for SendableDynamicReceiver<'ch, T> {} -unsafe impl<'ch, T: Send> Sync for SendableDynamicReceiver<'ch, T> {} +impl<'ch, T> Copy for SendDynamicReceiver<'ch, T> {} +unsafe impl<'ch, T: Send> Send for SendDynamicReceiver<'ch, T> {} +unsafe impl<'ch, T: Send> Sync for SendDynamicReceiver<'ch, T> {} -impl<'ch, T> SendableDynamicReceiver<'ch, T> { +impl<'ch, T> SendDynamicReceiver<'ch, T> { /// Receive the next value. /// /// See [`Channel::receive()`]. @@ -434,7 +434,7 @@ impl<'ch, T> SendableDynamicReceiver<'ch, T> { } } -impl<'ch, M, T, const N: usize> From> for SendableDynamicReceiver<'ch, T> +impl<'ch, M, T, const N: usize> From> for SendDynamicReceiver<'ch, T> where M: RawMutex + Sync + Send, { -- cgit