From a47fd6d1a838d43afc30cd7fcaccc556498c68a2 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Fri, 29 Mar 2024 13:28:59 -0400 Subject: [PATCH] feat: provide safe wrapper of global_timer_set in ic-cdk (#475) * feat: provide safe wrapper of global_timer_set in ic-cdk * changelog * e2e test --- e2e-tests/canisters/timers.rs | 5 +++++ e2e-tests/tests/e2e.rs | 33 +++++++++++++++++++++++++++++++++ src/ic-cdk/CHANGELOG.md | 4 ++++ src/ic-cdk/src/api/mod.rs | 17 +++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/e2e-tests/canisters/timers.rs b/e2e-tests/canisters/timers.rs index ec277fa8f..03433aaa8 100644 --- a/e2e-tests/canisters/timers.rs +++ b/e2e-tests/canisters/timers.rs @@ -92,4 +92,9 @@ fn add_event(event: &'static str) { EVENTS.with(|events| events.borrow_mut().push(event)); } +#[update] +fn set_global_timer(timestamp: u64) -> u64 { + ic_cdk::api::set_global_timer(timestamp) +} + fn main() {} diff --git a/e2e-tests/tests/e2e.rs b/e2e-tests/tests/e2e.rs index 6957dfcee..650849f19 100644 --- a/e2e-tests/tests/e2e.rs +++ b/e2e-tests/tests/e2e.rs @@ -269,6 +269,39 @@ fn advance_seconds(env: &StateMachine, seconds: u32) { } } +#[test] +fn test_set_global_timers() { + // Must be more than the queue limit (500) + let env = env(); + let system_time = std::time::SystemTime::now(); + + env.set_time(system_time); + + let wasm = cargo_build_canister("timers"); + let canister_id = env.create_canister(None); + env.install_canister(canister_id, wasm, vec![], None); + + call_candid::<_, ()>(&env, canister_id, "schedule_long", ()) + .expect("Failed to call schedule_long"); + let ts0 = system_time + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() as u64 + + 9_000_000_000; // the long event is scheduled 9 seconds from ts0 + advance_seconds(&env, 5); + + // set the timer to 5 seconds from ts0 + let ts1 = ts0 + 5_000_000_000; + let (previous,) = call_candid::<(u64,), (u64,)>(&env, canister_id, "set_global_timer", (ts1,)) + .expect("Failed to call set_global_timer"); + assert_eq!(previous, ts0); + + // deactivate the timer + let (previous,) = call_candid::<(u64,), (u64,)>(&env, canister_id, "set_global_timer", (0,)) + .expect("Failed to call set_global_timer"); + assert_eq!(previous, ts1); +} + #[test] fn test_canister_info() { let env = env(); diff --git a/src/ic-cdk/CHANGELOG.md b/src/ic-cdk/CHANGELOG.md index d6923d5ea..ef04c40b6 100644 --- a/src/ic-cdk/CHANGELOG.md +++ b/src/ic-cdk/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Added + +- Provide safe wrapper of global_timer_set in ic-cdk. (#475) + ## [0.13.1] - 2024-03-01 ### Changed diff --git a/src/ic-cdk/src/api/mod.rs b/src/ic-cdk/src/api/mod.rs index 98d13ca16..5a2d5fcf2 100644 --- a/src/ic-cdk/src/api/mod.rs +++ b/src/ic-cdk/src/api/mod.rs @@ -176,3 +176,20 @@ pub fn cycles_burn(amount: u128) -> u128 { } dst } + +/// Sets global timer. +/// +/// The canister can set a global timer to make the system +/// schedule a call to the exported canister_global_timer +/// Wasm method after the specified time. +/// The time must be provided as nanoseconds since 1970-01-01. +/// +/// The function returns the previous value of the timer. +/// If no timer is set before invoking the function, then the function returns zero. +/// +/// Passing zero as an argument to the function deactivates the timer and thus +/// prevents the system from scheduling calls to the canister's canister_global_timer Wasm method. +pub fn set_global_timer(timestamp: u64) -> u64 { + // SAFETY: ic0.global_timer_set is always safe to call. + unsafe { ic0::global_timer_set(timestamp as i64) as u64 } +}