Skip to content

Commit 929ac6b

Browse files
authored
Merge pull request #167 from yoshuawuyts/futures-time
Add `{Future,Stream}::wait_until`
2 parents 28d7098 + 0b0eb02 commit 929ac6b

File tree

7 files changed

+210
-1
lines changed

7 files changed

+210
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ slab = { version = "0.4.8", optional = true }
3939
smallvec = { version = "1.11.0", optional = true }
4040

4141
[dev-dependencies]
42+
async-io = "2.3.2"
4243
async-std = { version = "1.12.0", features = ["attributes"] }
4344
criterion = { version = "0.3", features = [
4445
"async",

src/future/futures_ext.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use futures_core::Future;
55

66
use super::join::tuple::Join2;
77
use super::race::tuple::Race2;
8+
use super::WaitUntil;
89

910
/// An extension trait for the `Future` trait.
1011
pub trait FutureExt: Future {
@@ -19,6 +20,44 @@ pub trait FutureExt: Future {
1920
where
2021
Self: Future<Output = T> + Sized,
2122
S2: IntoFuture<Output = T>;
23+
24+
/// Delay resolving the future until the given deadline.
25+
///
26+
/// The underlying future will not be polled until the deadline has expired. In addition
27+
/// to using a time source as a deadline, any future can be used as a
28+
/// deadline too. When used in combination with a multi-consumer channel,
29+
/// this method can be used to synchronize the start of multiple futures and streams.
30+
///
31+
/// # Example
32+
///
33+
/// ```
34+
/// # #[cfg(miri)]fn main() {}
35+
/// # #[cfg(not(miri))]
36+
/// # fn main() {
37+
/// use async_io::Timer;
38+
/// use futures_concurrency::prelude::*;
39+
/// use futures_lite::future::block_on;
40+
/// use std::time::{Duration, Instant};
41+
///
42+
/// block_on(async {
43+
/// let now = Instant::now();
44+
/// let duration = Duration::from_millis(100);
45+
///
46+
/// async { "meow" }
47+
/// .wait_until(Timer::after(duration))
48+
/// .await;
49+
///
50+
/// assert!(now.elapsed() >= duration);
51+
/// });
52+
/// # }
53+
/// ```
54+
fn wait_until<D>(self, deadline: D) -> WaitUntil<Self, D::IntoFuture>
55+
where
56+
Self: Sized,
57+
D: IntoFuture,
58+
{
59+
WaitUntil::new(self, deadline.into_future())
60+
}
2261
}
2362

2463
impl<F1> FutureExt for F1

src/future/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ pub use join::Join;
7676
pub use race::Race;
7777
pub use race_ok::RaceOk;
7878
pub use try_join::TryJoin;
79+
pub use wait_until::WaitUntil;
7980

8081
/// A growable group of futures which act as a single unit.
8182
#[cfg(feature = "alloc")]
@@ -86,3 +87,4 @@ pub(crate) mod join;
8687
pub(crate) mod race;
8788
pub(crate) mod race_ok;
8889
pub(crate) mod try_join;
90+
pub(crate) mod wait_until;

src/future/wait_until.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use core::future::Future;
2+
use core::pin::Pin;
3+
use core::task::{ready, Context, Poll};
4+
5+
/// Suspends a future until the specified deadline.
6+
///
7+
/// This `struct` is created by the [`wait_until`] method on [`FutureExt`]. See its
8+
/// documentation for more.
9+
///
10+
/// [`wait_until`]: crate::future::FutureExt::wait_until
11+
/// [`FutureExt`]: crate::future::FutureExt
12+
#[derive(Debug)]
13+
#[pin_project::pin_project]
14+
#[must_use = "futures do nothing unless polled or .awaited"]
15+
pub struct WaitUntil<F, D> {
16+
#[pin]
17+
future: F,
18+
#[pin]
19+
deadline: D,
20+
state: State,
21+
}
22+
23+
/// The internal state
24+
#[derive(Debug)]
25+
enum State {
26+
Started,
27+
PollFuture,
28+
Completed,
29+
}
30+
31+
impl<F, D> WaitUntil<F, D> {
32+
pub(super) fn new(future: F, deadline: D) -> Self {
33+
Self {
34+
future,
35+
deadline,
36+
state: State::Started,
37+
}
38+
}
39+
}
40+
41+
impl<F: Future, D: Future> Future for WaitUntil<F, D> {
42+
type Output = F::Output;
43+
44+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
45+
let mut this = self.project();
46+
loop {
47+
match this.state {
48+
State::Started => {
49+
ready!(this.deadline.as_mut().poll(cx));
50+
*this.state = State::PollFuture;
51+
}
52+
State::PollFuture => {
53+
let value = ready!(this.future.as_mut().poll(cx));
54+
*this.state = State::Completed;
55+
return Poll::Ready(value);
56+
}
57+
State::Completed => panic!("future polled after completing"),
58+
}
59+
}
60+
}
61+
}

src/stream/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub use stream_ext::StreamExt;
5454
#[doc(inline)]
5555
#[cfg(feature = "alloc")]
5656
pub use stream_group::StreamGroup;
57+
pub use wait_until::WaitUntil;
5758
pub use zip::Zip;
5859

5960
/// A growable group of streams which act as a single unit.
@@ -64,4 +65,5 @@ pub(crate) mod chain;
6465
mod into_stream;
6566
pub(crate) mod merge;
6667
mod stream_ext;
68+
pub(crate) mod wait_until;
6769
pub(crate) mod zip;

src/stream/stream_ext.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
use core::future::IntoFuture;
2+
13
use crate::stream::{IntoStream, Merge};
24
use futures_core::Stream;
35

46
#[cfg(feature = "alloc")]
57
use crate::concurrent_stream::FromStream;
68

7-
use super::{chain::tuple::Chain2, merge::tuple::Merge2, zip::tuple::Zip2, Chain, Zip};
9+
use super::{chain::tuple::Chain2, merge::tuple::Merge2, zip::tuple::Zip2, Chain, WaitUntil, Zip};
810

911
/// An extension trait for the `Stream` trait.
1012
pub trait StreamExt: Stream {
@@ -34,6 +36,45 @@ pub trait StreamExt: Stream {
3436
{
3537
FromStream::new(self)
3638
}
39+
40+
/// Delay the yielding of items from the stream until the given deadline.
41+
///
42+
/// The underlying stream will not be polled until the deadline has expired. In addition
43+
/// to using a time source as a deadline, any future can be used as a
44+
/// deadline too. When used in combination with a multi-consumer channel,
45+
/// this method can be used to synchronize the start of multiple streams and futures.
46+
///
47+
/// # Example
48+
/// ```
49+
/// # #[cfg(miri)] fn main() {}
50+
/// # #[cfg(not(miri))]
51+
/// # fn main() {
52+
/// use async_io::Timer;
53+
/// use futures_concurrency::prelude::*;
54+
/// use futures_lite::{future::block_on, stream};
55+
/// use futures_lite::prelude::*;
56+
/// use std::time::{Duration, Instant};
57+
///
58+
/// block_on(async {
59+
/// let now = Instant::now();
60+
/// let duration = Duration::from_millis(100);
61+
///
62+
/// stream::once("meow")
63+
/// .wait_until(Timer::after(duration))
64+
/// .next()
65+
/// .await;
66+
///
67+
/// assert!(now.elapsed() >= duration);
68+
/// });
69+
/// # }
70+
/// ```
71+
fn wait_until<D>(self, deadline: D) -> WaitUntil<Self, D::IntoFuture>
72+
where
73+
Self: Sized,
74+
D: IntoFuture,
75+
{
76+
WaitUntil::new(self, deadline.into_future())
77+
}
3778
}
3879

3980
impl<S1> StreamExt for S1

src/stream/wait_until.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use core::future::Future;
2+
use core::pin::Pin;
3+
use core::task::{Context, Poll};
4+
5+
use futures_core::stream::Stream;
6+
use pin_project::pin_project;
7+
8+
/// Delay execution of a stream once for the specified duration.
9+
///
10+
/// This `struct` is created by the [`wait_until`] method on [`StreamExt`]. See its
11+
/// documentation for more.
12+
///
13+
/// [`wait_until`]: crate::stream::StreamExt::wait_until
14+
/// [`StreamExt`]: crate::stream::StreamExt
15+
#[derive(Debug)]
16+
#[must_use = "streams do nothing unless polled or .awaited"]
17+
#[pin_project]
18+
pub struct WaitUntil<S, D> {
19+
#[pin]
20+
stream: S,
21+
#[pin]
22+
deadline: D,
23+
state: State,
24+
}
25+
26+
#[derive(Debug)]
27+
enum State {
28+
Timer,
29+
Streaming,
30+
}
31+
32+
impl<S, D> WaitUntil<S, D> {
33+
pub(crate) fn new(stream: S, deadline: D) -> Self {
34+
WaitUntil {
35+
stream,
36+
deadline,
37+
state: State::Timer,
38+
}
39+
}
40+
}
41+
42+
impl<S, D> Stream for WaitUntil<S, D>
43+
where
44+
S: Stream,
45+
D: Future,
46+
{
47+
type Item = S::Item;
48+
49+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
50+
let this = self.project();
51+
52+
match this.state {
53+
State::Timer => match this.deadline.poll(cx) {
54+
Poll::Pending => Poll::Pending,
55+
Poll::Ready(_) => {
56+
*this.state = State::Streaming;
57+
this.stream.poll_next(cx)
58+
}
59+
},
60+
State::Streaming => this.stream.poll_next(cx),
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)