rustasync-awaitsleeprust-tokiocrossterm

Sleep in Future::poll


I am trying to create a future polling for inputs from the crossterm crate, which does not provide an asynchronous API, as far as I know.

At first I tried to do something like the following :


use crossterm::event::poll as crossterm_poll;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;

use tokio::time::{sleep, timeout};

struct Polled {}

impl Polled {
    pub fn new() -> Polled {
        Polled {}
    }
}

impl Future for Polled {
    type Output = bool;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        // If there are events pending, it returns "Ok(true)", else it returns instantly
        let poll_status = crossterm_poll(Duration::from_secs(0));
        if poll_status.is_ok() && poll_status.unwrap() {
            return Poll::Ready(true);
        }
        Poll::Pending
    }
}

pub async fn poll(d: Duration) -> Result<bool, ()> {
    let polled = Polled::new();

    match timeout(d, polled).await {
        Ok(b) => Ok(b),
        Err(_) => Err(()),
    }
}

It technically works but obivously the program started using 100% CPU all the time since the executor always try to poll the future in case there's something new. Thus I wanted to add some asynchronous equivalent to sleep, that would delay the next time the executor tries to poll the Future, so I tried adding the following (right before returning Poll::Pending), which obviously did not work since sleep_future::poll() just returns Pending :

        let mut sleep_future = sleep(Duration::from_millis(50));
        tokio::pin!(sleep_future);
        sleep_future.poll(cx);
        cx.waker().wake_by_ref();

The fact that poll is not async forbids the use of async functions, and I'm starting to wonder if what I want to do is actually feasible, or if I'm not diving in my first problem the wrong way.

Is finding a way to do some async sleep the good way to go ? If not, what is it ? Am I missing something in the asynchronous paradigm ? Or is it just sometimes impossible to wrap some synchronous logic into a Future if the crate does not give you the necessary tools to do so ?

Thanks in advance anyway !

EDIT : I found a way to do what I want using an async block :


pub async fn poll(d: Duration) -> Result<bool, ()> {
    let mdr = async {
        loop {
            let a = crossterm_poll(Duration::from_secs(0));
            if a.is_ok() && a.unwrap() {
                break;
            }
            sleep(Duration::from_millis(50)).await;
        }
        true
    };

    match timeout(d, mdr).await {
        Ok(b) => Ok(b),
        Err(_) => Err(()),
    }
}

Is it the idiomatic way to do so ? Or did I miss something more elegant ?


Solution

  • Yes, using an async block is a good way to compose futures, like your custom poller and tokio's sleep.

    However, if you did want to write your own Future which also invokes tokio's sleep, here's what you would need to do differently:

    1. Don't call wake_by_ref() immediately — the sleep future will take care of that when its time comes, and that's how you avoid spinning (using 100% CPU).
    2. You must construct the sleep() future once when you intend to sleep (not every time you're polled), then store it in your future (this will require pin-projection) and poll the same future again the next time you're polled. That's how you ensure you wait the intended amount of time and not shorter.

    Async blocks are usually a much easier way to get the same result.