Skip to main content

tokio/time/
timeout.rs

1//! Allows a future to execute for a maximum amount of time.
2//!
3//! See [`Timeout`] documentation for more details.
4//!
5//! [`Timeout`]: struct@Timeout
6
7use crate::{
8    task::coop,
9    time::{error::Elapsed, Duration, Instant, Sleep},
10    util::trace,
11};
12
13use pin_project_lite::pin_project;
14use std::future::{Future, IntoFuture};
15use std::pin::Pin;
16use std::task::{self, Poll};
17
18/// Requires a `Future` to complete before the specified duration has elapsed.
19///
20/// If the future completes before the duration has elapsed, then the completed
21/// value is returned. Otherwise, an error is returned and the future is
22/// canceled.
23///
24/// Note that the future is polled before the timeout is checked, so if the future
25/// does not yield during execution then it is possible for the future to complete
26/// and exceed the timeout _without_ returning an error.
27///
28/// This function returns a future whose return type is [`Result`]`<T,`[`Elapsed`]`>`, where `T` is the
29/// return type of the provided future.
30///
31/// If the provided future completes immediately, then the future returned from
32/// this function is guaranteed to complete immediately with an [`Ok`] variant
33/// no matter the provided duration.
34///
35/// [`Ok`]: std::result::Result::Ok
36/// [`Result`]: std::result::Result
37/// [`Elapsed`]: crate::time::error::Elapsed
38///
39/// # Cancellation
40///
41/// Cancelling a timeout is done by dropping the future. No additional cleanup
42/// or other work is required.
43///
44/// The original future may be obtained by calling [`Timeout::into_inner`]. This
45/// consumes the `Timeout`.
46///
47/// # Examples
48///
49/// Create a new `Timeout` set to expire in 10 milliseconds.
50///
51/// ```rust
52/// use tokio::time::timeout;
53/// use tokio::sync::oneshot;
54///
55/// use std::time::Duration;
56///
57/// # async fn dox() {
58/// let (tx, rx) = oneshot::channel();
59/// # tx.send(()).unwrap();
60///
61/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
62/// if let Err(_) = timeout(Duration::from_millis(10), rx).await {
63///     println!("did not receive value within 10 ms");
64/// }
65/// # }
66/// ```
67///
68/// # Panics
69///
70/// This function panics if there is no current timer set.
71///
72/// It can be triggered when [`Builder::enable_time`] or
73/// [`Builder::enable_all`] are not included in the builder.
74///
75/// It can also panic whenever a timer is created outside of a
76/// Tokio runtime. That is why `rt.block_on(sleep(...))` will panic,
77/// since the function is executed outside of the runtime.
78/// Whereas `rt.block_on(async {sleep(...).await})` doesn't panic.
79/// And this is because wrapping the function on an async makes it lazy,
80/// and so gets executed inside the runtime successfully without
81/// panicking.
82///
83/// [`Builder::enable_time`]: crate::runtime::Builder::enable_time
84/// [`Builder::enable_all`]: crate::runtime::Builder::enable_all
85#[track_caller]
86pub fn timeout<F>(duration: Duration, future: F) -> Timeout<F::IntoFuture>
87where
88    F: IntoFuture,
89{
90    // Closures don't preserve `#[track_caller]`.
91    #[allow(clippy::manual_map)]
92    Timeout {
93        value: future.into_future(),
94        delay: match Instant::now().checked_add(duration) {
95            Some(deadline) => Some(Sleep::new_timeout(deadline, trace::caller_location())),
96            None => None,
97        },
98    }
99}
100
101/// Requires a `Future` to complete before the specified instant in time.
102///
103/// If the future completes before the instant is reached, then the completed
104/// value is returned. Otherwise, an error is returned.
105///
106/// This function returns a future whose return type is [`Result`]`<T,`[`Elapsed`]`>`, where `T` is the
107/// return type of the provided future.
108///
109/// If the provided future completes immediately, then the future returned from
110/// this function is guaranteed to complete immediately with an [`Ok`] variant
111/// no matter the provided deadline.
112///
113/// [`Ok`]: std::result::Result::Ok
114/// [`Result`]: std::result::Result
115/// [`Elapsed`]: crate::time::error::Elapsed
116///
117/// # Cancellation
118///
119/// Cancelling a timeout is done by dropping the future. No additional cleanup
120/// or other work is required.
121///
122/// The original future may be obtained by calling [`Timeout::into_inner`]. This
123/// consumes the `Timeout`.
124///
125/// # Examples
126///
127/// Create a new `Timeout` set to expire in 10 milliseconds.
128///
129/// ```rust
130/// use tokio::time::{Instant, timeout_at};
131/// use tokio::sync::oneshot;
132///
133/// use std::time::Duration;
134///
135/// # async fn dox() {
136/// let (tx, rx) = oneshot::channel();
137/// # tx.send(()).unwrap();
138///
139/// // Wrap the future with a `Timeout` set to expire 10 milliseconds into the
140/// // future.
141/// if let Err(_) = timeout_at(Instant::now() + Duration::from_millis(10), rx).await {
142///     println!("did not receive value within 10 ms");
143/// }
144/// # }
145/// ```
146///
147/// # Panics
148///
149/// This function panics if there is no current timer set.
150///
151/// It can be triggered when [`Builder::enable_time`] or
152/// [`Builder::enable_all`] are not included in the builder.
153///
154/// It can also panic whenever a timer is created outside of a
155/// Tokio runtime. That is why `rt.block_on(sleep(...))` will panic,
156/// since the function is executed outside of the runtime.
157/// Whereas `rt.block_on(async {sleep(...).await})` doesn't panic.
158/// And this is because wrapping the function on an async makes it lazy,
159/// and so gets executed inside the runtime successfully without
160/// panicking.
161///
162/// [`Builder::enable_time`]: crate::runtime::Builder::enable_time
163/// [`Builder::enable_all`]: crate::runtime::Builder::enable_all
164#[track_caller]
165pub fn timeout_at<F>(deadline: Instant, future: F) -> Timeout<F::IntoFuture>
166where
167    F: IntoFuture,
168{
169    Timeout {
170        value: future.into_future(),
171        delay: Some(Sleep::new_timeout(deadline, trace::caller_location())),
172    }
173}
174
175pin_project! {
176    /// Future returned by [`timeout`](timeout) and [`timeout_at`](timeout_at).
177    #[must_use = "futures do nothing unless you `.await` or poll them"]
178    #[derive(Debug)]
179    pub struct Timeout<T> {
180        #[pin]
181        value: T,
182        #[pin]
183        delay: Option<Sleep>,
184    }
185}
186
187impl<T> Timeout<T> {
188    /// Gets a reference to the underlying value in this timeout.
189    pub fn get_ref(&self) -> &T {
190        &self.value
191    }
192
193    /// Gets a mutable reference to the underlying value in this timeout.
194    pub fn get_mut(&mut self) -> &mut T {
195        &mut self.value
196    }
197
198    /// Consumes this timeout, returning the underlying value.
199    pub fn into_inner(self) -> T {
200        self.value
201    }
202}
203
204impl<T> Future for Timeout<T>
205where
206    T: Future,
207{
208    type Output = Result<T::Output, Elapsed>;
209
210    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
211        let me = self.project();
212
213        let had_budget_before = coop::has_budget_remaining();
214
215        // First, try polling the future
216        if let Poll::Ready(v) = me.value.poll(cx) {
217            return Poll::Ready(Ok(v));
218        }
219
220        match me.delay.as_pin_mut() {
221            Some(delay) => poll_delay(had_budget_before, delay, cx).map(Err),
222            None => Poll::Pending,
223        }
224    }
225}
226
227// The T-invariant portion of Timeout::<T>::poll. Pulling this out reduces the
228// amount of code that gets duplicated during monomorphization.
229fn poll_delay(
230    had_budget_before: bool,
231    delay: Pin<&mut Sleep>,
232    cx: &mut task::Context<'_>,
233) -> Poll<Elapsed> {
234    let delay_poll = || match delay.poll(cx) {
235        Poll::Ready(()) => Poll::Ready(Elapsed::new()),
236        Poll::Pending => Poll::Pending,
237    };
238
239    let has_budget_now = coop::has_budget_remaining();
240
241    if let (true, false) = (had_budget_before, has_budget_now) {
242        // if it is the underlying future that exhausted the budget, we poll
243        // the `delay` with an unconstrained one. This prevents pathological
244        // cases where the underlying future always exhausts the budget and
245        // we never get a chance to evaluate whether the timeout was hit or
246        // not.
247        coop::with_unconstrained(delay_poll)
248    } else {
249        delay_poll()
250    }
251}