uben
uben

Reputation: 1491

Can I miss a value by calling select on two async receivers?

Is it possible, if a task sends to a and an other (at the same time) sends to b, that tokio::select! on a and b drops one of the value by cancelling the remaining future? Or is it guaranteed to be received at the next loop iteration?

use tokio::sync::mpsc::Receiver;

async fn foo(mut a: Receiver<()>, mut b: Receiver<()>) {
    loop {
        tokio::select!{
            _ = a.recv() => {
                println!("A!");
            }
            _ = b.recv() => {
                println!("B!");
            }
        }
    }
}

My mind can't get around what is really happening behind the async magic in that case.

Upvotes: 9

Views: 1204

Answers (1)

user1937198
user1937198

Reputation: 5358

It doesn't appear to be guaranteed in the documentation anywhere, but is likely to work for reading directly from a channel because of the way rusts poll based architecture works. A select is equivalent to polling each of futures in a random order, until one of them is ready, or if none are, then waiting until a waker is signaled and then repeating the process. A message is only removed from the channel when returned by a successful poll. A successful poll stops the select, so the rest of the channels will not be touched. Thus they will be polled the next time the loop occurs and then return the message.

However, this is a dangerous approach, because if the receiver is replaced with something that returns a future that does anything more complex than a direct read, where it could potentially suspend after the read, then you could lose messages when that happens. As such it should probably be treated as if it wouldn't work. A safer approach would be to store the futures in mutable variables that you update when they fire:

use tokio::sync::mpsc::Receiver;

async fn foo(mut a: Receiver<()>, mut b: Receiver<()>) {
    let mut a_fut = a.recv();
    let mut b_fut = b.recv();
    loop {
        tokio::select!{
            _ = a_fut => {
                println!("A!");
                a_fut = a.recv();
            }
            _ = b_fut => {
                println!("B!");
                b_fut = b.recv();
            }
        }
    }
}

Upvotes: 4

Related Questions