Search code examples
rustasync-awaitrust-tokio

Why doesn't a nested await point yield up to tokio::select


I think I'm missing something very basic. My expectation is that when tick.tick() completes and starts the sleep loop, tokio::time::sleep(...).await should yield back to the select statement, allowing longer_tick a chance to complete. But once the shorter tick completes, this gets stuck in the sleep loop, never yielding back to select.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ddb47ab0cd5669def5ea0596803381a8

use std::time::Duration;
use tokio::time::interval;

#[tokio::main]
async fn main() {
    let mut tick = interval(Duration::from_millis(500));
    let mut longer_tick = interval(Duration::from_millis(1000));

    loop {
        tokio::select! {
            _ = longer_tick.tick() => {
                println!("longer tick");
            },
            _ = tick.tick() => {
                println!("sleeping");
                sleep().await;
            },
        }
    }
}

async fn sleep() {
    let mut idx = 0;
    loop {
        let time = idx.min(5);
        println!("Sleeping for {} s", time);
        tokio::time::sleep(Duration::from_secs(time)).await;
        idx += 1;
    }
}

Solution

  • Tokio's select only waits for the futures in the branch section, not the handler. So the sleep future is not considered when using select!.

    Also, per the documentation

    Waits on multiple concurrent branches, returning when the first branch completes, cancelling the remaining branches.

    When one of the branch completes (the interval's in this case), the other branches are cancelled. Therefore when you are using select, it will only ever execute one of the branch handlers.