Heartbeat improvements / Timers [Community Consideration]

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

You need to enable the ‘timers’ feature.

See here - open-chat/Cargo.toml at master · open-ic/open-chat · GitHub

4 Likes

Ah gotcha, thanks @hpeebles !

1 Like

I am running into this error when I enable the timers feature. Any ideas?

image

@Severin @berestovskyy

EDIT: Some more context. I have enabled the timer’s feature in my Cargo.toml for the crate requiring this feature. Hence, my imports work.

If you look at the error message, it seems to be breaking because an internal dependency of ic_cdk, ic0::global_timer_set is not resolved.

Other things I tried:

  • nuked my target directory and tried building again
  • enabled the feature in both the workspace cargo toml and the crate using it

So it’s all good now, or there is still an issue?

1 Like

I managed to solve it.

When I had posted I had already enabled the timer’s feature.

What solved it was running cargo update after enabling the timer feature.
That was the missing piece.

1 Like

Are timers in motoko 0.7.4? If not, when is the plan?

2 Likes