Heartbeat improvements / Timers [Community Consideration]

The API surface should remain as simple as possible so I think the proposed API is really good.

To cancel an interval timer after a certain time you can simply set another timer which does the cancellation. There is no need to add complexity to the API.

1 Like

Sure, I think it’s fine since this is a lower level API and we can have wrapper libraries that handle these cases

Is this a typo, or is there a reason for using i64 instead of u64?

Sure, it is u64, but it’s not a typo. It’s just the way our specification describes the system API calls.

1 Like

The motion proposal for canister timers is now live: 88293

4 Likes

That’s because of the WebAssembly type system: “Integers are not inherently signed or unsigned, their interpretation is determined by individual operations.”

Even though the System API uses i64 it will be interpreted as an unsigned number similar to how the existing ic0.time : () -> (timestamp : i64); does it.

It is also worth noting that the ic0.set_global_timer() will not be directly exposed by Rust CDK. Instead we will have a higher level library operates on proper time types.

3 Likes

hoi guys,
A small update. The timers has been implemented on the IC side. The final piece was merged yesterday, and the functionality will be rolled out ~next week. We’re focusing now on implementing Rust and Motoko CDK libraries.

If you’re a CDK developer and plan to implement the timers for your CDK, please read the details below. Otherwise, we’ll keep you posted!

CDK Implementation Details

A new System API call was just added: ic0.global_timer_set(timestamp: i64). It adds support for a single global timer, and the support for multiple and interval timers will be implemented as CDK libraries for Rust/Motoko.

Here is an example implementation of the set_timer() in rusty pseudocode:

struct Task {
  id: u32,
  time_to_schedule: u64,
  fun: Fn,
}

struct TimersLib {
  tasks: MinBinaryHeap<Task>;
}

static GLOBAL_TIMERS: TimersLib = TimersLib{};
static GLOBAL_TIMER_ID: u32 = 0;

impl TimersLib {
  /// One-off execution of the fun after a minimum `delay` in seconds.
  pub fn set_timer(delay: u64, fun: Fn) -> u32 {
    GLOBAL_TIMER_ID += 1;
    let id = GLOBAL_TIMER_ID;

    let now = ic0.time();
    let time_to_schedule = now + delay;

    GLOBAL_TIMERS.tasks.push(Task{id, time_to_schedule, fun});

    let next_task = GLOBAL_TIMERS.tasks.peek();
    // Schedule the timer for the next task
    ic0.global_timer_set(next_task.time_to_schedule);
    return id;
  }
}


#[global_timer]
fn global_timer() {
  let now = ic0.time();
  for task in GLOBAL_TIMERS.tasks {
    if task.time_to_schedule <= now {
      // Execute the task
      let fun = GLOBAL_TIMERS.tasks.pop().fun;
      // IMPORTANT: call ourself (see the details below)
      ic0.call(SELF, “global_task_executor”, fun);
    } else {
      // Schedule the timer for a next task
      ic0.global_timer_set(task.time_to_schedule);
      break;
    }
  }
}

#[update]
async fn global_task_executor(fun: Fn) {
  // IMPORTANT: check the caller (see the details below)
  if ic0.caller_id() == SELF {
    // It’s ok for the user function to trap here
    fun.call();
  }
}

There are a few important details:

  1. If the global_timer() traps, all the changes are reverted. So if we set the next timer and then execute some code which traps, the timer will be lost.
    Instead, the timer library in global_timer() should just ic0.call() itself, and then do the ic0.global_timer_set(). Now when the execution in ic0.call() traps, the timer is still properly set.

  2. The global_task_executor() is a normal canister update function, so it might be called by other canisters or a user. This is potentially insecure.
    The timer library in global_task_executor() should either check again that the timer is expired, or (as in the example above) should check that the caller is this canister.

Hope that helps, and if you have questions, we’re here to help!

10 Likes

Ping Ping. No updates on Motoko side? Please can anyone refer me to Motoko implementation or example.

It is being worked on.

6 Likes

Hey there,

Any updates on availability of these methods for the Rust CDK?

Would really help with our canister cycle burn situation which has been linearly increasing with new user signup for some time now.

it is currently in review: feat: Implement canister timers API by adamspofford-dfinity · Pull Request #342 · dfinity/cdk-rs · GitHub

3 Likes

the motoko implementation is still in progress: feat: timers by ggreif · Pull Request #3542 · dfinity/motoko · GitHub

4 Likes

Is this in a version of dfx yet?

1 Like

I’m not seeing ic0.global_timer_set(timestamp: i64) reflected in the interface spec. Does that just need to be updated, or is this not actually live?

Hi @ulan . I have not participated in this thread so I apologize if this has already been addressed; but, I would really appreciate it if the current system heartbeat function was left in place. All of my designs use multiple canisters that all share a common heartbeat source and I prefer to keep it this way for simplicity and reliability sake.

I hope this is not too controversial of an ask. I just prefer to have all my canisters run on the same frequency and not worry about how the timer is implemented on each one. I think what y’all have come up with here is going to be really useful for a lot of applications. I also think it is different enough in how it works to not be redundant with the system heartbeat.

1 Like

I believe the plan is to keep the heartbeat as is, precisely not to break existing applications

2 Likes

That’s wonderful news. Thank you sir

1 Like

The new timers are in dfx 0.12.1, but not 0.12.0

3 Likes

The global_timer_set() is in the spec on the master branch. Will be published on the internetcomputer.org soon.

2 Likes

I am super confused about how to import this timer crate.

  1. when I try to import set_timer I get an error
use ic_cdk::timer::{set_timer};

lead to

error[E0432]: unresolved import ic_cdk::timer
→ src/console/src/lib.rs:22:13
|
22 | use ic_cdk::timer::{set_timer};
| ^^^^^ could not find timer in ic_cdk

Double checked lock file and ic_cdk local source code, there are some timer code - I do use ic_cdk 0.6.8 - but it seems to need a flag or something

  1. shouldn’t #[global_timer] be an attribute macro? don’t find it neither