How to deal with spurious wakeup in tokio? (induced by inquire's prompt)

I have two threads:

  • controller thread (process sink msg): send bool to tick thread using channel.
  • tick thread: get message from channel and once the sent msg is true, start to wakeup every n seconds.
    2023-07-31 at 12.30 AM

The duration is let mut interval = tokio::time::interval(Duration::from_secs(5)); => 5 seconds

The following is my tick thread:

pub async fn tick(
    p: ProgressBar,
    mut tick_receiver: Receiver<bool>,
    sink_sender: Sender<SinkMessage>,
) {
    let mut is_playing = false;
    let mut interval = tokio::time::interval(Duration::from_secs(5));

    // let sleep = time::sleep(Duration::from_secs(1));
    let tick_channel_future = async { tick_receiver.recv().await.unwrap() };
    // tokio::pin!(sleep);
    tokio::pin!(tick_channel_future);

    loop {
        tokio::select! {
            true = &mut tick_channel_future, if !is_playing => {
                // println!("{:?}", "----------> getting tick message true");
                is_playing = true;
            },
            _ = interval.tick(), if is_playing => {
                println!("in tick {:?} {:?}", p.position(), chrono::Utc::now().to_rfc2822());
                // p.inc(1);
            },
            else => {
                is_playing = false;
            }
        }
    }
}

During the startup, there are so many spurious wakeup from the interval tick as shown in the log:

2023-07-31 at 12.34 AM

What's the root cause and how to deal with this issue? Thanks in advance!

1 Like

Things seem to work as expected with this code.

use std::time::Duration;

#[tokio::main]
async fn main() {
    let mut interval = tokio::time::interval(Duration::from_secs(5));
    let (tx, mut rx) = tokio::sync::mpsc::channel(1);
    let fut = async { rx.recv().await.unwrap() };
    tokio::pin!(fut);
    tx.send(true).await.unwrap();
    let mut done = false;
    loop {
        tokio::select! {
            true = &mut fut, if !done => {
                println!("go");
                done = true;
            }
            _ = interval.tick(), if done => {
                println!("{}", chrono::Utc::now().to_rfc2822())
            }
        }
    }
}

are you sure you aren't calling tick multiple times?

Thanks for the reply. I'm able to reproduce the issue with the following snippet:
The key is to add the Select::new() from inquire.

use inquire::Select;
use std::thread;
use std::time::Duration;
use tokio::sync::mpsc::Receiver;

#[tokio::main]
async fn main() {
    let (tx, rx) = tokio::sync::mpsc::channel(100);
    let _t = tokio::spawn(async move {
        tick(rx).await;
    });

    let b = thread::spawn(move || {
        let options = vec![
            "Banana",
            "Apple",
            "Strawberry",
            "Grapes",
            "Lemon",
            "Tangerine",
            "Watermelon",
            "Orange",
            "Pear",
            "Avocado",
            "Pineapple",
            "Banana",
            "Apple",
            "Strawberry",
            "Grapes",
            "Lemon",
            "Tangerine",
            "Watermelon",
            "Orange",
            "Banana",
            "Apple",
            "Strawberry",
            "Grapes",
            "Lemon",
            "Tangerine",
            "Watermelon",
            "Orange",
            "Banana",
            "Apple",
            "Strawberry",
            "Grapes",
            "Lemon",
            "Tangerine",
            "Watermelon",
            "Orange",
        ];

        let _ans = Select::new("choose a song: ", options).prompt();

        tx.blocking_send(true).unwrap();
        loop {
            thread::sleep(Duration::from_secs(4));
        }
    });

    b.join().unwrap();
}

async fn tick(mut rx: Receiver<bool>) {
    let mut interval = tokio::time::interval(Duration::from_secs(5));
    let fut = async { rx.recv().await.unwrap() };
    tokio::pin!(fut);
    let mut done = false;
    loop {
        tokio::select! {
            true = &mut fut, if !done => {
                println!("go");
                done = true;
            }
            _ = interval.tick(), if done => {
                println!("{}", chrono::Utc::now().to_rfc2822())
            }
        }
    }
}

2023-07-31 at 1.18 PM
Try to spend a few more seconds while you are on this interface:

2023-07-31 at 1.19 PM

Ahh okay. I'm seeing one "spurious" tick per 5 seconds I wait on the menu. The interval is "catching up" on missed ticks because the default missed_tick_behavior is MissedTickBehavior::Burst

Adding

interval.set_missed_tick_behavior(MissedTickBehavior::Skip);

gets the behavior you want, at least in this part of the program.

1 Like

WOW, amazing! Thanks a lot.