tokio: Budgeting with a conditional check against an always ready stream blocks the condition

Version

0.2.20

Platform

playground

Description

Taking the first correct example from the tokio::select! docs and running it in the playground results in an infinite hang (playground with println commented out).

The tested code includes an additional time::delay_for(Duration::from_millis(10)).await; bit of work in the some_async_work which avoids this issue.

This has the same underlying problem as https://github.com/rust-lang/futures-rs/issues/2157, the budgeting is only applied to the conditional and blocks it from ever becoming true, while the actual work continues running because it is not subject to the budgeting.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Comments: 23 (23 by maintainers)

Most upvoted comments

Well, if you want a more realistic case where this would happen, consider this:

use tokio::time::{self, Duration};
use futures::channel::mpsc::channel;
use futures::stream::StreamExt;

#[tokio::main]
async fn main() {
    let mut delay = time::delay_for(Duration::from_millis(50));

    let (send, recv) = channel::<()>(10);

    drop(send);
    let mut recv = recv.fuse();

    loop {
        tokio::select! {
            _ = &mut delay => {
                println!("operation timed out");
                break;
            }
            msg = recv.next() => {
                if let Some(()) = msg {
                    println!("operation completed");
                }
            }
        }
    }
}

playground

I just had someone make a similar mistake over on Discord, albeit that was with futures’ select! macro, and the next() was on an empty FuturesUnordered.

For reference, the example is:

use tokio::time::{self, Duration};

async fn some_async_work() {
    // do work
}

#[tokio::main]
async fn main() {
    let mut delay = time::delay_for(Duration::from_millis(50));

    while !delay.is_elapsed() {
        tokio::select! {
            _ = &mut delay, if !delay.is_elapsed() => {
                println!("operation timed out");
            }
            _ = some_async_work() => {
                println!("operation completed");
            }
        }
    }
}