Skip to content

Commit

Permalink
feat: safe binding for msg_deadline (#545)
Browse files Browse the repository at this point in the history
* feat: safe binding for msg_deadline

* Option<NonZeroU64> for msg_deadline
  • Loading branch information
lwshang authored Jan 13, 2025
1 parent 3184c9c commit 5fb07f5
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 2 deletions.
40 changes: 39 additions & 1 deletion e2e-tests/src/bin/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! # NOTE
//! The [`inspect_message`] function defined below mandates that all the update/query entrypoints must start with "call_".
use candid::Principal;
use ic_cdk::api::*;
use ic_cdk::{api::*, call::ConfigurableCall};

#[export_name = "canister_update call_msg_arg_data"]
fn call_msg_arg_data() {
Expand All @@ -13,6 +16,41 @@ fn call_msg_caller() {
msg_reply(vec![]);
}

/// This entrypoint will call [`call_msg_deadline`] with both best-effort and guaranteed responses.
#[ic_cdk::update]
async fn call_msg_deadline_caller() {
use ic_cdk::call::{Call, SendableCall};
// Call with best-effort responses.
let reply1 = Call::new(canister_self(), "call_msg_deadline")
.call_raw()
.await
.unwrap();
assert_eq!(reply1, vec![1]);
// Call with guaranteed responses.
let reply1 = Call::new(canister_self(), "call_msg_deadline")
.with_guaranteed_response()
.call_raw()
.await
.unwrap();
assert_eq!(reply1, vec![0]);
}

/// This entrypoint is to be called by [`call_msg_deadline_caller`].
/// If the call was made with best-effort responses, `msg_deadline` should be `Some`, then return 1.
/// If the call was made with guaranteed responses, `msg_deadline` should be `None`, then return 0.
#[export_name = "canister_update call_msg_deadline"]
fn call_msg_deadline() {
let reply = match msg_deadline() {
Some(v) => {
// `NonZeroU64::get()` converts the value to `u64`.
assert!(v.get() > 1);
1
}
None => 0,
};
msg_reply(vec![reply]);
}

#[export_name = "canister_update call_msg_reply"]
fn call_msg_reply() {
msg_reply(vec![42]);
Expand Down
7 changes: 7 additions & 0 deletions e2e-tests/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ fn call_api() {
.update_call(canister_id, sender, "call_msg_caller", vec![])
.unwrap();
assert_eq!(res, WasmResult::Reply(vec![]));
let res = pic
.update_call(canister_id, sender, "call_msg_deadline_caller", vec![])
.unwrap();
// Unlike the other entry points, `call_msg_dealine_caller` was implemented with the `#[update]` macro.
// So it returns the bytes of the Candid value `()` which is not the vec![]`.
// The assertion below is to check if the call was successful.
assert!(matches!(res, WasmResult::Reply(_)));
// `msg_reject_code` and `msg_reject_msg` can't be tested here.
// They are invoked in the reply/reject callback of inter-canister calls.
// So the `call.rs` test covers them.
Expand Down
30 changes: 29 additions & 1 deletion ic-cdk/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
//! For example, [`msg_arg_data`] wraps both `ic0::msg_arg_data_size` and `ic0::msg_arg_data_copy`.
use candid::Principal;
use std::convert::TryFrom;
use std::{convert::TryFrom, num::NonZeroU64};

pub mod call;
pub mod management_canister;
Expand Down Expand Up @@ -83,6 +83,34 @@ pub fn msg_reject_msg() -> String {
String::from_utf8_lossy(&bytes).into_owned()
}

/// Gets the deadline, in nanoseconds since 1970-01-01, after which the caller might stop waiting for a response.
///
/// For calls to update methods with best-effort responses and their callbacks,
/// the deadline is computed based on the time the call was made,
/// and the `timeout_seconds` parameter provided by the caller.
/// In such cases, the deadline value will be converted to `NonZeroU64` and wrapped in `Some`.
/// To get the deadline value as a `u64`, call `get()` on the `NonZeroU64` value.
///
/// ```rust,no_run
/// use ic_cdk::api::msg_deadline;
/// if let Some(deadline) = msg_deadline() {
/// let deadline_value : u64 = deadline.get();
/// }
/// ```
///
/// For other calls (ingress messages and all calls to query and composite query methods,
/// including calls in replicated mode), a `None` is returned.
/// Please note that the raw `msg_deadline` system API returns 0 in such cases.
/// This function is a wrapper around the raw system API that provides more semantic information through the return type.
pub fn msg_deadline() -> Option<NonZeroU64> {
// SAFETY: ic0.msg_deadline is always safe to call.
let nano_seconds = unsafe { ic0::msg_deadline() };
match nano_seconds {
0 => None,
_ => Some(NonZeroU64::new(nano_seconds).unwrap()),
}
}

/// Replies to the sender with the data.
pub fn msg_reply<T: AsRef<[u8]>>(data: T) {
let buf = data.as_ref();
Expand Down

0 comments on commit 5fb07f5

Please sign in to comment.