Skip to content

Commit 5fb07f5

Browse files
authored
feat: safe binding for msg_deadline (#545)
* feat: safe binding for msg_deadline * Option<NonZeroU64> for msg_deadline
1 parent 3184c9c commit 5fb07f5

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

e2e-tests/src/bin/api.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
//! # NOTE
2+
//! The [`inspect_message`] function defined below mandates that all the update/query entrypoints must start with "call_".
3+
14
use candid::Principal;
2-
use ic_cdk::api::*;
5+
use ic_cdk::{api::*, call::ConfigurableCall};
36

47
#[export_name = "canister_update call_msg_arg_data"]
58
fn call_msg_arg_data() {
@@ -13,6 +16,41 @@ fn call_msg_caller() {
1316
msg_reply(vec![]);
1417
}
1518

19+
/// This entrypoint will call [`call_msg_deadline`] with both best-effort and guaranteed responses.
20+
#[ic_cdk::update]
21+
async fn call_msg_deadline_caller() {
22+
use ic_cdk::call::{Call, SendableCall};
23+
// Call with best-effort responses.
24+
let reply1 = Call::new(canister_self(), "call_msg_deadline")
25+
.call_raw()
26+
.await
27+
.unwrap();
28+
assert_eq!(reply1, vec![1]);
29+
// Call with guaranteed responses.
30+
let reply1 = Call::new(canister_self(), "call_msg_deadline")
31+
.with_guaranteed_response()
32+
.call_raw()
33+
.await
34+
.unwrap();
35+
assert_eq!(reply1, vec![0]);
36+
}
37+
38+
/// This entrypoint is to be called by [`call_msg_deadline_caller`].
39+
/// If the call was made with best-effort responses, `msg_deadline` should be `Some`, then return 1.
40+
/// If the call was made with guaranteed responses, `msg_deadline` should be `None`, then return 0.
41+
#[export_name = "canister_update call_msg_deadline"]
42+
fn call_msg_deadline() {
43+
let reply = match msg_deadline() {
44+
Some(v) => {
45+
// `NonZeroU64::get()` converts the value to `u64`.
46+
assert!(v.get() > 1);
47+
1
48+
}
49+
None => 0,
50+
};
51+
msg_reply(vec![reply]);
52+
}
53+
1654
#[export_name = "canister_update call_msg_reply"]
1755
fn call_msg_reply() {
1856
msg_reply(vec![42]);

e2e-tests/tests/api.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ fn call_api() {
2020
.update_call(canister_id, sender, "call_msg_caller", vec![])
2121
.unwrap();
2222
assert_eq!(res, WasmResult::Reply(vec![]));
23+
let res = pic
24+
.update_call(canister_id, sender, "call_msg_deadline_caller", vec![])
25+
.unwrap();
26+
// Unlike the other entry points, `call_msg_dealine_caller` was implemented with the `#[update]` macro.
27+
// So it returns the bytes of the Candid value `()` which is not the vec![]`.
28+
// The assertion below is to check if the call was successful.
29+
assert!(matches!(res, WasmResult::Reply(_)));
2330
// `msg_reject_code` and `msg_reject_msg` can't be tested here.
2431
// They are invoked in the reply/reject callback of inter-canister calls.
2532
// So the `call.rs` test covers them.

ic-cdk/src/api.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
//! For example, [`msg_arg_data`] wraps both `ic0::msg_arg_data_size` and `ic0::msg_arg_data_copy`.
2020
2121
use candid::Principal;
22-
use std::convert::TryFrom;
22+
use std::{convert::TryFrom, num::NonZeroU64};
2323

2424
pub mod call;
2525
pub mod management_canister;
@@ -83,6 +83,34 @@ pub fn msg_reject_msg() -> String {
8383
String::from_utf8_lossy(&bytes).into_owned()
8484
}
8585

86+
/// Gets the deadline, in nanoseconds since 1970-01-01, after which the caller might stop waiting for a response.
87+
///
88+
/// For calls to update methods with best-effort responses and their callbacks,
89+
/// the deadline is computed based on the time the call was made,
90+
/// and the `timeout_seconds` parameter provided by the caller.
91+
/// In such cases, the deadline value will be converted to `NonZeroU64` and wrapped in `Some`.
92+
/// To get the deadline value as a `u64`, call `get()` on the `NonZeroU64` value.
93+
///
94+
/// ```rust,no_run
95+
/// use ic_cdk::api::msg_deadline;
96+
/// if let Some(deadline) = msg_deadline() {
97+
/// let deadline_value : u64 = deadline.get();
98+
/// }
99+
/// ```
100+
///
101+
/// For other calls (ingress messages and all calls to query and composite query methods,
102+
/// including calls in replicated mode), a `None` is returned.
103+
/// Please note that the raw `msg_deadline` system API returns 0 in such cases.
104+
/// This function is a wrapper around the raw system API that provides more semantic information through the return type.
105+
pub fn msg_deadline() -> Option<NonZeroU64> {
106+
// SAFETY: ic0.msg_deadline is always safe to call.
107+
let nano_seconds = unsafe { ic0::msg_deadline() };
108+
match nano_seconds {
109+
0 => None,
110+
_ => Some(NonZeroU64::new(nano_seconds).unwrap()),
111+
}
112+
}
113+
86114
/// Replies to the sender with the data.
87115
pub fn msg_reply<T: AsRef<[u8]>>(data: T) {
88116
let buf = data.as_ref();

0 commit comments

Comments
 (0)