I’ve been testing the outgoing HTTP requests functionality, and I had a heartbeat going that would execute 6 outgoing http requests every 1 minute. I ran into some strange behavior. When my local canister no longer had enough cycles to perform the http requests (well, I assume this is what happened), my heartbeat seemed to stop its execution part-way through the function’s execution. I didn’t get any error messages or panics. I am handling all errors that I know of using the equivalent of Results (in Azle), and thus I assumed if there was an error it would be handled and logged appropriately.
What should I expect to happen in a heartbeat function when a canister runs low on cycles? Does this behavior sound appropriate for the situation?
The replica executes the low-level Wasm
canister_heartbeat() function atomically: the state changes done by the function are committed only if the function succeeds. If the function traps, then the state changes are discarded (except for charging for the executed instructions which is done even if the function fails). In the scenario you described, the function traps with an out-of-cycles error and the state changes should be discarded. So, the part-way execution of heartbeat is unexpected.
Any async await point within a function breaks the atomicity because the parts of the function before and after an await point are executed as separate messages. Could it be that there is some await point that could explain the behaviour?
If you have an example that reproduces the issue, I’d be happy to help with debugging.