Skip to content

tokio::sync::watch::Receiver does not adhere to the specification #7281

@beroal

Description

@beroal

Version

└── tokio v1.44.2
    └── tokio-macros v2.5.0 (proc-macro)

Platform
Linux beroal 6.14.3-arch1-1 #1 SMP PREEMPT_DYNAMIC Sun, 20 Apr 2025 12:38:52 +0000 x86_64 GNU/Linux

Description

Hi. When exploring whether Receiver can detect that the corresponding Senders are dropped, I found the following behavior demonstrated by the program below.

use std::error::Error;
use tokio::sync::watch;

fn new_watch<T: Clone>(initial: T)
-> Result<(watch::Sender<T>, watch::Receiver<T>), watch::error::SendError<T>> {
    let (tx, rx) = watch::channel(initial.clone());
    let () = tx.send(initial)?;
    Ok((tx, rx))
}

async fn has_changed() -> Result<(), Box<dyn Error>> {
    let (_tx, rx) = new_watch(()).map_err(Box::new)?;
    rx.has_changed().map_err(Box::new)?;
    Ok(())
}

async fn drop_sender_then_has_changed() -> Result<(), Box<dyn Error>> {
    let rx = new_watch(()).map_err(Box::new)?.1;
    rx.has_changed().map_err(Box::new)?;
    Ok(())
}

async fn drop_sender_then_changed() -> Result<(), Box<dyn Error>> {
    let mut rx = new_watch(()).map_err(Box::new)?.1;
    rx.changed().await.map_err(Box::new)?;
    Ok(())
}

#[tokio::main]
async fn main() {
    eprintln!("has_changed: {:?}", has_changed().await);
    eprintln!(
        "drop_sender_then_has_changed: {:?}",
        drop_sender_then_has_changed().await,
    );
    eprintln!(
        "drop_sender_then_changed: {:?}",
        drop_sender_then_changed().await,
    );
}

drop_sender_then_has_changed returns an error. The specification of watch::Receiver::has_changed says,

Returns an error if the channel has been closed.

However, not all receivers have been dropped. It's likely that the fact that all senders have been dropped is responsible for the error because my has_changed does not return an error.

The specification of watch::Receiver::changed says,

This method returns an error if and only if the Sender is dropped.

All senders have been dropped, yet drop_sender_then_changed does not return an error.

Personally, I think that neither has_changed nor changed should return an error. If you allow for two Senders, why wouldn't you allow for zero Senders?

P.S. I suggest that you move the definition of “closed channel” to the top-level page instead of copying it to methods (such as watch::Sender::is_closed and watch::Sender::send).

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-tokioArea: The main tokio crateC-bugCategory: This is a bug.M-syncModule: tokio/sync

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions