Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 665
uben Avatar asked Sep 18 '20 17:09

uben


1 Answers

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();
            }
        }
    }
}
like image 62
user1937198 Avatar answered Nov 19 '22 05:11

user1937198