IC Cron - let's schedule some tasks bois

This is great! Is there any documentation on this?

I tried this locally with dfx 0.8.0 - it doesn’t work yet.

Does Motoko need func heartbeat() or func canister_heartbeat? Does it need to be public?

Eventually, surely https://sdk.dfinity.org/docs/interface-spec/index.html will show it. I guess Roman leaked it before it is officially there :slight_smile:

This would likely become a system func heartbeat(), like preupgrade and postupgrade. But it needs compiler support (just created an issue).

3 Likes

Just do this in rust

#[export_name = "canister_heartbeat"]
fn canister_heartbeat() {
    ic_cdk::print("we are running cron");
}
1 Like

Hey there, @botch!
Thanks for your response c:

The code snipped you’ve provided is only 50% of what a cron implementation needs to do. To be useful for developers it needs a couple more things:

  1. Scheduling - you need to be able to do something not each consensus tick , but, for example, each month or each minute.
  2. Tasks - you need a way to define subprograms that will be run by the scheduler, once their time has come.

This is exactly what you can use this library for.

Have a great day!

3 Likes

I talked to the execution team earlier and got some more details on heartbeat:

  • Called at most once per consensus round
  • Cannot be customized
  • Is called first before other functions
  • No special restrictions - can do awaits, standard cycles limit
6 Likes

What does Cannot be customized mean?

Not possible to set an interval, I believe.

1 Like

This is oddly vague. It either does run or doesn’t run each round. If it runs each round the check for if you need it to run or not could be a cycle sink.

Timer hashtables would have to be used similar to multiplayer games would use. Each tick you see what subscriber needs to be triggered and trigger it.

2 Likes

IC cron is now promoted to 0.4.0 adding heartbeat support.
Please, consider helping with testing and provide feedback.

9 Likes

Thanks! Can you please add license to the library?

Done.

Adding more text to make the forum engine happy.

This is great, but how do we consume it with Motoko? (Not a Rust dev.)

I believe there is no way for that rn.

1 Like

Thank you for responding. It looks like we’ll be getting a system function named heartbeat to implement cron jobs.

If there is more work than the interval between heartbeats ( ~2s), how will this work given that the underlying heartbeat function is sync (not async)?

Async is just a wrapper around request-response. All your requests will be sent during the heartbeat function. All your responses will be processed at the block they come back to your canister.

If you want to call an async function inside the heartbeat you could use ‘ic_cdk::block_on()’ (or something like this, I don’t remember the name).

1 Like

Just adding to your answer @senior.joinu. Here’s an ultra-basic async example that does automated inter-canister calls (just using heartbeat). It took me a little while to figure this out so hopefully it helps others :rocket:

use ic_cdk::export::Principal;
use ic_cdk::api::time;
use ic_cdk::api::call;

static mut LAST_JOB_TIME: u64 = 0;

#[export_name = "canister_heartbeat"]
fn canister_heartbeat() {
    let duration: u64  = 1_000_000_000 * 10; // 10 seconds
    let t = time();
    unsafe {
        if t - duration > LAST_JOB_TIME {
            LAST_JOB_TIME = t;
            ic_cdk::block_on(test_api_call());
        }
    }
}

async fn test_api_call() {
    let principal = Principal::from_text("rrkah-fqaaa-aaaaa-aaaaq-cai").unwrap();
    let res: Result<(String,), _> = call::call(principal, "ping", ()).await;
    let res_string = format!("{:?}", res);
    
    ic_cdk::print(res_string); // -> Ok(("pong",))
}
4 Likes