Skip to content

Commit 71cc4cf

Browse files
author
Joel Scarfone
committed
introduce Timed to track how long a future is alive for
1 parent d41d49d commit 71cc4cf

File tree

4 files changed

+134
-1
lines changed

4 files changed

+134
-1
lines changed

tokio-util/src/time/mod.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
use std::future::Future;
1212
use std::time::Duration;
13-
use tokio::time::Timeout;
13+
use tokio::time::{Timed, Timeout};
1414

1515
mod wheel;
1616

@@ -43,6 +43,36 @@ pub trait FutureExt: Future {
4343
{
4444
tokio::time::timeout(timeout, self)
4545
}
46+
47+
/// A wrapper around [`tokio::time::Timed`], with the advantage that it is easier to write
48+
/// fluent call chains.
49+
///
50+
/// # Examples
51+
///
52+
/// ```rust
53+
/// /// use tokio::{sync::oneshot, time::Duration};
54+
/// use tokio_util::time::FutureExt;
55+
/// use tokio::sync::oneshot;
56+
/// use tokio::time::Duration;
57+
///
58+
/// # async fn dox() {
59+
/// let (tx, rx) = oneshot::channel::<()>();
60+
///
61+
/// tokio::task::spawn(async move {
62+
/// tokio::time::sleep(Duration::from_millis(5)).await;
63+
/// tx.send(()).unwrap();
64+
/// });
65+
///
66+
/// let (res, duration) = rx.timed().await;
67+
/// println!("Received {res:?} after {duration:?}");
68+
/// # }
69+
/// ```
70+
fn timed(self) -> Timed<Self>
71+
where
72+
Self: Sized,
73+
{
74+
tokio::time::timed(self)
75+
}
4676
}
4777

4878
impl<T: Future + ?Sized> FutureExt for T {}

tokio/src/time/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ pub use interval::{interval, interval_at, Interval, MissedTickBehavior};
101101
mod sleep;
102102
pub use sleep::{sleep, sleep_until, Sleep};
103103

104+
mod timed;
105+
pub use timed::{timed, Timed};
106+
104107
mod timeout;
105108
#[doc(inline)]
106109
pub use timeout::{timeout, timeout_at, Timeout};

tokio/src/time/timed.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use std::{
2+
future::{Future, IntoFuture},
3+
pin::Pin,
4+
task::Poll,
5+
};
6+
7+
use pin_project_lite::pin_project;
8+
9+
use crate::time::Instant;
10+
11+
/// Constructs a `Timed` future that wraps an underlying `Future`. The `Timed` future will return
12+
/// the result of the underlying future as well as the duration the `Timed` was constructed for.
13+
///
14+
/// /// # Examples
15+
///
16+
/// Track how long we waited on a oneshot rx.
17+
///
18+
/// ```rust
19+
/// use tokio::time::timed;
20+
/// use tokio::sync::oneshot;
21+
///
22+
/// # async fn dox() {
23+
/// let (tx, rx) = oneshot::channel();
24+
/// # tx.send(()).unwrap();
25+
///
26+
/// // Wrap the future with a `Timed` to see how long we were at this step.
27+
/// let (_received, duration) = timed(rx).await;
28+
/// println!("Received a value after waiting {duration:?}");
29+
/// # }
30+
/// ```
31+
pub fn timed<F>(future: F) -> Timed<F::IntoFuture>
32+
where
33+
F: IntoFuture,
34+
{
35+
Timed {
36+
inner: future.into_future(),
37+
start: Instant::now(),
38+
}
39+
}
40+
41+
pin_project! {
42+
/// A helper future that wraps an inner future to also return how long the `Timed` was constructed.
43+
/// If constructed right before an await, it will give accurate timing information on the
44+
/// underlying future.
45+
///
46+
/// # Examples
47+
///
48+
/// Time something in a generic async workflow.
49+
///
50+
/// ```
51+
/// use tokio::time::timed;
52+
///
53+
/// async fn i_wish_i_knew_how_long_something_took_in_here() {
54+
/// let (_, elapsed) = timed(async {
55+
/// // do async work
56+
/// }).await;
57+
/// }
58+
/// ```
59+
pub struct Timed<T> {
60+
#[pin]
61+
inner: T,
62+
start: Instant,
63+
}
64+
}
65+
66+
impl<T> Future for Timed<T>
67+
where
68+
T: Future,
69+
{
70+
type Output = (T::Output, std::time::Duration);
71+
72+
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
73+
let this = self.project();
74+
if let Poll::Ready(inner) = this.inner.poll(cx) {
75+
Poll::Ready((inner, this.start.elapsed()))
76+
} else {
77+
Poll::Pending
78+
}
79+
}
80+
}

tokio/tests/time_timed.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use tokio::time::{self, timed, Duration};
2+
3+
#[tokio::test(start_paused = true)]
4+
async fn accuracy() {
5+
let durations = vec![
6+
Duration::from_millis(50),
7+
Duration::from_millis(100),
8+
Duration::from_millis(200),
9+
];
10+
for expected in durations {
11+
let ((), elapsed) = timed(time::sleep(expected)).await;
12+
assert_eq!(elapsed, expected);
13+
}
14+
}
15+
16+
#[tokio::test(start_paused = true)]
17+
async fn immediate_future() {
18+
let ((), elapsed) = timed(std::future::ready(())).await;
19+
assert_eq!(elapsed, Duration::from_millis(0));
20+
}

0 commit comments

Comments
 (0)