A lifecycle hook or method is a common way of allowing developers a passive way to tie into and handle the occurrence of specific and important events. In the context of React and Angular, lifecycle hooks allow developers to tie into a component mounting or state changes, and in the context of AWS EC2 these hooks are triggered when auto-scaling an instance.
On the IC, developers are currently able to actively interact with canisters but are limited in their ability to passively tie into specific important events in the lifecycle of a canister. This proposal aims to introduce an initial set of these lifecycle hooks for canisters on the IC.
Types of Lifecycle Hooks Proposed
Canister Metric Hooks
-
canister_on_low_cycles(cyclesThreshold: Nat): async ()
→ triggers when the canister hascycles <= cyclesThreshold
-
canister_on_low_heap_memory(heapMemoryThreshold: Nat): async ()
→ triggers when the canister hasheapMemory >= heapMemoryThreshold
Currently in order to monitor canisters, developers need to to proactively reach out to the canister or call a system level API. Providing canister metric lifecycle hooks allows developers to define their own thresholds for canisters and react when these thresholds are breached. Each of these thresholds will use 8 bytes and be stored in the canister settings within the replica.
If a canister metric threshold has been provided, once the replica detects that a canister has crossed that threshold it will send a message to the specific endpoint (i.e. canister_on_low_cycles()
) of the canister, triggering that specific lifecycle hook once the canister is able to process the message.
Canister Error Hooks
-
canister_on_error(methodName: Text, args: Blob, error: Error): async()
→ triggers on uncaught canister runtime error (trap), allows the developer to capture, analyze, and log different types of errors
Currently, there is no way for canister developers to catch synchronous errors occuring within a single canister, including heartbeat errors. By introducing the canister_on_error()
hook, after executing a message/heartbeat/timer, if the execution fails the replica will schedule a message to be sent to the canister_on_error() endpoint of the canister passing the error message.
Canister Lifecycle Hooks
-
canister_post_init(): async()
→ triggers immediately after thecanister_init()
(i.e. constructor) function of the canister
Currently, there is no way to execute inter-canister/asynchronous calls during the canister canister_init()
method. In order to ensure that the asynchronous call happens before any other message is executed, the current workaround is to add a guard in place that either awaits on the completion of the asynchronous task or rejects any messages until that task is completed.
The canister_post_init()
lifecycle hook would create a message in the per-canister task queue abstraction mentioned here Cross-canister compatible Post-Init hook - #5 by ulan to ensure that the canister_post_init()
hook is executed before any other “regular” message.
Wish List Hooks (additional, nice to have)
-
canister_output_queue_size(): Nat
→ synchronous call that exposes the output queue size of a canister, helping a developer to throttle/pace outgoing calls from a canister. -
canister_on_output_queue_full(): ()
→ synchronous call that triggers when a canister’s output queue is full
Special thanks to @ulan for his technical background and expertise, and encouraging me to take on this proposal.