Skip to main content
Every Stateful Actor has a single per-instance alarm — a one-shot timer you can set, replace, read, or clear from inside any method. When the alarm fires, the runtime calls the actor’s alarm(alarmInfo) handler. Alarms are the way to do deferred work without an external scheduler. There is one alarm per actor instance, not per method or per key. Setting it again replaces the previous time.

Set and Handle

The handler is just another method call on the actor — it inherits the same single-threaded dispatch guarantee as any other method. You don’t need a lock around the state the alarm touches.

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 storage before applying effects (as the Session example 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 retryCount incremented — 4 attempts total, then the alarm is deleted (getAlarm() returns null). There is no signal when that happens.
  • A failing handler loses its alarm — don’t use throw as 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 with setAlarm at a backoff you choose.
  • Use info.isRetry to 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

See Session.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 storage and use the single alarm to drive a scheduler loop.
  • when is 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 alarm handler.
  • 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 after deleteAll(), and a handler that re-arms itself will keep running against empty state. To decommission an actor: deleteAlarm() first, then deleteAll().

Next Steps