Skip to content

Commit e1b6de0

Browse files
committed
feat(timers): implement a drop-in replacement for futures_timer::Delay
1 parent 07fe7a2 commit e1b6de0

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

src/ic-cdk-timers/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [unreleased]
88

9+
### Added
10+
11+
- Implemented a drop-in replacement for `futures_timer::Delay`.
12+
913
## [0.4.0] - 2023-07-13
1014

1115
### Changed

src/ic-cdk-timers/src/delay.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use std::fmt;
2+
use std::future::Future;
3+
use std::pin::Pin;
4+
use std::sync::Arc;
5+
use std::task::{Context, Poll};
6+
use std::time::Duration;
7+
8+
use futures_util::task::AtomicWaker;
9+
10+
use crate::{clear_timer, set_timer, TimerId};
11+
12+
/// A future representing the notification that an elapsed duration has
13+
/// occurred.
14+
///
15+
/// This is created through the `Delay::new` method indicating when the future should fire.
16+
/// Note that these futures are not intended for high resolution timers.
17+
pub struct Delay {
18+
timer_id: Option<TimerId>,
19+
waker: Arc<AtomicWaker>,
20+
at: Duration,
21+
}
22+
23+
impl Delay {
24+
/// Creates a new future which will fire at `dur` time into the future.
25+
pub fn new(dur: Duration) -> Delay {
26+
let now = duration_since_epoch();
27+
28+
Delay {
29+
timer_id: None,
30+
waker: Arc::new(AtomicWaker::new()),
31+
at: now + dur,
32+
}
33+
}
34+
35+
/// Resets this timeout to an new timeout which will fire at the time
36+
/// specified by `at`.
37+
pub fn reset(&mut self, dur: Duration) {
38+
let now = duration_since_epoch();
39+
self.at = now + dur;
40+
41+
if let Some(id) = self.timer_id.take() {
42+
clear_timer(id);
43+
}
44+
}
45+
}
46+
47+
impl Future for Delay {
48+
type Output = ();
49+
50+
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
51+
let now = duration_since_epoch();
52+
53+
if now >= self.at {
54+
Poll::Ready(())
55+
} else {
56+
// Register the latest waker
57+
self.waker.register(cx.waker());
58+
59+
// Register to global timer
60+
if self.timer_id.is_none() {
61+
let waker = self.waker.clone();
62+
let id = set_timer(self.at - now, move || waker.wake());
63+
self.timer_id = Some(id);
64+
}
65+
66+
Poll::Pending
67+
}
68+
}
69+
}
70+
71+
impl Drop for Delay {
72+
fn drop(&mut self) {
73+
if let Some(id) = self.timer_id.take() {
74+
clear_timer(id);
75+
}
76+
}
77+
}
78+
79+
impl fmt::Debug for Delay {
80+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
81+
f.debug_struct("Delay").finish()
82+
}
83+
}
84+
85+
fn duration_since_epoch() -> Duration {
86+
Duration::from_nanos(ic_cdk::api::time())
87+
}

src/ic-cdk-timers/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ use slotmap::{new_key_type, KeyData, SlotMap};
3131

3232
use ic_cdk::api::call::RejectionCode;
3333

34+
mod delay;
35+
36+
pub use delay::Delay;
37+
3438
// To ensure that tasks are removable seamlessly, there are two separate concepts here: tasks, for the actual function being called,
3539
// and timers, the scheduled execution of tasks. As this is an implementation detail, this does not affect the exported name TimerId,
3640
// which is more accurately a task ID. (The obvious solution to this, `pub use`, invokes a very silly compiler error.)

0 commit comments

Comments
 (0)