Continuing the discussion from Why isn't there a watch::Sender::new in tokio::sync:
While that problem above can be worked around by using Sender::send_replace
, I have run into yet another problem with closed channels, which I would like to explain in the following:
Receiver::borrow_and_update
will cause a read-lock. I thus assumed that it's better to check first if the value has changed by using Receiver::has_changed
(which uses an atomic operation, see source, which should be cheaper than acquiring/releasing a read-lock?).
Unfortunately, the has_changed
method will fail if the Sender
has been dropped (and it's not even possible to clone the Sender
and store the clone somewhere else):
use tokio::sync::watch;
fn main() {
let (tx, mut rx) = watch::channel(1);
tx.send_replace(2);
//drop(tx); // if `Sender` is dropped, `has_changed` becomes useless:
if rx.has_changed().unwrap_or(false) {
println!("{}", rx.borrow_and_update().clone());
}
}
Given a scenario where the Sender
might be dropped at any point but I still want to retrieve the last sent value, I seem to have to work around this problem in a complicated way:
use tokio::sync::watch;
fn main() {
let (tx, mut rx) = watch::channel(1);
let mut closed = false;
tx.send_replace(2);
drop(tx);
for _ in 0..3 {
let changed = match closed {
false => rx.has_changed(),
true => Ok(false),
};
let close = changed.is_err();
if changed.unwrap_or(true) {
println!("{}", rx.borrow_and_update().clone());
}
if close {
closed = true;
}
}
}
Here, I ensure that if the channel is closed, I will borrow_and_update
exactly one more time (such that I don't miss any potential update). It is unwieldy and also invokes borrow_and_update
once without need if there wasn't any change (but the Sender
was just dropped).
How can I solve this problem in a better way?
I guess the check for a closed channel in has_changed
(source) could be easily omitted? Perhaps Tokio could/should have an alternative method which doesn't include this check? This would simplify use cases where the Receiver
must not accidentally miss sent values.
Update: Re-thinking about this, I think I can (and should) modify my receiving task such that it terminates when the struct that's keeping the Sender
is dropped. Thus, I think I no longer have this problem myself.