From ed58d66da9f4e5a7d3623c07901c130f08887bfc Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Mon, 7 Aug 2023 14:52:59 +0300 Subject: [PATCH 1/2] chore(timers): replace `futures` with `futures-util` --- Cargo.toml | 1 + src/ic-cdk-timers/Cargo.toml | 2 +- src/ic-cdk-timers/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3739a4709..ced71719e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ ic-cdk-timers = { path = "src/ic-cdk-timers", version = "0.4.0" } candid = "0.9" futures = "0.3" +futures-util = "0.3" hex = "0.4" quote = "1" serde = "1" diff --git a/src/ic-cdk-timers/Cargo.toml b/src/ic-cdk-timers/Cargo.toml index c4934f458..624bd50b6 100644 --- a/src/ic-cdk-timers/Cargo.toml +++ b/src/ic-cdk-timers/Cargo.toml @@ -25,7 +25,7 @@ ic-cdk.workspace = true serde.workspace = true serde_bytes.workspace = true slotmap.workspace = true -futures.workspace = true +futures-util.workspace = true [package.metadata.docs.rs] default-target = "wasm32-unknown-unknown" diff --git a/src/ic-cdk-timers/src/lib.rs b/src/ic-cdk-timers/src/lib.rs index a25ec0336..79a5eac71 100644 --- a/src/ic-cdk-timers/src/lib.rs +++ b/src/ic-cdk-timers/src/lib.rs @@ -26,7 +26,7 @@ use std::{ time::Duration, }; -use futures::{stream::FuturesUnordered, StreamExt}; +use futures_util::{stream::FuturesUnordered, StreamExt}; use slotmap::{new_key_type, KeyData, SlotMap}; use ic_cdk::api::call::RejectionCode; From 8a0d7f5314111a14a92fdd5620eb827f5723a664 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Mon, 7 Aug 2023 14:53:41 +0300 Subject: [PATCH 2/2] feat(timers): implement a drop-in replacement for `futures_timer::Delay` --- src/ic-cdk-timers/CHANGELOG.md | 4 ++ src/ic-cdk-timers/src/delay.rs | 87 ++++++++++++++++++++++++++++++++++ src/ic-cdk-timers/src/lib.rs | 4 ++ 3 files changed, 95 insertions(+) create mode 100644 src/ic-cdk-timers/src/delay.rs diff --git a/src/ic-cdk-timers/CHANGELOG.md b/src/ic-cdk-timers/CHANGELOG.md index 341af576b..7823a1e37 100644 --- a/src/ic-cdk-timers/CHANGELOG.md +++ b/src/ic-cdk-timers/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Added + +- Implemented a drop-in replacement for `futures_timer::Delay`. + ## [0.4.0] - 2023-07-13 ### Changed diff --git a/src/ic-cdk-timers/src/delay.rs b/src/ic-cdk-timers/src/delay.rs new file mode 100644 index 000000000..aeb07ba55 --- /dev/null +++ b/src/ic-cdk-timers/src/delay.rs @@ -0,0 +1,87 @@ +use std::fmt; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; + +use futures_util::task::AtomicWaker; + +use crate::{clear_timer, set_timer, TimerId}; + +/// A future representing the notification that an elapsed duration has +/// occurred. +/// +/// This is created through the `Delay::new` method indicating when the future should fire. +/// Note that these futures are not intended for high resolution timers. +pub struct Delay { + timer_id: Option, + waker: Arc, + at: Duration, +} + +impl Delay { + /// Creates a new future which will fire at `dur` time into the future. + pub fn new(dur: Duration) -> Delay { + let now = duration_since_epoch(); + + Delay { + timer_id: None, + waker: Arc::new(AtomicWaker::new()), + at: now + dur, + } + } + + /// Resets this timeout to an new timeout which will fire at the time + /// specified by `at`. + pub fn reset(&mut self, dur: Duration) { + let now = duration_since_epoch(); + self.at = now + dur; + + if let Some(id) = self.timer_id.take() { + clear_timer(id); + } + } +} + +impl Future for Delay { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let now = duration_since_epoch(); + + if now >= self.at { + Poll::Ready(()) + } else { + // Register the latest waker + self.waker.register(cx.waker()); + + // Register to global timer + if self.timer_id.is_none() { + let waker = self.waker.clone(); + let id = set_timer(self.at - now, move || waker.wake()); + self.timer_id = Some(id); + } + + Poll::Pending + } + } +} + +impl Drop for Delay { + fn drop(&mut self) { + if let Some(id) = self.timer_id.take() { + clear_timer(id); + } + } +} + +impl fmt::Debug for Delay { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("Delay").finish() + } +} + +fn duration_since_epoch() -> Duration { + Duration::from_nanos(ic_cdk::api::time()) +} diff --git a/src/ic-cdk-timers/src/lib.rs b/src/ic-cdk-timers/src/lib.rs index 79a5eac71..599bb887c 100644 --- a/src/ic-cdk-timers/src/lib.rs +++ b/src/ic-cdk-timers/src/lib.rs @@ -31,6 +31,10 @@ use slotmap::{new_key_type, KeyData, SlotMap}; use ic_cdk::api::call::RejectionCode; +mod delay; + +pub use delay::Delay; + // To ensure that tasks are removable seamlessly, there are two separate concepts here: tasks, for the actual function being called, // and timers, the scheduled execution of tasks. As this is an implementation detail, this does not affect the exported name TimerId, // which is more accurately a task ID. (The obvious solution to this, `pub use`, invokes a very silly compiler error.)