Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/seniorjoinu/ic-cron

Task scheduler for the Internet Computer
https://github.com/seniorjoinu/ic-cron

cron cronjob dfinity internet-computer rust scheduler

Last synced: about 2 months ago
JSON representation

Task scheduler for the Internet Computer

Awesome Lists containing this project

README

        

## IC Cron

Task scheduler rust library for the Internet Computer

### Motivation

The IC provides built-in "heartbeat" functionality which is basically a special function that gets executed each time
consensus ticks. But this is not enough for a comprehensive task scheduling - you still have to implement scheduling
logic by yourself. This rust library does exactly that - provides you with simple APIs for complex background scheduling
scenarios to execute your code at any specific time, as many times as you want.

### Installation

Make sure you're using `dfx 0.8.4` or higher.

```toml
# Cargo.toml

[dependencies]
ic-cron = "0.7"
```

### Usage

```rust
// somewhere in your canister's code
ic_cron::implement_cron!();

#[derive(CandidType, Deserialize)]
enum TaskKind {
SendGoodMorning(String),
DoSomethingElse,
}

// enqueue a task
#[ic_cdk_macros::update]
pub fn enqueue_task_1() {
cron_enqueue(
// set a task payload - any CandidType is supported
TaskKind::SendGoodMorning(String::from("sweetie")),
// set a scheduling interval (how often and how many times to execute)
ic_cron::types::SchedulingOptions {
1_000_000_000 * 60 * 5, // after waiting for 5 minutes delay once
1_000_000_000 * 10, // each 10 seconds
iterations: Iterations::Exact(20), // until executed 20 times
},
);
}

// enqueue another task
#[ic_cdk_macros::update]
pub fn enqueue_task_2() {
cron_enqueue(
TaskKind::DoSomethingElse,
ic_cron::types::SchedulingOptions {
0, // start immediately
1_000_000_000 * 60 * 5, // each 5 minutes
iterations: Iterations::Infinite, // repeat infinitely
},
);
}

// in a canister heartbeat function get all tasks ready for execution at this exact moment and use it
#[ic_cdk_macros::heartbeat]
fn heartbeat() {
// cron_ready_tasks will only return tasks which should be executed right now
for task in cron_ready_tasks() {
let kind = task.get_payload::().expect("Serialization error");

match kind {
TaskKind::SendGoodMorning(name) => {
// will print "Good morning, sweetie!"
println!("Good morning, {}!", name);
},
TaskKind::DoSomethingElse => {
...
},
};
}
}
```

### How many cycles does it consume?

Since this library is just a fancy task queue, there is no significant overhead in terms of cycles.

## How does it work?

This library uses built-in canister heartbeat functionality. Each time you enqueue a task it gets added to the task
queue. Tasks could be scheduled in different ways - they can be executed some exact number of times or infinitely. It is
very similar to how you use `setTimeout()` and `setInterval()` in javascript, but more flexible. Each
time `canister_heartbeat` function is called, you have to call `cron_ready_tasks()` function which efficiently iterates
over the task queue and pops tasks which scheduled execution timestamp is <= current timestamp. Rescheduled tasks get
their next execution timestamp relative to their previous planned execution timestamp - this way the scheduler
compensates an error caused by unstable consensus intervals.

## Limitations

Since `ic-cron` can't pulse faster than the consensus ticks, it has an error of ~2s.

## Tutorials
* [Introduction To ic-cron Library](https://dev.to/seniorjoinu/introduction-to-ic-cron-library-17g1)
* [Extending Sonic With Limit Orders Using ic-cron Library](https://hackernoon.com/tutorial-extending-sonic-with-limit-orders-using-ic-cron-library)
* [How to Execute Background Tasks on Particular Weekdays with IC-Cron and Chrono](https://hackernoon.com/how-to-execute-background-tasks-on-particular-weekdays-with-ic-cron-and-chrono)
* [How To Build A Token With Recurrent Payments On The Internet Computer Using ic-cron Library](https://dev.to/seniorjoinu/tutorial-how-to-build-a-token-with-recurrent-payments-on-the-internet-computer-using-ic-cron-library-3l2h)

## API

See the [example](./example) project for better understanding.

### implement_cron!()

This macro will implement all the functions you will use: `get_cron_state()`, `cron_enqueue()`, `cron_dequeue()`
and `cron_ready_tasks()`.

Basically, this macro implements an inheritance pattern. Just like in a regular object-oriented programming language.
Check the [source code](ic-cron-rs/src/macros.rs) for further info.

### cron_enqueue()

Schedules a new task. Returns task id, which then can be used in `cron_dequeue()` to de-schedule the task.

Params:

* `payload: CandidType` - the data you want to provide with the task
* `scheduling_interval: SchedulingInterval` - how often your task should be executed and how many times it should be
rescheduled

Returns:

* `ic_cdk::export::candid::Result` - `Ok(task id)` if everything is fine, and `Err` if there is a serialization
issue with your `payload`

### cron_dequeue()

Deschedules the task, removing it from the queue.

Params:

* `task_id: u64` - an id of the task you want to delete from the queue

Returns:

* `Option` - `Some(task)`, if the operation was a success; `None`, if there was no such task.

### cron_ready_tasks()

Returns a vec of tasks ready to be executed right now.

Returns:

* `Vec` - vec of tasks to handle

### get_cron_state()

Returns a static mutable reference to object which can be used to observe scheduler's state and modify it. Mostly
intended for advanced users who want to extend `ic-cron`. See the [source code](ic-cron-rs/src/task_scheduler.rs) for
further info.

### _take_cron_state()

Returns (moved) the cron state. Used to upgrade a canister without state cloning. Make sure you're not using `get_cron_state()`
before `_put_cron_state()` after you call this function.

### _put_cron_state()

Sets the global state of the task scheduler, so this new state is accessible from `get_cron_state()` function.

Params:

* `Option` - state object you can get from `get_cron_state()` function

These two functions could be used to persist scheduled tasks between canister upgrades:
```rust
#[ic_cdk_macros::pre_upgrade]
fn pre_upgrade_hook() {
let cron_state = _take_cron_state();

stable_save((cron_state,)).expect("Unable to save the state to stable memory");
}

#[ic_cdk_macros::post_upgrade]
fn post_upgrade_hook() {
let (cron_state,): (Option,) =
stable_restore().expect("Unable to restore the state from stable memory");

_put_cron_state(cron_state);
}
```

## Candid

You don't need to modify your `.did` file for this library to work.

## Contribution

You can reach me out here on Github opening an issue, or you could start a thread on Dfinity developer forum.

You're also welcome to suggest new features and open PR's.