[async][futures] Is there a better way to implement this future?

I want a simple Timeout future for testing and other capabilities. The following will work but it feels a little brute force to me and I was wondering if there was a better way to implement this that might take advantage of the OS or something. A caveat is that it must use only core Rust. I cannot ingest the Tokio ecosystem nor do I want to import a crate of hundreds of things for just this.

    struct TimeoutFuture {
        /// State contains a bool indicating if the future completed and the waker it is tracking.
        state: Arc<Mutex<(bool, Option<Waker>)>>,
    }

    impl TimeoutFuture {
        fn new(duration: Duration) -> Self {
            let result = TimeoutFuture {
                state: Arc::new(Mutex::new((false, None))),
            };

            let state_clone = result.state.clone();
            thread::spawn(move || {
                thread::sleep(duration);
                let mut state = state_clone.lock().unwrap();
                state.0 = true;
                if let Some(waker) = state.1.take() {
                    waker.wake();
                }
            });

            result
        }
    }

    impl Future for TimeoutFuture {
        type Output = ();

        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            let mut state = self.state.lock().unwrap();
            if state.0 {
                Poll::Ready(())
            } else {
                state.1 = Some(cx.waker().clone());
                Poll::Pending
            }
        }
    }

The future works but it just feels wrong.

Can you expand on your meaning of "core Rust"? Does that mean that acceptable answers cannot use the futures(-preview) crate? There's a library shipped with the compiler called libcore and one called libstd; are answers allowed to use libstd? Are they allowed to use libcore? Perhaps "core Rust" only means the language itself?

A quick look seems to indicate that your boolean is redundant due to the Option. Could it just be .is_none()? Ah, perhaps not because it starts as None... If that's the case, I might switch to an enum with more verbose states.

futures-preview is of curse good or I couldnt be doing futures at all. I should have mentioned that. I just dont want to bring in Tokio ecosystem

Could you draw some lines around the "Tokio ecosystem"? I wouldn't want to provide an answer that you cannot / don't want to use. Tokio uses mio and the bytes crate; are those acceptable? Is it just any crate that matches the regex /tokio/?

One possibility is to use a channel:

fn timeout(duration: Duration) -> impl Future<Output = ()> {
    let (tx, rx) = oneshot::channel();

    thread::spawn(move || {
        thread::sleep(duration);
        tx.send(()).expect("Unable to send timeout signal");
    });

    rx.then(|_| async {})
}

Using the limited set of tools you are allowed, I don't know much better way than threads.

If you are interested, you could read this paper by Varghese and Lauck. It describes "a hashed timing wheel". You could implement such a technique and publish it to crates.io to allow other people to make use of the work that you've done!

2 Likes

If crates without too many dependencies are ok, there are