Skip to main content

moirai_async/timer/
timeout.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4use std::time::Duration;
5
6use crate::timer::delay::Delay;
7
8/// Timeout wrapper for futures with comprehensive cancellation
9pub struct Timeout<F> {
10    future: F,
11    delay: Delay,
12}
13
14impl<F> Timeout<F>
15where
16    F: Future,
17{
18    pub(super) fn new(future: F, duration: Duration) -> Self {
19        Self {
20            future,
21            delay: Delay::new(duration),
22        }
23    }
24}
25
26impl<F> Future for Timeout<F>
27where
28    F: Future,
29{
30    type Output = Result<F::Output, TimeoutError>;
31
32    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
33        // Safety: once `Timeout<F>` is pinned, its fields are not moved in
34        // `poll`. Projecting the generic future in place preserves support for
35        // `!Unpin` futures without allocating a `Pin<Box<F>>`.
36        let this = unsafe { self.get_unchecked_mut() };
37
38        // First check if the future is ready
39        let future = unsafe { Pin::new_unchecked(&mut this.future) };
40        if let Poll::Ready(output) = future.poll(cx) {
41            return Poll::Ready(Ok(output));
42        }
43
44        // Then check if the timeout has elapsed
45        if let Poll::Ready(()) = Pin::new(&mut this.delay).poll(cx) {
46            return Poll::Ready(Err(TimeoutError));
47        }
48
49        Poll::Pending
50    }
51}
52
53/// Error returned when a timeout elapses
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct TimeoutError;
56
57impl std::fmt::Display for TimeoutError {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        f.write_str("operation timed out")
60    }
61}
62
63impl std::error::Error for TimeoutError {}