Motoko Timer gets cancelled on canister upgrade

@ggreif @claudio

I was recently testing out a simple recurring timer that I specifically start by invoking a canister API (the timer is not initialized in the constructor, and noticed that the timer is cancelled (stopped) on canister upgrade.

It doesn’t help if I save the TimerId (Nat) as a stable variable.

In the GitHub pull request description of Feat: Timers, a method is described for persisting canister timers.

The upgrade story

Easy. The global timer gets jettisoned on upgrade, and the timers need to be set up in the post-upgrade hook. Stable variables can be used to remember the timers if they don’t have a rigid structure.

Ideally, I’m looking for a pattern that would allow me to upgrade my canister without worrying that the canister timer will be cancelled and that this pattern would allow the canister timer to pick up exactly where it last left off (almost like the upgrade never happened).

There’s the option of deploying a separate canister who’s only job is to trigger the timer (I don’t need to upgrade that canister), but that feels like a bit of a waste (ideally the upgrade process doesn’t interfere with existing timers).

Anyone have any timer patterns that they’ve found which work for them and persist the timer across canister upgrades?

2 Likes

I think there is a misunderstanding here. I am not suggesting to keep the TimerId in a stable variable (after all it is just a Nat). Instead the data necessary for re-establishing the timers (recurrent or not) should be kept in stable variables (or just hard-coded in the source) and inserted at post_upgrade time. I have considered having an automatic way of “stable timers”, but that turned to be impossible due to the function and async types involved, which cannot be serialised.

How is that done? Do you have an example?

	system func postupgrade() {
		// other code

		ignore Timer.recurringTimer(#seconds(60), log_canisters_health);

	};

I guess this works…

1 Like

Right, just like this. You might want to remember the TimerIds in (flexible/non-stable) actor variables, which gives you the ability to cancel certain timers when not needed any more.

Yeah although upgrade of canister cancels everything correct?

We likely need some kind of simple stable structure that post upgrade can cycle through and reestablish.

Are functions shared? I don’t think they are. I think each project will likely need to use variants.

If you are storing the timer for cancellation later then the pattern is going to get more complicated…although storing in the variant may be an option.

I’m about to face this with some Dutch auction functionality in the origyn nft, so I’ll try to modularize it and at least make the pattern easy to follow.

2 Likes

The current solution I’ve come to is to spin up an external canister for running timers. Although this is a does cost a bit more for initial startup of the canister, this cost is negligible and it simplifies upgrades and compartmentalizes the timer logic.

1 Like

Yep. It does, that’s why you have to re-insert it in postupgrade.

1 Like

Yeah that is what I have.