alarm(alarmInfo) handler. Alarms are the way to do deferred work without an external scheduler.
| What | How |
|---|---|
| Set or replace the alarm | await this.ctx.storage.setAlarm(when) — when is ms since epoch |
| Read the scheduled time | await this.ctx.storage.getAlarm() → number | null |
| Clear the alarm | await this.ctx.storage.deleteAlarm() |
| Top-level alias | await this.ctx.setAlarm(when) (mirrors storage.setAlarm) |
| Handle the fire | override async alarm(info: AlarmInfo) on your subclass |
Set and Handle
Delivery Contract
AlarmInfo:
- At-least-once delivery. Plan for the handler to run more than once for the same scheduled time. Make writes idempotent — re-check state in
storagebefore applying effects (as theSessionexample above does), or record a done-marker keyed by the scheduled time. - A failed run is redelivered 3 times, about a second apart. If the handler throws (or exceeds its time budget), the platform re-fires it with
retryCountincremented — 4 attempts total, then the alarm is deleted (getAlarm()returnsnull). There is no signal when that happens. - A failing handler loses its alarm — don’t use
throwas your retry mechanism. Three retries a second apart won’t outlast a real outage. If the deferred work must happen, make the handler tolerate its own errors: catch, record, and re-arm withsetAlarmat a backoff you choose. - Use
info.isRetryto branch. On retry, log it, treat the work as best-effort, or skip already-completed steps.
Patterns
TTL / expiry
Set the alarm when you create the row; on fire, check the row is still expired and delete it. If the row was touched since, reset the alarm instead.Retry backoff
If your method kicks off a flaky external call, set an alarm to retry later; on fire, attempt again and either succeed or set a new alarm with a longer backoff.Session timeout
SeeSession.touch above — every interaction pushes the alarm out by the idle window; the alarm only fires if no one touches the session in time.
Coalesced drain
Because there’s only one alarm per instance, you can use it as a “flush trigger”: set it on the first write, and when it fires, drain the queue and clear it. Subsequent writes during the window just append to the queue.Limits
- One alarm per actor instance. If you need multiple timers, keep a queue in
storageand use the single alarm to drive a scheduler loop. whenis ms since epoch. Don’t pass seconds. Firing precision is about one second — don’t schedule sub-second work with it.- No recurring flag. If you want periodic work, set the next alarm at the end of your
alarmhandler. - The handler has a time budget.
alarm()runs under its own wall-clock cap — larger than the 30-second method budget, on the order of minutes, but still bounded. A run that exceeds it counts as a failure and is redelivered. deleteAll()does not clear the alarm. Keys and the alarm are separate; a pending alarm still fires afterdeleteAll(), and a handler that re-arms itself will keep running against empty state. To decommission an actor:deleteAlarm()first, thendeleteAll().
Next Steps
- Runtime API —
ActorStorage,ActorContext,AlarmInfo - Quick Start — Scaffold, deploy, and curl an Account actor
- Shared Actors — Reach one actor from many functions