agnostic_lite/async_io/
timeout.rs

1use crate::time::{AsyncLocalTimeout, AsyncTimeout, Elapsed};
2use ::async_io::Timer;
3use core::{
4  future::Future,
5  pin::Pin,
6  task::{Context, Poll},
7  time::Duration,
8};
9use std::time::Instant;
10
11pin_project_lite::pin_project! {
12  /// The [`AsyncTimeout`] implementation for any runtime based on [`async-io`](async_io), e.g. `async-std` and `smol`.
13  pub struct AsyncIoTimeout<F> {
14    #[pin]
15    future: F,
16    #[pin]
17    delay: Timer,
18  }
19}
20
21impl<F: Future> Future for AsyncIoTimeout<F> {
22  type Output = Result<F::Output, Elapsed>;
23
24  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
25    let this = self.project();
26    match this.future.poll(cx) {
27      Poll::Pending => {}
28      other => return other.map(Ok),
29    }
30
31    if this.delay.poll(cx).is_ready() {
32      Poll::Ready(Err(Elapsed))
33    } else {
34      Poll::Pending
35    }
36  }
37}
38
39impl<F: Future + Send> AsyncTimeout<F> for AsyncIoTimeout<F> {
40  type Instant = Instant;
41
42  fn timeout(t: Duration, fut: F) -> Self
43  where
44    Self: Sized,
45  {
46    <Self as AsyncLocalTimeout<F>>::timeout_local(t, fut)
47  }
48
49  fn timeout_at(deadline: Instant, fut: F) -> Self
50  where
51    Self: Sized,
52  {
53    <Self as AsyncLocalTimeout<F>>::timeout_local_at(deadline, fut)
54  }
55}
56
57impl<F> AsyncLocalTimeout<F> for AsyncIoTimeout<F>
58where
59  F: Future,
60{
61  type Instant = Instant;
62
63  fn timeout_local(timeout: Duration, fut: F) -> Self
64  where
65    Self: Sized,
66  {
67    Self {
68      future: fut,
69      delay: Timer::after(timeout),
70    }
71  }
72
73  fn timeout_local_at(deadline: Instant, fut: F) -> Self
74  where
75    Self: Sized,
76  {
77    Self {
78      future: fut,
79      delay: Timer::at(deadline),
80    }
81  }
82}
83
84#[cfg(test)]
85mod tests {
86  use super::{AsyncIoTimeout, AsyncTimeout, Timer};
87  use std::time::{Duration, Instant};
88
89  const BAD: Duration = Duration::from_secs(1);
90  const GOOD: Duration = Duration::from_millis(10);
91  const TIMEOUT: Duration = Duration::from_millis(200);
92  const BOUND: Duration = Duration::from_secs(10);
93
94  #[test]
95  fn test_timeout() {
96    futures::executor::block_on(async {
97      let fut = async {
98        Timer::after(BAD).await;
99        1
100      };
101      let start = Instant::now();
102      let rst = AsyncIoTimeout::timeout(TIMEOUT, fut).await;
103      assert!(rst.is_err());
104      let elapsed = start.elapsed();
105      assert!(elapsed >= TIMEOUT && elapsed <= TIMEOUT + BOUND);
106
107      let fut = async {
108        Timer::after(GOOD).await;
109        1
110      };
111
112      let start = Instant::now();
113      let rst = AsyncIoTimeout::timeout(TIMEOUT, fut).await;
114      assert!(rst.is_ok());
115      let elapsed = start.elapsed();
116      assert!(elapsed >= GOOD && elapsed <= GOOD + BOUND);
117    });
118  }
119
120  #[test]
121  fn test_timeout_at() {
122    futures::executor::block_on(async {
123      let fut = async {
124        Timer::after(BAD).await;
125        1
126      };
127      let start = Instant::now();
128      let rst = AsyncIoTimeout::timeout_at(Instant::now() + TIMEOUT, fut).await;
129      assert!(rst.is_err());
130      let elapsed = start.elapsed();
131      assert!(elapsed >= TIMEOUT && elapsed <= TIMEOUT + BOUND);
132
133      let fut = async {
134        Timer::after(GOOD).await;
135        1
136      };
137
138      let start = Instant::now();
139      let rst = AsyncIoTimeout::timeout_at(Instant::now() + TIMEOUT, fut).await;
140      assert!(rst.is_ok());
141      let elapsed = start.elapsed();
142      assert!(elapsed >= GOOD && elapsed <= GOOD + BOUND);
143    });
144  }
145}