Overview
Grain timers let a grain schedule messages to itself: once after a delay, repeatedly at a fixed interval, or on a cron expression. Ticks are delivered through the grain’s mailbox and processed like any other message, serialized with the rest of the grain’s messages, withctx.Message() returning the scheduled message.
Timers are volatile and activation-scoped. They are all cancelled when the grain deactivates, they are never persisted, and they never reactivate a passivated grain. Use them for work that only matters while the grain is in memory: heartbeats, batch flushing, cache refresh, timeouts, and polling.
Grain timers are self-scheduling only. To schedule messages to actors from anywhere in the system, use the actor scheduler. Durable, reactivating schedules for grains (reminders) are not available.
API
The same four methods are available onGrainContext (inside OnReceive) and on GrainProps (inside OnActivate and OnDeactivate):
Every schedule call returns the timer’s reference, the handle used to cancel it.
Timer Options
Starting a Timer From OnActivate
Registering timers inOnActivate is the canonical way to start a grain’s periodic behavior. Timers registered there stay dormant until activation completes, so a short delay cannot fire into a grain that is not ready yet, and they are discarded if activation fails.
Scheduling From OnReceive
A grain can schedule and cancel timers while handling messages, for example to implement a timeout:Semantics
Lifecycle
- Timers registered during
OnActivatestay dormant until activation completes and are discarded when activation fails. - All timers are cancelled when the grain deactivates: passivation, system shutdown, or a failure.
OnDeactivateruns after the timers are stopped, so scheduling from that hook returnsErrGrainTimersStopped. - A tick that is already in the mailbox when its timer is cancelled, or when the grain deactivates, is dropped instead of delivered.
- After reactivation a grain starts with no timers; re-register them in
OnActivate.
Delivery
- Ticks are processed one at a time, serialized with the grain’s other messages. Tick handling never runs concurrently with other work on the same grain.
Schedulefires at a fixed cadence, matching the actor scheduler. A handler slower than the interval accumulates queued ticks in the mailbox; execution itself never overlaps. This differs from Orleans, where the period is measured from callback completion.- Tick handlers run with a background context: no deadline and no cancellation.
- A tick is fire-and-forget: an error reported with
ctx.Err(err)is logged as a warning, andctx.Unhandled()is logged the same way. Signal at most once per message (Err,NoErr, orUnhandled), as with any Tell-style message. - When the mailbox is full (bounded mailboxes only), the tick is dropped and logged; interval and cron timers keep firing.
Passivation
- By default a tick does not reset the grain’s passivation clock: a grain that only receives timer ticks still passivates on schedule. Opt in per timer with
WithTimerKeepAlive(). - Delivered ticks count as processed messages, so message-count-based passivation strategies see them.
Cron
- Cron expressions use the Quartz format, evaluated in the process’s local timezone.
- Unlike
ActorSystem.ScheduleWithCronin cluster mode, no explicit reference is required and no cluster-wide arbitration takes place: a grain has exactly one activation cluster-wide, so each tick fires exactly once by construction.