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 6a4ac5bd60693307721aa82c26909ffd05e2b193 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 17 Jun 2024 01:38:57 +0200 Subject: Add collapse_debuginfo to fmt.rs macros. This makes location info in defmt logs point to the code calling the macro, instead of always to fmt.rs as before. Fix works with nightlies starting with today's, and stable 1.81+. --- embassy-sync/src/fmt.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/fmt.rs b/embassy-sync/src/fmt.rs index 2ac42c557..35b929fde 100644 --- a/embassy-sync/src/fmt.rs +++ b/embassy-sync/src/fmt.rs @@ -6,6 +6,7 @@ use core::fmt::{Debug, Display, LowerHex}; #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); +#[collapse_debuginfo(yes)] macro_rules! assert { ($($x:tt)*) => { { @@ -17,6 +18,7 @@ macro_rules! assert { }; } +#[collapse_debuginfo(yes)] macro_rules! assert_eq { ($($x:tt)*) => { { @@ -28,6 +30,7 @@ macro_rules! assert_eq { }; } +#[collapse_debuginfo(yes)] macro_rules! assert_ne { ($($x:tt)*) => { { @@ -39,6 +42,7 @@ macro_rules! assert_ne { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert { ($($x:tt)*) => { { @@ -50,6 +54,7 @@ macro_rules! debug_assert { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert_eq { ($($x:tt)*) => { { @@ -61,6 +66,7 @@ macro_rules! debug_assert_eq { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert_ne { ($($x:tt)*) => { { @@ -72,6 +78,7 @@ macro_rules! debug_assert_ne { }; } +#[collapse_debuginfo(yes)] macro_rules! todo { ($($x:tt)*) => { { @@ -84,6 +91,7 @@ macro_rules! todo { } #[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] macro_rules! unreachable { ($($x:tt)*) => { ::core::unreachable!($($x)*) @@ -91,12 +99,14 @@ macro_rules! unreachable { } #[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] macro_rules! unreachable { ($($x:tt)*) => { ::defmt::unreachable!($($x)*) }; } +#[collapse_debuginfo(yes)] macro_rules! panic { ($($x:tt)*) => { { @@ -108,6 +118,7 @@ macro_rules! panic { }; } +#[collapse_debuginfo(yes)] macro_rules! trace { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -121,6 +132,7 @@ macro_rules! trace { }; } +#[collapse_debuginfo(yes)] macro_rules! debug { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -134,6 +146,7 @@ macro_rules! debug { }; } +#[collapse_debuginfo(yes)] macro_rules! info { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -147,6 +160,7 @@ macro_rules! info { }; } +#[collapse_debuginfo(yes)] macro_rules! warn { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -160,6 +174,7 @@ macro_rules! warn { }; } +#[collapse_debuginfo(yes)] macro_rules! error { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -174,6 +189,7 @@ macro_rules! error { } #[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] macro_rules! unwrap { ($($x:tt)*) => { ::defmt::unwrap!($($x)*) @@ -181,6 +197,7 @@ macro_rules! unwrap { } #[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] macro_rules! unwrap { ($arg:expr) => { match $crate::fmt::Try::into_result($arg) { -- cgit From a716a3f006e439bbd9d11ab858d8ee4a93ec48f8 Mon Sep 17 00:00:00 2001 From: Tarun Singh Date: Wed, 17 Jul 2024 17:05:52 -0400 Subject: Reduced define for 'unreachable!' to a single macro rule --- embassy-sync/src/fmt.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/fmt.rs b/embassy-sync/src/fmt.rs index 35b929fde..8ca61bc39 100644 --- a/embassy-sync/src/fmt.rs +++ b/embassy-sync/src/fmt.rs @@ -90,19 +90,15 @@ macro_rules! todo { }; } -#[cfg(not(feature = "defmt"))] -#[collapse_debuginfo(yes)] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] #[collapse_debuginfo(yes)] macro_rules! unreachable { ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } }; } -- cgit From 05e0f128462ebdacd3dffc27f74502913d782589 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 27 Jul 2024 11:49:02 +0200 Subject: embassy-sync: add LazyLock `LazyLock` is inspired by Rust 1.80.0's `std::sync::LazyLock` type. --- embassy-sync/src/lazy_lock.rs | 84 +++++++++++++++++++++++++++++++++++++++++++ embassy-sync/src/lib.rs | 1 + 2 files changed, 85 insertions(+) create mode 100644 embassy-sync/src/lazy_lock.rs (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs new file mode 100644 index 000000000..2b5742491 --- /dev/null +++ b/embassy-sync/src/lazy_lock.rs @@ -0,0 +1,84 @@ +//! Synchronization primitive for initializing a value once, allowing others to get a reference to the value. + +use core::cell::Cell; +use core::mem::MaybeUninit; +use core::sync::atomic::{AtomicBool, Ordering}; + +/// The `LazyLock` is a synchronization primitive that allows for +/// initializing a value once, and allowing others to obtain a +/// reference to the value. This is useful for lazy initialization of +/// a static value. +/// +/// # Example +/// ``` +/// use futures_executor::block_on; +/// use embassy_sync::lazy_lock::LazyLock; +/// +/// // Define a static value that will be lazily initialized +/// // at runtime at the first access. +/// static VALUE: LazyLock = LazyLock::new(|| 20); +/// +/// let reference = VALUE.get(); +/// assert_eq!(reference, &20); +/// ``` +pub struct LazyLock T> { + init: AtomicBool, + init_fn: Cell>, + data: Cell>, +} + +unsafe impl Sync for LazyLock {} + +impl T> LazyLock { + /// Create a new uninitialized `StaticLock`. + pub const fn new(init_fn: F) -> Self { + Self { + init: AtomicBool::new(false), + init_fn: Cell::new(Some(init_fn)), + data: Cell::new(MaybeUninit::zeroed()), + } + } + + /// Get a reference to the underlying value, initializing it if it + /// has not been done already. + #[inline] + pub fn get(&self) -> &T { + self.ensure_init_fast(); + unsafe { (*self.data.as_ptr()).assume_init_ref() } + } + + /// Consume the `LazyLock`, returning the underlying value. The + /// initialization function will be called if it has not been + /// already. + #[inline] + pub fn into_inner(self) -> T { + self.ensure_init_fast(); + unsafe { self.data.into_inner().assume_init() } + } + + /// Initialize the `LazyLock` if it has not been initialized yet. + /// This function is a fast track to [`Self::ensure_init`] + /// which does not require a critical section in most cases when + /// the value has been initialized already. + /// When this function returns, `self.data` is guaranteed to be + /// initialized and visible on the current core. + #[inline] + fn ensure_init_fast(&self) { + if !self.init.load(Ordering::Acquire) { + self.ensure_init(); + } + } + + /// Initialize the `LazyLock` if it has not been initialized yet. + /// When this function returns, `self.data` is guaranteed to be + /// initialized and visible on the current core. + fn ensure_init(&self) { + critical_section::with(|_| { + if !self.init.load(Ordering::Acquire) { + let init_fn = self.init_fn.take().unwrap(); + self.data.set(MaybeUninit::new(init_fn())); + self.init.store(true, Ordering::Release); + } + }); + } +} diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index a5eee8d02..014bf1d06 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 lazy_lock; pub mod mutex; pub mod once_lock; pub mod pipe; -- cgit From 93696c912e7264e101308a3f205272dcdd44e6b2 Mon Sep 17 00:00:00 2001 From: wanglei Date: Wed, 31 Jul 2024 00:24:39 +0800 Subject: embassy-sync: fix the data of LazyLock never drop Using `union` can save more space. And the `MaybeUninit` will never drop the T, when dropping the `MaybeUninit`. Fixed it. Signed-off-by: wanglei --- embassy-sync/src/lazy_lock.rs | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs index 2b5742491..cf88bfdf8 100644 --- a/embassy-sync/src/lazy_lock.rs +++ b/embassy-sync/src/lazy_lock.rs @@ -1,7 +1,7 @@ //! Synchronization primitive for initializing a value once, allowing others to get a reference to the value. -use core::cell::Cell; -use core::mem::MaybeUninit; +use core::cell::UnsafeCell; +use core::mem::ManuallyDrop; use core::sync::atomic::{AtomicBool, Ordering}; /// The `LazyLock` is a synchronization primitive that allows for @@ -23,8 +23,12 @@ use core::sync::atomic::{AtomicBool, Ordering}; /// ``` pub struct LazyLock T> { init: AtomicBool, - init_fn: Cell>, - data: Cell>, + data: UnsafeCell>, +} + +union Data { + value: ManuallyDrop, + f: ManuallyDrop, } unsafe impl Sync for LazyLock {} @@ -34,8 +38,9 @@ impl T> LazyLock { pub const fn new(init_fn: F) -> Self { Self { init: AtomicBool::new(false), - init_fn: Cell::new(Some(init_fn)), - data: Cell::new(MaybeUninit::zeroed()), + data: UnsafeCell::new(Data { + f: ManuallyDrop::new(init_fn), + }), } } @@ -44,7 +49,7 @@ impl T> LazyLock { #[inline] pub fn get(&self) -> &T { self.ensure_init_fast(); - unsafe { (*self.data.as_ptr()).assume_init_ref() } + unsafe { &(*self.data.get()).value } } /// Consume the `LazyLock`, returning the underlying value. The @@ -53,7 +58,10 @@ impl T> LazyLock { #[inline] pub fn into_inner(self) -> T { self.ensure_init_fast(); - unsafe { self.data.into_inner().assume_init() } + let this = ManuallyDrop::new(self); + let data = unsafe { core::ptr::read(&this.data) }.into_inner(); + + ManuallyDrop::into_inner(unsafe { data.value }) } /// Initialize the `LazyLock` if it has not been initialized yet. @@ -75,10 +83,23 @@ impl T> LazyLock { fn ensure_init(&self) { critical_section::with(|_| { if !self.init.load(Ordering::Acquire) { - let init_fn = self.init_fn.take().unwrap(); - self.data.set(MaybeUninit::new(init_fn())); + let data = unsafe { &mut *self.data.get() }; + let f = unsafe { ManuallyDrop::take(&mut data.f) }; + let value = f(); + data.value = ManuallyDrop::new(value); + self.init.store(true, Ordering::Release); } }); } } + +impl Drop for LazyLock { + fn drop(&mut self) { + if self.init.load(Ordering::Acquire) { + unsafe { ManuallyDrop::drop(&mut self.data.get_mut().value) }; + } else { + unsafe { ManuallyDrop::drop(&mut self.data.get_mut().f) }; + } + } +} -- cgit From 05562b92af53c742c6531ec616afd518112687b8 Mon Sep 17 00:00:00 2001 From: wanglei Date: Wed, 31 Jul 2024 00:25:28 +0800 Subject: embassy-sync: more unit-test for LazyLock Signed-off-by: wanglei --- embassy-sync/src/lazy_lock.rs | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs index cf88bfdf8..18e3c2019 100644 --- a/embassy-sync/src/lazy_lock.rs +++ b/embassy-sync/src/lazy_lock.rs @@ -103,3 +103,50 @@ impl Drop for LazyLock { } } } + +#[cfg(test)] +mod tests { + use core::sync::atomic::{AtomicU32, Ordering}; + + use super::*; + + #[test] + fn test_lazy_lock() { + static VALUE: LazyLock = LazyLock::new(|| 20); + let reference = VALUE.get(); + assert_eq!(reference, &20); + } + #[test] + fn test_lazy_lock_into_inner() { + let lazy: LazyLock = LazyLock::new(|| 20); + let value = lazy.into_inner(); + assert_eq!(value, 20); + } + + static DROP_CHECKER: AtomicU32 = AtomicU32::new(0); + struct DropCheck; + + impl Drop for DropCheck { + fn drop(&mut self) { + DROP_CHECKER.fetch_add(1, Ordering::Acquire); + } + } + + #[test] + fn test_lazy_drop() { + let lazy: LazyLock = LazyLock::new(|| DropCheck); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 0); + lazy.get(); + drop(lazy); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); + + let dropper = DropCheck; + let lazy_fn: LazyLock = LazyLock::new(move || { + let _a = dropper; + 20 + }); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); + drop(lazy_fn); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 2); + } +} -- 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 From a0d17ea5ca0bd76ef4d4398c28bc8f98c4e50065 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 16 Jun 2025 13:57:19 +0200 Subject: Remove futures-util where unnecessary --- embassy-sync/src/channel.rs | 4 ++-- embassy-sync/src/pubsub/subscriber.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 856551417..dda91c651 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -443,7 +443,7 @@ where } } -impl<'ch, M, T, const N: usize> futures_util::Stream for Receiver<'ch, M, T, N> +impl<'ch, M, T, const N: usize> futures_core::Stream for Receiver<'ch, M, T, N> where M: RawMutex, { @@ -962,7 +962,7 @@ where } } -impl futures_util::Stream for Channel +impl futures_core::Stream for Channel where M: RawMutex, { diff --git a/embassy-sync/src/pubsub/subscriber.rs b/embassy-sync/src/pubsub/subscriber.rs index 6ad660cb3..649382cf1 100644 --- a/embassy-sync/src/pubsub/subscriber.rs +++ b/embassy-sync/src/pubsub/subscriber.rs @@ -115,7 +115,7 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Unpin for Sub<'a, PSB, T> {} /// Warning: The stream implementation ignores lag results and returns all messages. /// This might miss some messages without you knowing it. -impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> futures_util::Stream for Sub<'a, PSB, T> { +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> futures_core::Stream for Sub<'a, PSB, T> { type Item = T; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { -- cgit From 051c63fea2fdb9cc9773c3bd311e6c423f3d1cd2 Mon Sep 17 00:00:00 2001 From: Melvin Wang Date: Wed, 18 Jun 2025 15:38:57 -0700 Subject: fix missing sync bounds --- embassy-sync/src/lazy_lock.rs | 7 ++++++- embassy-sync/src/once_lock.rs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs index 18e3c2019..f1bd88b61 100644 --- a/embassy-sync/src/lazy_lock.rs +++ b/embassy-sync/src/lazy_lock.rs @@ -31,7 +31,12 @@ union Data { f: ManuallyDrop, } -unsafe impl Sync for LazyLock {} +unsafe impl Sync for LazyLock +where + T: Sync, + F: Sync, +{ +} impl T> LazyLock { /// Create a new uninitialized `StaticLock`. diff --git a/embassy-sync/src/once_lock.rs b/embassy-sync/src/once_lock.rs index cd05b986d..1e848685a 100644 --- a/embassy-sync/src/once_lock.rs +++ b/embassy-sync/src/once_lock.rs @@ -42,7 +42,7 @@ pub struct OnceLock { data: Cell>, } -unsafe impl Sync for OnceLock {} +unsafe impl Sync for OnceLock where T: Sync {} impl OnceLock { /// Create a new uninitialized `OnceLock`. -- cgit From 72248a601a9ea28ac696f186e2cbe4c2f128a133 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 29 Jun 2025 22:37:11 +0200 Subject: Update Rust nightly, stable. --- embassy-sync/src/pubsub/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index 606efff0a..9206b9383 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -88,7 +88,7 @@ impl Result, Error> { + pub fn subscriber(&self) -> Result, Error> { self.inner.lock(|inner| { let mut s = inner.borrow_mut(); @@ -120,7 +120,7 @@ impl Result, Error> { + pub fn publisher(&self) -> Result, Error> { self.inner.lock(|inner| { let mut s = inner.borrow_mut(); @@ -151,13 +151,13 @@ impl ImmediatePublisher { + pub fn immediate_publisher(&self) -> ImmediatePublisher<'_, M, T, CAP, SUBS, PUBS> { ImmediatePublisher(ImmediatePub::new(self)) } /// Create a new publisher that can only send immediate messages. /// This kind of publisher does not take up a publisher slot. - pub fn dyn_immediate_publisher(&self) -> DynImmediatePublisher { + pub fn dyn_immediate_publisher(&self) -> DynImmediatePublisher<'_, T> { DynImmediatePublisher(ImmediatePub::new(self)) } -- cgit From e4aa539708781af2474240c5c16456ffe554754b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 9 Jul 2025 14:02:20 +0200 Subject: add embassy sync channel example for message passing between interrupt and task --- embassy-sync/src/channel.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index dda91c651..a0e39fcb5 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -17,6 +17,31 @@ //! messages that it can store, and if this limit is reached, trying to send //! another message will result in an error being returned. //! +//! # Example: Message passing between task and interrupt handler +//! +//! ```rust +//! use embassy_sync::channel::Channel; +//! use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +//! +//! static SHARED_CHANNEL: Channel = Channel::new(); +//! +//! fn my_interrupt_handler() { +//! // Do some work.. +//! // ... +//! if let Err(e) = SHARED_CHANNEL.sender().try_send(42) { +//! // Channel is full.. +//! } +//! } +//! +//! async fn my_async_task() { +//! // ... +//! let receiver = SHARED_CHANNEL.receiver(); +//! loop { +//! let data_from_interrupt = receiver.receive().await; +//! // Do something with the data. +//! } +//! } +//! ``` use core::cell::RefCell; use core::future::Future; -- cgit From 42c8379c5a571aa76214cdd73ef05a2c720eeb6e Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 9 Jul 2025 14:21:19 +0200 Subject: some minor documentation fixes --- embassy-sync/src/mutex.rs | 2 +- embassy-sync/src/pipe.rs | 8 ++++---- embassy-sync/src/priority_channel.rs | 4 ++-- embassy-sync/src/pubsub/publisher.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 7528a9f68..67c682704 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -23,7 +23,7 @@ struct State { /// Async mutex. /// -/// The mutex is generic over a blocking [`RawMutex`](crate::blocking_mutex::raw::RawMutex). +/// The mutex is generic over a blocking [RawMutex]. /// The raw mutex is used to guard access to the internal "is locked" flag. It /// is held for very short periods only, while locking and unlocking. It is *not* held /// for the entire time the async Mutex is locked. diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index 2598652d2..df3b28b45 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -152,7 +152,7 @@ where impl<'p, M, const N: usize> Unpin for ReadFuture<'p, M, N> where M: RawMutex {} -/// Future returned by [`Pipe::fill_buf`] and [`Reader::fill_buf`]. +/// Future returned by [`Reader::fill_buf`]. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct FillBufFuture<'p, M, const N: usize> where @@ -587,7 +587,7 @@ where } } -/// Write-only access to a [`DynamicPipe`]. +/// Write-only access to the dynamic pipe. pub struct DynamicWriter<'p> { pipe: &'p dyn DynamicPipe, } @@ -657,7 +657,7 @@ where } } -/// Read-only access to a [`DynamicPipe`]. +/// Read-only access to a dynamic pipe. pub struct DynamicReader<'p> { pipe: &'p dyn DynamicPipe, } @@ -742,7 +742,7 @@ where } } -/// Future returned by [`DynamicPipe::fill_buf`] and [`DynamicReader::fill_buf`]. +/// Future returned by [`DynamicReader::fill_buf`]. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct DynamicFillBufFuture<'p> { pipe: Option<&'p dyn DynamicPipe>, diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 623c52993..a6fbe8def 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -1,7 +1,7 @@ //! A queue for sending values between asynchronous tasks. //! //! Similar to a [`Channel`](crate::channel::Channel), however [`PriorityChannel`] sifts higher priority items to the front of the queue. -//! Priority is determined by the `Ord` trait. Priority behavior is determined by the [`Kind`](heapless::binary_heap::Kind) parameter of the channel. +//! Priority is determined by the `Ord` trait. Priority behavior is determined by the [Kind] parameter of the channel. use core::cell::RefCell; use core::future::Future; @@ -473,7 +473,7 @@ where /// received from the channel. /// /// Sent data may be reordered based on their priority within the channel. -/// For example, in a [`Max`](heapless::binary_heap::Max) [`PriorityChannel`] +/// For example, in a [Max][PriorityChannel] /// containing `u32`'s, data sent in the following order `[1, 2, 3]` will be received as `[3, 2, 1]`. pub struct PriorityChannel where diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs index 7a1ab66de..2af1a9334 100644 --- a/embassy-sync/src/pubsub/publisher.rs +++ b/embassy-sync/src/pubsub/publisher.rs @@ -75,7 +75,7 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { self.channel.is_full() } - /// Create a [`futures::Sink`] adapter for this publisher. + /// Create a [futures_sink::Sink] adapter for this publisher. #[inline] pub const fn sink(&self) -> PubSink<'a, '_, PSB, T> { PubSink { publ: self, fut: None } -- cgit From da392ed942bbf78117f1dbba32208458de7cdea8 Mon Sep 17 00:00:00 2001 From: Robin Mueller <31589589+robamu@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:26:20 +0200 Subject: Update embassy-sync/src/mutex.rs Co-authored-by: James Munns --- embassy-sync/src/mutex.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 67c682704..8496f34bf 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -23,7 +23,7 @@ struct State { /// Async mutex. /// -/// The mutex is generic over a blocking [RawMutex]. +/// The mutex is generic over a blocking [`RawMutex`]. /// The raw mutex is used to guard access to the internal "is locked" flag. It /// is held for very short periods only, while locking and unlocking. It is *not* held /// for the entire time the async Mutex is locked. -- cgit From 9892963da946aa896d9387916ee2b5d509b63e3b Mon Sep 17 00:00:00 2001 From: Robin Mueller <31589589+robamu@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:28:18 +0200 Subject: Update embassy-sync/src/priority_channel.rs Co-authored-by: James Munns --- embassy-sync/src/priority_channel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index a6fbe8def..6765a1503 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -1,7 +1,7 @@ //! A queue for sending values between asynchronous tasks. //! //! Similar to a [`Channel`](crate::channel::Channel), however [`PriorityChannel`] sifts higher priority items to the front of the queue. -//! Priority is determined by the `Ord` trait. Priority behavior is determined by the [Kind] parameter of the channel. +//! Priority is determined by the `Ord` trait. Priority behavior is determined by the [`Kind`] parameter of the channel. use core::cell::RefCell; use core::future::Future; -- cgit From 554fbef571cf9f4692d6361d66f962df926615df Mon Sep 17 00:00:00 2001 From: Robin Mueller <31589589+robamu@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:31:04 +0200 Subject: Update embassy-sync/src/priority_channel.rs Co-authored-by: James Munns --- embassy-sync/src/priority_channel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 6765a1503..715a20e86 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -473,7 +473,7 @@ where /// received from the channel. /// /// Sent data may be reordered based on their priority within the channel. -/// For example, in a [Max][PriorityChannel] +/// For example, in a [`Max`] [`PriorityChannel`] /// containing `u32`'s, data sent in the following order `[1, 2, 3]` will be received as `[3, 2, 1]`. pub struct PriorityChannel where -- cgit From fa0f6bc670c29b799e0ef8812de041727c5d86f3 Mon Sep 17 00:00:00 2001 From: Robin Mueller <31589589+robamu@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:31:42 +0200 Subject: Update embassy-sync/src/pubsub/publisher.rs Co-authored-by: James Munns --- embassy-sync/src/pubsub/publisher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs index 2af1a9334..52a52f926 100644 --- a/embassy-sync/src/pubsub/publisher.rs +++ b/embassy-sync/src/pubsub/publisher.rs @@ -75,7 +75,7 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { self.channel.is_full() } - /// Create a [futures_sink::Sink] adapter for this publisher. + /// Create a [`futures_sink::Sink`] adapter for this publisher. #[inline] pub const fn sink(&self) -> PubSink<'a, '_, PSB, T> { PubSink { publ: self, fut: None } -- cgit From 03b60dd5619bb65dff697cf9dd96f57ccc23f35e Mon Sep 17 00:00:00 2001 From: Anthony Grondin <104731965+AnthonyGrondin@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:01:11 -0400 Subject: feat(embassy-sync): Add `get_mut` for `LazyLock` --- embassy-sync/src/lazy_lock.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs index f1bd88b61..a919f0037 100644 --- a/embassy-sync/src/lazy_lock.rs +++ b/embassy-sync/src/lazy_lock.rs @@ -57,6 +57,14 @@ impl T> LazyLock { unsafe { &(*self.data.get()).value } } + /// Get a mutable reference to the underlying value, initializing it if it + /// has not been done already. + #[inline] + pub fn get_mut(&mut self) -> &mut T { + self.ensure_init_fast(); + unsafe { &mut (*self.data.get()).value } + } + /// Consume the `LazyLock`, returning the underlying value. The /// initialization function will be called if it has not been /// already. @@ -122,6 +130,13 @@ mod tests { assert_eq!(reference, &20); } #[test] + fn test_lazy_lock_mutation() { + let mut value: LazyLock = LazyLock::new(|| 20); + *value.get_mut() = 21; + let reference = value.get(); + assert_eq!(reference, &21); + } + #[test] fn test_lazy_lock_into_inner() { let lazy: LazyLock = LazyLock::new(|| 20); let value = lazy.into_inner(); -- cgit From 89d52827564b7997f0900614c7b0eb67664c121a Mon Sep 17 00:00:00 2001 From: Brezak Date: Fri, 1 Aug 2025 18:42:25 +0200 Subject: embassy-sync: Update `MultiWakerRegistration::register` docs In 3081ecf301a54f8ed3d0f72350dd21f8ac9e1b18 `register` was changed to clear the buffer when it's full, but the docs weren't updated. --- embassy-sync/src/waitqueue/multi_waker.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/waitqueue/multi_waker.rs b/embassy-sync/src/waitqueue/multi_waker.rs index 0384d6bed..1c05f8eaf 100644 --- a/embassy-sync/src/waitqueue/multi_waker.rs +++ b/embassy-sync/src/waitqueue/multi_waker.rs @@ -15,7 +15,9 @@ impl MultiWakerRegistration { Self { wakers: Vec::new() } } - /// Register a waker. If the buffer is full the function returns it in the error + /// Register a waker. + /// + /// If the buffer is full, [wakes all the wakers](Self::wake), clears its buffer and registers the waker. pub fn register(&mut self, w: &Waker) { // If we already have some waker that wakes the same task as `w`, do nothing. // This avoids cloning wakers, and avoids unnecessary mass-wakes. -- cgit From c7b9060a7443cd004d366586c418a3d95bf3447a Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 12 Aug 2025 20:23:20 +0200 Subject: fix: prepare embassy-sync 0.7.1 release * Add newtype for moved type to preserve API compat --- embassy-sync/src/channel.rs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index a0e39fcb5..8e9fcc234 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -419,6 +419,11 @@ pub struct SendDynamicReceiver<'ch, T> { pub(crate) channel: &'ch dyn DynamicChannel, } +/// 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. +#[deprecated(since = "0.7.1", note = "please use `SendDynamicReceiver` instead")] +pub type SendableDynamicReceiver<'ch, T> = SendDynamicReceiver<'ch, T>; + impl<'ch, T> Clone for SendDynamicReceiver<'ch, T> { fn clone(&self) -> Self { *self -- cgit From 368738bef44dbba1a178383d878a6d9423b1ccd9 Mon Sep 17 00:00:00 2001 From: Curly Date: Tue, 19 Aug 2025 22:30:53 -0700 Subject: chore: add more `Debug` impls to `embassy-sync`, particularly on `OnceLock` All tests green --- embassy-sync/src/blocking_mutex/mod.rs | 1 + embassy-sync/src/blocking_mutex/raw.rs | 2 ++ embassy-sync/src/channel.rs | 7 +++++++ embassy-sync/src/lazy_lock.rs | 2 ++ embassy-sync/src/mutex.rs | 1 + embassy-sync/src/once_lock.rs | 10 ++++++++++ embassy-sync/src/pipe.rs | 8 ++++++++ embassy-sync/src/pubsub/mod.rs | 2 ++ embassy-sync/src/pubsub/publisher.rs | 6 ++++++ embassy-sync/src/pubsub/subscriber.rs | 3 +++ embassy-sync/src/ring_buffer.rs | 1 + embassy-sync/src/rwlock.rs | 1 + embassy-sync/src/semaphore.rs | 6 ++++++ embassy-sync/src/signal.rs | 1 + embassy-sync/src/waitqueue/atomic_waker_turbo.rs | 1 + embassy-sync/src/waitqueue/multi_waker.rs | 1 + embassy-sync/src/watch.rs | 6 ++++++ embassy-sync/src/zerocopy_channel.rs | 5 +++++ 18 files changed, 64 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 a41bc3569..11809c763 100644 --- a/embassy-sync/src/blocking_mutex/mod.rs +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -22,6 +22,7 @@ use self::raw::RawMutex; /// /// In all cases, the blocking mutex is intended to be short lived and not held across await points. /// Use the async [`Mutex`](crate::mutex::Mutex) if you need a lock that is held across await points. +#[derive(Debug)] pub struct Mutex { // NOTE: `raw` must be FIRST, so when using ThreadModeMutex the "can't drop in non-thread-mode" gets // to run BEFORE dropping `data`. diff --git a/embassy-sync/src/blocking_mutex/raw.rs b/embassy-sync/src/blocking_mutex/raw.rs index a8afcad34..50f965e00 100644 --- a/embassy-sync/src/blocking_mutex/raw.rs +++ b/embassy-sync/src/blocking_mutex/raw.rs @@ -37,6 +37,7 @@ pub unsafe trait RawMutex { /// # Safety /// /// This mutex is safe to share between different executors and interrupts. +#[derive(Debug)] pub struct CriticalSectionRawMutex { _phantom: PhantomData<()>, } @@ -65,6 +66,7 @@ unsafe impl RawMutex for CriticalSectionRawMutex { /// # Safety /// /// **This Mutex is only safe within a single executor.** +#[derive(Debug)] pub struct NoopRawMutex { _phantom: PhantomData<*mut ()>, } diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 8e9fcc234..de437cc52 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -55,6 +55,7 @@ use crate::blocking_mutex::Mutex; use crate::waitqueue::WakerRegistration; /// Send-only access to a [`Channel`]. +#[derive(Debug)] pub struct Sender<'ch, M, T, const N: usize> where M: RawMutex, @@ -241,6 +242,7 @@ impl<'ch, T> SendDynamicSender<'ch, T> { } /// Receive-only access to a [`Channel`]. +#[derive(Debug)] pub struct Receiver<'ch, M, T, const N: usize> where M: RawMutex, @@ -486,6 +488,7 @@ where /// Future returned by [`Channel::receive`] and [`Receiver::receive`]. #[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] pub struct ReceiveFuture<'ch, M, T, const N: usize> where M: RawMutex, @@ -506,6 +509,7 @@ where /// Future returned by [`Channel::ready_to_receive`] and [`Receiver::ready_to_receive`]. #[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] pub struct ReceiveReadyFuture<'ch, M, T, const N: usize> where M: RawMutex, @@ -549,6 +553,7 @@ impl<'ch, M: RawMutex, T, const N: usize> From> for /// Future returned by [`Channel::send`] and [`Sender::send`]. #[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] pub struct SendFuture<'ch, M, T, const N: usize> where M: RawMutex, @@ -646,6 +651,7 @@ pub enum TrySendError { Full(T), } +#[derive(Debug)] struct ChannelState { queue: Deque, receiver_waker: WakerRegistration, @@ -785,6 +791,7 @@ impl ChannelState { /// received from the channel. /// /// All data sent will become available in the same order as it was sent. +#[derive(Debug)] pub struct Channel where M: RawMutex, diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs index a919f0037..945560a80 100644 --- a/embassy-sync/src/lazy_lock.rs +++ b/embassy-sync/src/lazy_lock.rs @@ -21,6 +21,7 @@ use core::sync::atomic::{AtomicBool, Ordering}; /// let reference = VALUE.get(); /// assert_eq!(reference, &20); /// ``` +#[derive(Debug)] pub struct LazyLock T> { init: AtomicBool, data: UnsafeCell>, @@ -144,6 +145,7 @@ mod tests { } static DROP_CHECKER: AtomicU32 = AtomicU32::new(0); + #[derive(Debug)] struct DropCheck; impl Drop for DropCheck { diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 8496f34bf..4ce6dd987 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -16,6 +16,7 @@ use crate::waitqueue::WakerRegistration; #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TryLockError; +#[derive(Debug)] struct State { locked: bool, waker: WakerRegistration, diff --git a/embassy-sync/src/once_lock.rs b/embassy-sync/src/once_lock.rs index 1e848685a..73edfea9a 100644 --- a/embassy-sync/src/once_lock.rs +++ b/embassy-sync/src/once_lock.rs @@ -1,6 +1,7 @@ //! Synchronization primitive for initializing a value once, allowing others to await a reference to the value. use core::cell::Cell; +use core::fmt::{Debug, Formatter}; use core::future::{poll_fn, Future}; use core::mem::MaybeUninit; use core::sync::atomic::{AtomicBool, Ordering}; @@ -42,6 +43,15 @@ pub struct OnceLock { data: Cell>, } +impl Debug for OnceLock { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("OnceLock") + .field("init", &self.init) + .field("data", &"Cell>") + .finish() + } +} + unsafe impl Sync for OnceLock where T: Sync {} impl OnceLock { diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index df3b28b45..6d624979a 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -13,6 +13,7 @@ use crate::ring_buffer::RingBuffer; use crate::waitqueue::WakerRegistration; /// Write-only access to a [`Pipe`]. +#[derive(Debug)] pub struct Writer<'p, M, const N: usize> where M: RawMutex, @@ -52,6 +53,7 @@ where /// Future returned by [`Pipe::write`] and [`Writer::write`]. #[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] pub struct WriteFuture<'p, M, const N: usize> where M: RawMutex, @@ -77,6 +79,7 @@ where impl<'p, M, const N: usize> Unpin for WriteFuture<'p, M, N> where M: RawMutex {} /// Read-only access to a [`Pipe`]. +#[derive(Debug)] pub struct Reader<'p, M, const N: usize> where M: RawMutex, @@ -128,6 +131,7 @@ where /// Future returned by [`Pipe::read`] and [`Reader::read`]. #[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] pub struct ReadFuture<'p, M, const N: usize> where M: RawMutex, @@ -154,6 +158,7 @@ impl<'p, M, const N: usize> Unpin for ReadFuture<'p, M, N> where M: RawMutex {} /// Future returned by [`Reader::fill_buf`]. #[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] pub struct FillBufFuture<'p, M, const N: usize> where M: RawMutex, @@ -199,6 +204,7 @@ pub enum TryWriteError { Full, } +#[derive(Debug)] struct PipeState { buffer: RingBuffer, read_waker: WakerRegistration, @@ -206,6 +212,7 @@ struct PipeState { } #[repr(transparent)] +#[derive(Debug)] struct Buffer(UnsafeCell<[u8; N]>); impl Buffer { @@ -230,6 +237,7 @@ unsafe impl Sync for Buffer {} /// buffer is full, attempts to `write` new bytes will wait until buffer space is freed up. /// /// All data written will become available in the same order as it was written. +#[derive(Debug)] pub struct Pipe where M: RawMutex, diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index 9206b9383..ad9402f5a 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -71,6 +71,7 @@ pub use subscriber::{DynSubscriber, Subscriber}; /// # block_on(test); /// ``` /// +#[derive(Debug)] pub struct PubSubChannel { inner: Mutex>>, } @@ -297,6 +298,7 @@ impl { /// The queue contains the last messages that have been published and a countdown of how many subscribers are yet to read it queue: Deque<(T, usize), CAP>, diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs index 52a52f926..2a67a0002 100644 --- a/embassy-sync/src/pubsub/publisher.rs +++ b/embassy-sync/src/pubsub/publisher.rs @@ -10,6 +10,7 @@ use super::{PubSubBehavior, PubSubChannel}; use crate::blocking_mutex::raw::RawMutex; /// A publisher to a channel +#[derive(Debug)] pub struct Pub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { /// The channel we are a publisher for channel: &'a PSB, @@ -106,6 +107,7 @@ impl<'a, T: Clone> DerefMut for DynPublisher<'a, T> { } /// A publisher that holds a generic reference to the channel +#[derive(Debug)] pub struct Publisher<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( pub(super) Pub<'a, PubSubChannel, T>, ); @@ -130,6 +132,7 @@ impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: /// A publisher that can only use the `publish_immediate` function, but it doesn't have to be registered with the channel. /// (So an infinite amount is possible) +#[derive(Debug)] pub struct ImmediatePub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { /// The channel we are a publisher for channel: &'a PSB, @@ -205,6 +208,7 @@ impl<'a, T: Clone> DerefMut for DynImmediatePublisher<'a, T> { } /// An immediate publisher that holds a generic reference to the channel +#[derive(Debug)] pub struct ImmediatePublisher<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( pub(super) ImmediatePub<'a, PubSubChannel, T>, ); @@ -229,6 +233,7 @@ 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`]. +#[derive(Debug)] pub struct PubSink<'a, 'p, PSB, T> where T: Clone, @@ -290,6 +295,7 @@ where /// Future for the publisher wait action #[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] pub struct PublisherWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { /// The message we need to publish message: Option, diff --git a/embassy-sync/src/pubsub/subscriber.rs b/embassy-sync/src/pubsub/subscriber.rs index 649382cf1..356de23f6 100644 --- a/embassy-sync/src/pubsub/subscriber.rs +++ b/embassy-sync/src/pubsub/subscriber.rs @@ -10,6 +10,7 @@ use super::{PubSubBehavior, PubSubChannel, WaitResult}; use crate::blocking_mutex::raw::RawMutex; /// A subscriber to a channel +#[derive(Debug)] pub struct Sub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { /// The message id of the next message we are yet to receive next_message_id: u64, @@ -151,6 +152,7 @@ impl<'a, T: Clone> DerefMut for DynSubscriber<'a, T> { } /// A subscriber that holds a generic reference to the channel +#[derive(Debug)] pub struct Subscriber<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( pub(super) Sub<'a, PubSubChannel, T>, ); @@ -175,6 +177,7 @@ impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: /// Future for the subscriber wait action #[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] pub struct SubscriberWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { subscriber: &'s mut Sub<'a, PSB, T>, } diff --git a/embassy-sync/src/ring_buffer.rs b/embassy-sync/src/ring_buffer.rs index 81e60c42b..f03b7dd8f 100644 --- a/embassy-sync/src/ring_buffer.rs +++ b/embassy-sync/src/ring_buffer.rs @@ -1,5 +1,6 @@ use core::ops::Range; +#[derive(Debug)] pub struct RingBuffer { start: usize, end: usize, diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index deeadd167..0d784a7dc 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -16,6 +16,7 @@ use crate::waitqueue::WakerRegistration; #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TryLockError; +#[derive(Debug)] struct State { readers: usize, writer: bool, diff --git a/embassy-sync/src/semaphore.rs b/embassy-sync/src/semaphore.rs index d30eee30b..4e82b0fcd 100644 --- a/embassy-sync/src/semaphore.rs +++ b/embassy-sync/src/semaphore.rs @@ -46,6 +46,7 @@ pub trait Semaphore: Sized { /// A representation of a number of acquired permits. /// /// The acquired permits will be released back to the [`Semaphore`] when this is dropped. +#[derive(Debug)] pub struct SemaphoreReleaser<'a, S: Semaphore> { semaphore: &'a S, permits: usize, @@ -181,6 +182,7 @@ impl Semaphore for GreedySemaphore { } } +#[derive(Debug)] struct SemaphoreState { permits: usize, waker: WakerRegistration, @@ -221,6 +223,7 @@ impl SemaphoreState { /// /// Up to `N` tasks may attempt to acquire permits concurrently. If additional /// tasks attempt to acquire a permit, a [`WaitQueueFull`] error will be returned. +#[derive(Debug)] pub struct FairSemaphore where M: RawMutex, @@ -341,6 +344,7 @@ impl Semaphore for FairSemaphore { } } +#[derive(Debug)] struct FairAcquire<'a, M: RawMutex, const N: usize> { sema: &'a FairSemaphore, permits: usize, @@ -364,6 +368,7 @@ impl<'a, M: RawMutex, const N: usize> core::future::Future for FairAcquire<'a, M } } +#[derive(Debug)] struct FairAcquireAll<'a, M: RawMutex, const N: usize> { sema: &'a FairSemaphore, min: usize, @@ -387,6 +392,7 @@ impl<'a, M: RawMutex, const N: usize> core::future::Future for FairAcquireAll<'a } } +#[derive(Debug)] struct FairSemaphoreState { permits: usize, next_ticket: usize, diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs index e7095401e..d96e36245 100644 --- a/embassy-sync/src/signal.rs +++ b/embassy-sync/src/signal.rs @@ -39,6 +39,7 @@ where state: Mutex>>, } +#[derive(Debug)] enum State { None, Waiting(Waker), diff --git a/embassy-sync/src/waitqueue/atomic_waker_turbo.rs b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs index c06b83056..a45adeab8 100644 --- a/embassy-sync/src/waitqueue/atomic_waker_turbo.rs +++ b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs @@ -7,6 +7,7 @@ use core::task::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. +#[derive(Debug)] pub struct AtomicWaker { waker: AtomicPtr<()>, } diff --git a/embassy-sync/src/waitqueue/multi_waker.rs b/embassy-sync/src/waitqueue/multi_waker.rs index 1c05f8eaf..56c0cd1b2 100644 --- a/embassy-sync/src/waitqueue/multi_waker.rs +++ b/embassy-sync/src/waitqueue/multi_waker.rs @@ -5,6 +5,7 @@ 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. +#[derive(Debug)] pub struct MultiWakerRegistration { wakers: Vec, } diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 08d6a833d..332ab5405 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -65,10 +65,12 @@ use crate::waitqueue::MultiWakerRegistration; /// }; /// block_on(f); /// ``` +#[derive(Debug)] pub struct Watch { mutex: Mutex>>, } +#[derive(Debug)] struct WatchState { data: Option, current_id: u64, @@ -392,6 +394,7 @@ impl Watch { } /// A receiver can `.await` a change in the `Watch` value. +#[derive(Debug)] pub struct Snd<'a, T: Clone, W: WatchBehavior + ?Sized> { watch: &'a W, _phantom: PhantomData, @@ -467,6 +470,7 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { /// /// For a simpler type definition, consider [`DynSender`] at the expense of /// some runtime performance due to dynamic dispatch. +#[derive(Debug)] 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> { @@ -622,6 +626,7 @@ 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. +#[derive(Debug)] pub struct AnonRcv<'a, T: Clone, W: WatchBehavior + ?Sized> { watch: &'a W, at_id: u64, @@ -726,6 +731,7 @@ impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { } /// A receiver of a `Watch` channel that cannot `.await` values. +#[derive(Debug)] 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> { diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index e3e5b2538..b3f7dbe8c 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -34,6 +34,7 @@ use crate::waitqueue::WakerRegistration; /// /// The channel requires a buffer of recyclable elements. Writing to the channel is done through /// an `&mut T`. +#[derive(Debug)] pub struct Channel<'a, M: RawMutex, T> { buf: BufferPtr, phantom: PhantomData<&'a mut T>, @@ -95,6 +96,7 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { } #[repr(transparent)] +#[derive(Debug)] struct BufferPtr(*mut T); impl BufferPtr { @@ -107,6 +109,7 @@ unsafe impl Send for BufferPtr {} unsafe impl Sync for BufferPtr {} /// Send-only access to a [`Channel`]. +#[derive(Debug)] pub struct Sender<'a, M: RawMutex, T> { channel: &'a Channel<'a, M, T>, } @@ -190,6 +193,7 @@ impl<'a, M: RawMutex, T> Sender<'a, M, T> { } /// Receive-only access to a [`Channel`]. +#[derive(Debug)] pub struct Receiver<'a, M: RawMutex, T> { channel: &'a Channel<'a, M, T>, } @@ -272,6 +276,7 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { } } +#[derive(Debug)] struct State { /// Maximum number of elements the channel can hold. capacity: usize, -- cgit From fcf659fbe5c0cd6acf328281089c35c999f5514a Mon Sep 17 00:00:00 2001 From: Matthew Tran <0e4ef622@gmail.com> Date: Thu, 28 Aug 2025 10:35:27 -0500 Subject: embassy-sync: Don't drop wakers in Signal::reset --- embassy-sync/src/signal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs index d96e36245..229b1fa99 100644 --- a/embassy-sync/src/signal.rs +++ b/embassy-sync/src/signal.rs @@ -83,7 +83,7 @@ where /// Remove the queued value in this `Signal`, if any. pub fn reset(&self) { - self.state.lock(|cell| cell.set(State::None)); + self.try_take(); } fn poll_wait(&self, cx: &mut Context<'_>) -> Poll { -- cgit From 78d5d3f2dde14fcbf4879de19076eb89d9b9ef8b Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 11 Sep 2025 14:40:29 -0700 Subject: Remove `Sized` bound from `MutexGuard::map` Since `MutexGuard` has `T: ?Sized`, `U` does not need to be restricted to `Sized` types. This now allows using `map` to cast from `MutexGuard<'_, M, ImplsTrait>` to `MutexGuard<'_, M, dyn Trait>`. --- embassy-sync/src/mutex.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 4ce6dd987..aea682899 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -187,7 +187,7 @@ where 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) -> MappedMutexGuard<'a, M, U> { + pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedMutexGuard<'a, M, U> { let mutex = this.mutex; let value = fun(unsafe { &mut *this.mutex.inner.get() }); // Don't run the `drop` method for MutexGuard. The ownership of the underlying @@ -279,7 +279,7 @@ where 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) -> MappedMutexGuard<'a, M, U> { + pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedMutexGuard<'a, M, U> { let state = this.state; let value = fun(unsafe { &mut *this.value }); // Don't run the `drop` method for MutexGuard. The ownership of the underlying -- cgit From 99febbe3a42ac05b723e6e76088d159eb0acfa5e Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 23 Sep 2025 13:55:55 +0200 Subject: more docs fixes --- embassy-sync/src/blocking_mutex/mod.rs | 4 ++-- embassy-sync/src/blocking_mutex/raw.rs | 4 ++-- embassy-sync/src/rwlock.rs | 2 -- 3 files changed, 4 insertions(+), 6 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 11809c763..62bfc26fb 100644 --- a/embassy-sync/src/blocking_mutex/mod.rs +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -135,9 +135,9 @@ impl Mutex { // There's still a ThreadModeRawMutex for use with the generic Mutex (handy with Channel, for example), // but that will require T: Send even though it shouldn't be needed. -#[cfg(any(cortex_m, feature = "std"))] +#[cfg(any(cortex_m, doc, feature = "std"))] pub use thread_mode_mutex::*; -#[cfg(any(cortex_m, feature = "std"))] +#[cfg(any(cortex_m, doc, feature = "std"))] mod thread_mode_mutex { use super::*; diff --git a/embassy-sync/src/blocking_mutex/raw.rs b/embassy-sync/src/blocking_mutex/raw.rs index 50f965e00..fbb9ece15 100644 --- a/embassy-sync/src/blocking_mutex/raw.rs +++ b/embassy-sync/src/blocking_mutex/raw.rs @@ -89,7 +89,7 @@ unsafe impl RawMutex for NoopRawMutex { // ================ -#[cfg(any(cortex_m, feature = "std"))] +#[cfg(any(cortex_m, doc, feature = "std"))] mod thread_mode { use super::*; @@ -147,5 +147,5 @@ mod thread_mode { return unsafe { (0xE000ED04 as *const u32).read_volatile() } & 0x1FF == 0; } } -#[cfg(any(cortex_m, feature = "std"))] +#[cfg(any(cortex_m, doc, feature = "std"))] pub use thread_mode::*; diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index 0d784a7dc..e43388c4d 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -37,8 +37,6 @@ struct State { /// 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, -- cgit From abc8e450f936567ad42cb34b5d2a7941b206aa5d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 6 Oct 2025 22:55:38 +0200 Subject: Edition 2024. --- embassy-sync/src/lib.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index 5d91b4d9c..1cfde8b10 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(async_fn_in_trait)] #![allow(clippy::new_without_default)] +#![allow(unsafe_op_in_unsafe_fn)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] -- cgit From 8730a013c395cf0bf4c2fa8eeb7f138288103039 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 6 Oct 2025 22:56:31 +0200 Subject: Rustfmt for edition 2024. --- embassy-sync/src/channel.rs | 30 ++++++++++++++++------------ embassy-sync/src/mutex.rs | 4 ++-- embassy-sync/src/once_lock.rs | 2 +- embassy-sync/src/pipe.rs | 2 +- embassy-sync/src/priority_channel.rs | 32 +++++++++++++++++------------- embassy-sync/src/pubsub/mod.rs | 2 +- embassy-sync/src/ring_buffer.rs | 6 +----- embassy-sync/src/rwlock.rs | 4 ++-- embassy-sync/src/semaphore.rs | 4 ++-- embassy-sync/src/signal.rs | 4 ++-- embassy-sync/src/waitqueue/atomic_waker.rs | 2 +- embassy-sync/src/watch.rs | 4 ++-- embassy-sync/src/zerocopy_channel.rs | 10 +++------- 13 files changed, 53 insertions(+), 53 deletions(-) (limited to 'embassy-sync/src') diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index de437cc52..dbd24a6c7 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -50,8 +50,8 @@ use core::task::{Context, Poll}; use heapless::Deque; -use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; +use crate::blocking_mutex::raw::RawMutex; use crate::waitqueue::WakerRegistration; /// Send-only access to a [`Channel`]. @@ -1112,11 +1112,13 @@ mod tests { static CHANNEL: StaticCell> = StaticCell::new(); let c = &*CHANNEL.init(Channel::new()); let c2 = c; - assert!(executor - .spawn(async move { - assert!(c2.try_send(1).is_ok()); - }) - .is_ok()); + assert!( + executor + .spawn(async move { + assert!(c2.try_send(1).is_ok()); + }) + .is_ok() + ); assert_eq!(c.receive().await, 1); } @@ -1143,13 +1145,15 @@ mod tests { // However, I've used the debugger to observe that the send does indeed wait. Delay::new(Duration::from_millis(500)).await; assert_eq!(c.receive().await, 1); - assert!(executor - .spawn(async move { - loop { - c.receive().await; - } - }) - .is_ok()); + assert!( + executor + .spawn(async move { + loop { + c.receive().await; + } + }) + .is_ok() + ); send_task_1.unwrap().await; send_task_2.unwrap().await; } diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index aea682899..96b834f02 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -2,13 +2,13 @@ //! //! 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, Future}; +use core::future::{Future, poll_fn}; 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; +use crate::blocking_mutex::raw::RawMutex; use crate::waitqueue::WakerRegistration; /// Error returned by [`Mutex::try_lock`] diff --git a/embassy-sync/src/once_lock.rs b/embassy-sync/src/once_lock.rs index 73edfea9a..2af19ca20 100644 --- a/embassy-sync/src/once_lock.rs +++ b/embassy-sync/src/once_lock.rs @@ -2,7 +2,7 @@ use core::cell::Cell; use core::fmt::{Debug, Formatter}; -use core::future::{poll_fn, Future}; +use core::future::{Future, poll_fn}; use core::mem::MaybeUninit; use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index 6d624979a..215a556d9 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -7,8 +7,8 @@ use core::ops::Range; use core::pin::Pin; use core::task::{Context, Poll}; -use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; +use crate::blocking_mutex::raw::RawMutex; use crate::ring_buffer::RingBuffer; use crate::waitqueue::WakerRegistration; diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 715a20e86..1af7d9221 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -8,11 +8,11 @@ use core::future::Future; use core::pin::Pin; use core::task::{Context, Poll}; -pub use heapless::binary_heap::{Kind, Max, Min}; use heapless::BinaryHeap; +pub use heapless::binary_heap::{Kind, Max, Min}; -use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; +use crate::blocking_mutex::raw::RawMutex; use crate::channel::{DynamicChannel, DynamicReceiver, DynamicSender, TryReceiveError, TrySendError}; use crate::waitqueue::WakerRegistration; @@ -799,11 +799,13 @@ mod tests { static CHANNEL: StaticCell> = StaticCell::new(); let c = &*CHANNEL.init(PriorityChannel::new()); let c2 = c; - assert!(executor - .spawn(async move { - assert!(c2.try_send(1).is_ok()); - }) - .is_ok()); + assert!( + executor + .spawn(async move { + assert!(c2.try_send(1).is_ok()); + }) + .is_ok() + ); assert_eq!(c.receive().await, 1); } @@ -830,13 +832,15 @@ mod tests { // However, I've used the debugger to observe that the send does indeed wait. Delay::new(Duration::from_millis(500)).await; assert_eq!(c.receive().await, 1); - assert!(executor - .spawn(async move { - loop { - c.receive().await; - } - }) - .is_ok()); + assert!( + executor + .spawn(async move { + loop { + c.receive().await; + } + }) + .is_ok() + ); send_task_1.unwrap().await; send_task_2.unwrap().await; } diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index ad9402f5a..127a208f1 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -10,8 +10,8 @@ use heapless::Deque; use self::publisher::{ImmediatePub, Pub}; use self::subscriber::Sub; -use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; +use crate::blocking_mutex::raw::RawMutex; use crate::waitqueue::MultiWakerRegistration; pub mod publisher; diff --git a/embassy-sync/src/ring_buffer.rs b/embassy-sync/src/ring_buffer.rs index f03b7dd8f..608447cd6 100644 --- a/embassy-sync/src/ring_buffer.rs +++ b/embassy-sync/src/ring_buffer.rs @@ -95,11 +95,7 @@ impl RingBuffer { fn wrap(&self, n: usize) -> usize { assert!(n <= N); - if n == N { - 0 - } else { - n - } + if n == N { 0 } else { n } } } diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs index e43388c4d..918a6aa41 100644 --- a/embassy-sync/src/rwlock.rs +++ b/embassy-sync/src/rwlock.rs @@ -3,12 +3,12 @@ //! 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::future::{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::blocking_mutex::raw::RawMutex; use crate::waitqueue::WakerRegistration; /// Error returned by [`RwLock::try_read`] and [`RwLock::try_write`] when the lock is already held. diff --git a/embassy-sync/src/semaphore.rs b/embassy-sync/src/semaphore.rs index 4e82b0fcd..8d2413931 100644 --- a/embassy-sync/src/semaphore.rs +++ b/embassy-sync/src/semaphore.rs @@ -1,13 +1,13 @@ //! A synchronization primitive for controlling access to a pool of resources. use core::cell::{Cell, RefCell}; use core::convert::Infallible; -use core::future::{poll_fn, Future}; +use core::future::{Future, poll_fn}; use core::task::{Poll, Waker}; use heapless::Deque; -use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; +use crate::blocking_mutex::raw::RawMutex; use crate::waitqueue::WakerRegistration; /// An asynchronous semaphore. diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs index 229b1fa99..cc02228cf 100644 --- a/embassy-sync/src/signal.rs +++ b/embassy-sync/src/signal.rs @@ -1,10 +1,10 @@ //! A synchronization primitive for passing the latest value to a task. use core::cell::Cell; -use core::future::{poll_fn, Future}; +use core::future::{Future, poll_fn}; use core::task::{Context, Poll, Waker}; -use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; +use crate::blocking_mutex::raw::RawMutex; /// Single-slot signaling primitive for a _single_ consumer. /// diff --git a/embassy-sync/src/waitqueue/atomic_waker.rs b/embassy-sync/src/waitqueue/atomic_waker.rs index 5a9910e7f..d2bf890e5 100644 --- a/embassy-sync/src/waitqueue/atomic_waker.rs +++ b/embassy-sync/src/waitqueue/atomic_waker.rs @@ -1,8 +1,8 @@ use core::cell::Cell; use core::task::Waker; -use crate::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex}; use crate::blocking_mutex::Mutex; +use crate::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex}; /// Utility struct to register and wake a waker. /// If a waker is registered, registering another waker will replace the previous one without waking it. diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 332ab5405..0f8a8d679 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -1,13 +1,13 @@ //! A synchronization primitive for passing the latest value to **multiple** receivers. use core::cell::RefCell; -use core::future::{poll_fn, Future}; +use core::future::{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::blocking_mutex::raw::RawMutex; use crate::waitqueue::MultiWakerRegistration; /// The `Watch` is a single-slot signaling primitive that allows _multiple_ (`N`) receivers to concurrently await diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index b3f7dbe8c..c572592b8 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -15,12 +15,12 @@ //! another message will result in an error being returned. use core::cell::RefCell; -use core::future::{poll_fn, Future}; +use core::future::{Future, poll_fn}; use core::marker::PhantomData; use core::task::{Context, Poll}; -use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; +use crate::blocking_mutex::raw::RawMutex; use crate::waitqueue::WakerRegistration; /// A bounded zero-copy channel for communicating between asynchronous tasks @@ -296,11 +296,7 @@ struct State { impl State { fn increment(&self, i: usize) -> usize { - if i + 1 == self.capacity { - 0 - } else { - i + 1 - } + if i + 1 == self.capacity { 0 } else { i + 1 } } fn clear(&mut self) { -- cgit