mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-29 00:00:11 +02:00
sync: clarify the behavior of tokio::sync::watch::Receiver (#7584)
This commit is contained in:
+85
-13
@@ -29,8 +29,10 @@
|
|||||||
//! The [`Receiver`] half provides an asynchronous [`changed`] method. This
|
//! The [`Receiver`] half provides an asynchronous [`changed`] method. This
|
||||||
//! method is ready when a new, *unseen* value is sent via the [`Sender`] half.
|
//! method is ready when a new, *unseen* value is sent via the [`Sender`] half.
|
||||||
//!
|
//!
|
||||||
//! * [`Receiver::changed()`] returns `Ok(())` on receiving a new value, or
|
//! * [`Receiver::changed()`] returns:
|
||||||
//! `Err(`[`error::RecvError`]`)` if all [`Sender`]s have been dropped.
|
//! * `Ok(())` on receiving a new value.
|
||||||
|
//! * `Err(`[`RecvError`](error::RecvError)`)` if the
|
||||||
|
//! channel has been closed __AND__ the current value is *seen*.
|
||||||
//! * If the current value is *unseen* when calling [`changed`], then
|
//! * If the current value is *unseen* when calling [`changed`], then
|
||||||
//! [`changed`] will return immediately. If the current value is *seen*, then
|
//! [`changed`] will return immediately. If the current value is *seen*, then
|
||||||
//! it will sleep until either a new message is sent via the [`Sender`] half,
|
//! it will sleep until either a new message is sent via the [`Sender`] half,
|
||||||
@@ -42,7 +44,28 @@
|
|||||||
//! The current value at the time the [`Receiver`] is created is considered
|
//! The current value at the time the [`Receiver`] is created is considered
|
||||||
//! *seen*.
|
//! *seen*.
|
||||||
//!
|
//!
|
||||||
//! ## `borrow_and_update` versus `borrow`
|
//! ## [`changed`] versus [`has_changed`]
|
||||||
|
//!
|
||||||
|
//! The [`Receiver`] half provides two methods for checking for changes
|
||||||
|
//! in the channel, [`has_changed`] and [`changed`].
|
||||||
|
//!
|
||||||
|
//! * [`has_changed`] is a *synchronous* method that checks whether the current
|
||||||
|
//! value is seen or not and returns a boolean. This method does __not__ mark the
|
||||||
|
//! value as seen.
|
||||||
|
//!
|
||||||
|
//! * [`changed`] is an *asynchronous* method that will return once an unseen
|
||||||
|
//! value is in the channel. This method does mark the value as seen.
|
||||||
|
//!
|
||||||
|
//! Note there are two behavioral differences on when these two methods return
|
||||||
|
//! an error.
|
||||||
|
//!
|
||||||
|
//! - [`has_changed`] errors if and only if the channel is closed.
|
||||||
|
//! - [`changed`] errors if the channel has been closed __AND__
|
||||||
|
//! the current value is seen.
|
||||||
|
//!
|
||||||
|
//! See the example below that shows how these methods have different fallibility.
|
||||||
|
//!
|
||||||
|
//! ## [`borrow_and_update`] versus [`borrow`]
|
||||||
//!
|
//!
|
||||||
//! If the receiver intends to await notifications from [`changed`] in a loop,
|
//! If the receiver intends to await notifications from [`changed`] in a loop,
|
||||||
//! [`Receiver::borrow_and_update()`] should be preferred over
|
//! [`Receiver::borrow_and_update()`] should be preferred over
|
||||||
@@ -84,6 +107,31 @@
|
|||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! Difference on fallibility of [`changed`] versus [`has_changed`].
|
||||||
|
//! ```
|
||||||
|
//! use tokio::sync::watch;
|
||||||
|
//!
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! let (tx, mut rx) = watch::channel("hello");
|
||||||
|
//! tx.send("goodbye").unwrap();
|
||||||
|
//! drop(tx);
|
||||||
|
//!
|
||||||
|
//! // `has_changed` does not mark the value as seen and errors
|
||||||
|
//! // since the channel is closed.
|
||||||
|
//! assert!(rx.has_changed().is_err());
|
||||||
|
//!
|
||||||
|
//! // `changed` returns Ok since the value is not already marked as seen
|
||||||
|
//! // even if the channel is closed.
|
||||||
|
//! assert!(rx.changed().await.is_ok());
|
||||||
|
//!
|
||||||
|
//! // The `changed` call above marks the value as seen.
|
||||||
|
//! // The next `changed` call now returns an error as the channel is closed
|
||||||
|
//! // AND the current value is seen.
|
||||||
|
//! assert!(rx.changed().await.is_err());
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! # Closing
|
//! # Closing
|
||||||
//!
|
//!
|
||||||
//! [`Sender::is_closed`] and [`Sender::closed`] allow the producer to detect
|
//! [`Sender::is_closed`] and [`Sender::closed`] allow the producer to detect
|
||||||
@@ -102,6 +150,9 @@
|
|||||||
//! [`Sender`]: crate::sync::watch::Sender
|
//! [`Sender`]: crate::sync::watch::Sender
|
||||||
//! [`Receiver`]: crate::sync::watch::Receiver
|
//! [`Receiver`]: crate::sync::watch::Receiver
|
||||||
//! [`changed`]: crate::sync::watch::Receiver::changed
|
//! [`changed`]: crate::sync::watch::Receiver::changed
|
||||||
|
//! [`has_changed`]: crate::sync::watch::Receiver::has_changed
|
||||||
|
//! [`borrow`]: crate::sync::watch::Receiver::borrow
|
||||||
|
//! [`borrow_and_update`]: crate::sync::watch::Receiver::borrow_and_update
|
||||||
//! [`Receiver::changed()`]: crate::sync::watch::Receiver::changed
|
//! [`Receiver::changed()`]: crate::sync::watch::Receiver::changed
|
||||||
//! [`Receiver::borrow()`]: crate::sync::watch::Receiver::borrow
|
//! [`Receiver::borrow()`]: crate::sync::watch::Receiver::borrow
|
||||||
//! [`Receiver::borrow_and_update()`]:
|
//! [`Receiver::borrow_and_update()`]:
|
||||||
@@ -637,15 +688,20 @@ impl<T> Receiver<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if this channel contains a message that this receiver has not yet
|
/// Checks if this channel contains a message that this receiver has not yet
|
||||||
/// seen. The new value is not marked as seen.
|
/// seen. The current value will not be marked as seen.
|
||||||
///
|
///
|
||||||
/// Although this method is called `has_changed`, it does not check new
|
/// Although this method is called `has_changed`, it does not check
|
||||||
/// messages for equality, so this call will return true even if the new
|
/// messages for equality, so this call will return true even if the current
|
||||||
/// message is equal to the old message.
|
/// message is equal to the previous message.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a [`RecvError`](error::RecvError) if and only if the channel has been closed.
|
||||||
///
|
///
|
||||||
/// Returns an error if the channel has been closed.
|
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
/// ## Basic usage
|
||||||
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tokio::sync::watch;
|
/// use tokio::sync::watch;
|
||||||
///
|
///
|
||||||
@@ -660,9 +716,22 @@ impl<T> Receiver<T> {
|
|||||||
///
|
///
|
||||||
/// // The value has been marked as seen
|
/// // The value has been marked as seen
|
||||||
/// assert!(!rx.has_changed().unwrap());
|
/// assert!(!rx.has_changed().unwrap());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Closed channel example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tokio::sync::watch;
|
||||||
|
///
|
||||||
|
/// #[tokio::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let (tx, rx) = watch::channel("hello");
|
||||||
|
/// tx.send("goodbye").unwrap();
|
||||||
///
|
///
|
||||||
/// drop(tx);
|
/// drop(tx);
|
||||||
/// // The `tx` handle has been dropped
|
///
|
||||||
|
/// // The channel is closed
|
||||||
/// assert!(rx.has_changed().is_err());
|
/// assert!(rx.has_changed().is_err());
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
@@ -701,19 +770,22 @@ impl<T> Receiver<T> {
|
|||||||
self.version = current_version;
|
self.version = current_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Waits for a change notification, then marks the newest value as seen.
|
/// Waits for a change notification, then marks the current value as seen.
|
||||||
///
|
///
|
||||||
/// If the newest value in the channel has not yet been marked seen when
|
/// If the current value in the channel has not yet been marked seen when
|
||||||
/// this method is called, the method marks that value seen and returns
|
/// this method is called, the method marks that value seen and returns
|
||||||
/// immediately. If the newest value has already been marked seen, then the
|
/// immediately. If the newest value has already been marked seen, then the
|
||||||
/// method sleeps until a new message is sent by a [`Sender`] connected to
|
/// method sleeps until a new message is sent by a [`Sender`] connected to
|
||||||
/// this `Receiver`, or until all [`Sender`]s are dropped.
|
/// this `Receiver`, or until all [`Sender`]s are dropped.
|
||||||
///
|
///
|
||||||
/// This method returns an error if and only if all [`Sender`]s are dropped.
|
|
||||||
///
|
|
||||||
/// For more information, see
|
/// For more information, see
|
||||||
/// [*Change notifications*](self#change-notifications) in the module-level documentation.
|
/// [*Change notifications*](self#change-notifications) in the module-level documentation.
|
||||||
///
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a [`RecvError`](error::RecvError) if the channel has been closed __AND__
|
||||||
|
/// the current value is seen.
|
||||||
|
///
|
||||||
/// # Cancel safety
|
/// # Cancel safety
|
||||||
///
|
///
|
||||||
/// This method is cancel safe. If you use it as the event in a
|
/// This method is cancel safe. If you use it as the event in a
|
||||||
|
|||||||
@@ -450,3 +450,56 @@ async fn sender_closed_is_cooperative() {
|
|||||||
_ = tokio::task::yield_now() => {},
|
_ = tokio::task::yield_now() => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn changed_succeeds_on_closed_channel_with_unseen_value() {
|
||||||
|
let (tx, mut rx) = watch::channel("A");
|
||||||
|
tx.send("B").unwrap();
|
||||||
|
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
rx.changed()
|
||||||
|
.await
|
||||||
|
.expect("should not return error as long as the current value is not seen");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn changed_errors_on_closed_channel_with_seen_value() {
|
||||||
|
let (tx, mut rx) = watch::channel("A");
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
rx.changed()
|
||||||
|
.await
|
||||||
|
.expect_err("should return error if the tx is closed and the current value is seen");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn has_changed_errors_on_closed_channel_with_unseen_value() {
|
||||||
|
let (tx, rx) = watch::channel("A");
|
||||||
|
tx.send("B").unwrap();
|
||||||
|
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
rx.has_changed()
|
||||||
|
.expect_err("`has_changed` returns an error if and only if channel is closed. Even if the current value is not seen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn has_changed_errors_on_closed_channel_with_seen_value() {
|
||||||
|
let (tx, rx) = watch::channel("A");
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
rx.has_changed()
|
||||||
|
.expect_err("`has_changed` returns an error if and only if channel is closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn wait_for_errors_on_closed_channel_true_predicate() {
|
||||||
|
let (tx, mut rx) = watch::channel("A");
|
||||||
|
tx.send("B").unwrap();
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
rx.wait_for(|_| true).await.expect(
|
||||||
|
"`wait_for` call does not return error even if channel is closed when predicate is true for last value.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user