Add interval api. (#41)

* Add interval api.

* Fix workflows.

* Address review comments.

* Don't tick immediately.

* Increase allowed jitter for ci.
This commit is contained in:
David Craven 2020-11-28 20:54:01 +01:00 committed by GitHub
parent 1f860a9525
commit 75316e2982
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 18 deletions

View File

@ -16,14 +16,14 @@ jobs:
rust: [nightly, beta, stable, 1.39.0] rust: [nightly, beta, stable, 1.39.0]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set current week of the year in environnement - name: Set current week of the year in environnement
if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macOS') if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macOS')
run: echo "::set-env name=CURRENT_WEEK::$(date +%V)" run: echo "CURRENT_WEEK=$(date +%V)" >> $GITHUB_ENV
- name: Set current week of the year in environnement - name: Set current week of the year in environnement
if: startsWith(matrix.os, 'windows') if: startsWith(matrix.os, 'windows')
run: echo "::set-env name=CURRENT_WEEK::$(Get-Date -UFormat %V)" run: echo "CURRENT_WEEK=$(Get-Date -UFormat %V)" >> $GITHUB_ENV
- name: Install latest ${{ matrix.rust }} - name: Install latest ${{ matrix.rust }}
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1

View File

@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set current week of the year in environnement - name: Set current week of the year in environnement
run: echo "::set-env name=CURRENT_WEEK::$(date +%V)" run: echo "CURRENT_WEEK=$(date +%V)" >> $GITHUB_ENV
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable

View File

@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set current week of the year in environnement - name: Set current week of the year in environnement
run: echo "::set-env name=CURRENT_WEEK::$(date +%V)" run: echo "CURRENT_WEEK=$(date +%V)" >> $GITHUB_ENV
- uses: actions-rs/audit-check@v1 - uses: actions-rs/audit-check@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -85,6 +85,11 @@ mod reactor;
pub use driver::block_on; pub use driver::block_on;
/// Use Duration::MAX once duration_constants are stabilized.
fn duration_max() -> Duration {
Duration::new(u64::MAX, 1_000_000_000 - 1)
}
/// A future that expires at a point in time. /// A future that expires at a point in time.
/// ///
/// Timers are futures that output the [`Instant`] at which they fired. /// Timers are futures that output the [`Instant`] at which they fired.
@ -127,6 +132,9 @@ pub struct Timer {
/// When this timer fires. /// When this timer fires.
when: Instant, when: Instant,
/// The period.
period: Duration,
} }
impl Timer { impl Timer {
@ -161,10 +169,8 @@ impl Timer {
/// # }); /// # });
/// ``` /// ```
pub fn at(instant: Instant) -> Timer { pub fn at(instant: Instant) -> Timer {
Timer { // Use Duration::MAX once duration_constants are stabilized.
id_and_waker: None, Timer::interval_at(instant, duration_max())
when: instant,
}
} }
/// Sets the timer to expire after the new duration of time. /// Sets the timer to expire after the new duration of time.
@ -222,6 +228,47 @@ impl Timer {
*id = Reactor::get().insert_timer(self.when, waker); *id = Reactor::get().insert_timer(self.when, waker);
} }
} }
/// Creates a timer that ticks every period.
///
/// # Examples
///
/// ```
/// use async_io::Timer;
/// use futures_lite::StreamExt;
/// use std::time::{Duration, Instant};
///
/// # futures_lite::future::block_on(async {
/// let period = Duration::from_secs(1);
/// Timer::interval(period).next().await;
/// # });
/// ```
pub fn interval(period: Duration) -> Timer {
Timer::interval_at(Instant::now() + period, period)
}
/// Creates a timer that ticks every period, starting at `start`.
///
/// # Examples
///
/// ```
/// use async_io::Timer;
/// use futures_lite::StreamExt;
/// use std::time::{Duration, Instant};
///
/// # futures_lite::future::block_on(async {
/// let now = Instant::now();
/// let period = Duration::from_secs(1);
/// Timer::interval_at(now, period).next().await;
/// # });
/// ```
pub fn interval_at(start: Instant, period: Duration) -> Timer {
Timer {
id_and_waker: None,
when: start,
period: period,
}
}
} }
impl Drop for Timer { impl Drop for Timer {
@ -236,14 +283,33 @@ impl Drop for Timer {
impl Future for Timer { impl Future for Timer {
type Output = Instant; type Output = Instant;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.poll_next(cx) {
Poll::Ready(Some(when)) => Poll::Ready(when),
Poll::Pending => Poll::Pending,
Poll::Ready(None) => unreachable!(),
}
}
}
impl Stream for Timer {
type Item = Instant;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// Check if the timer has already fired. // Check if the timer has already fired.
if Instant::now() >= self.when { if Instant::now() >= self.when {
if let Some((id, _)) = self.id_and_waker.take() { if let Some((id, _)) = self.id_and_waker.take() {
// Deregister the timer from the reactor. // Deregister the timer from the reactor.
Reactor::get().remove_timer(self.when, id); Reactor::get().remove_timer(self.when, id);
} }
Poll::Ready(self.when) let when = self.when;
if let Some(next) = when.checked_add(self.period) {
self.when = next;
// Register the timer in the reactor.
let id = Reactor::get().insert_timer(self.when, cx.waker());
self.id_and_waker = Some((id, cx.waker().clone()));
}
return Poll::Ready(Some(when));
} else { } else {
match &self.id_and_waker { match &self.id_and_waker {
None => { None => {
@ -261,8 +327,8 @@ impl Future for Timer {
} }
Some(_) => {} Some(_) => {}
} }
Poll::Pending
} }
Poll::Pending
} }
} }

View File

@ -5,7 +5,7 @@ use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use async_io::Timer; use async_io::Timer;
use futures_lite::{future, FutureExt}; use futures_lite::{future, FutureExt, StreamExt};
fn spawn<T: Send + 'static>( fn spawn<T: Send + 'static>(
f: impl Future<Output = T> + Send + 'static, f: impl Future<Output = T> + Send + 'static,
@ -30,6 +30,22 @@ fn smoke() {
}); });
} }
#[test]
fn interval() {
future::block_on(async {
let period = Duration::from_secs(1);
let jitter = Duration::from_millis(500);
let start = Instant::now();
let mut timer = Timer::interval(period);
timer.next().await;
let elapsed = start.elapsed();
assert!(elapsed >= period && elapsed - period < jitter);
timer.next().await;
let elapsed = start.elapsed();
assert!(elapsed >= period * 2 && elapsed - period * 2 < jitter);
});
}
#[test] #[test]
fn poll_across_tasks() { fn poll_across_tasks() {
future::block_on(async { future::block_on(async {