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 a grain from anywhere in the system, including to a
grain that has passivated, use the actor system’s grain scheduling methods; to schedule messages
to actors, use the actor scheduler. Neither is persisted: durable reminders that survive a node
restart are not available.
API
The same four methods are available onGrainContext (inside OnReceive) and on GrainProps (inside OnActivate and
OnDeactivate):
- ScheduleOnce: Deliver once after the delay.
- Schedule: Deliver repeatedly at a fixed interval.
- ScheduleWithCron: Deliver according to a cron expression.
- CancelSchedule: Cancel a timer.
Timer options
- WithTimerReference: Set the timer’s reference. References are scoped to the grain; registering under a reference already in use cancels and replaces the existing timer.
- WithTimerKeepAlive: Make ticks count as passivation activity, keeping the grain alive while the timer runs. By default ticks do not reset the passivation clock.
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.
Starting a Timer From OnActivate
Scheduling from OnReceive
A grain can schedule and cancel timers while handling messages, for example to implement a timeout:Scheduling From OnReceive
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: the period is measured from the previous fire time, not from the handler’s completion. A handler slower than the interval accumulates queued ticks in the mailbox; execution itself never overlaps.- 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.
Errors
- ErrGrainTimersStopped: Scheduling or cancelling while the grain is deactivating or not activated.
- ErrInvalidTimerInterval:
Scheduleis called with an interval that is not strictly positive. - ErrScheduledReferenceNotFound: Cancelling an unknown reference, or a one-shot that has already fired.
Grain timers vs scheduling
Grain timers are one of two ways to deliver a message to a grain later. The other is the actor system’s scheduler, which targets actors through their*PID and grains through their identity; for the grain side of that comparison,
see Grain schedules vs grain timers. Against the actor scheduler:
- Who schedules: Grain timers: The grain itself. Actor scheduler: Any code holding a
*PID. - Target: Grain timers: Always the scheduling grain. Actor scheduler: Any actor, local or remote.
- Lifetime: Grain timers: The current activation. Actor scheduler: Until cancelled or the node stops.
- Wakes the target: Grain timers: Never. Actor scheduler: Delivery fails if the actor is gone.
- Cluster cron: Grain timers: No arbitration needed (single activation). Actor scheduler:
Requires
WithReferencefor single fire.