Overview
GoAkt includes a built-in scheduler for delayed and recurring message delivery. It is powered by go-quartz, a minimalist scheduling library that supports the Quartz cron expression format. The scheduler starts automatically when the actor system starts and stops when the system shuts down. Its lifetime is not tied to the context passed toStart: cancelling that context once the system is up, as a start-up timeout does,
leaves the scheduler and its schedules running. Use it for timeouts, periodic tasks, heartbeats, and cron-style jobs.
Location transparency
Message scheduling is location-transparent: the same APIs work for local and remote actors. Obtain the target PID viaActorOf and pass it to ScheduleOnce, Schedule, or ScheduleWithCron; the scheduler delivers to the actor
regardless of where it runs.
The same scheduler also targets grains:
ScheduleGrainOnce, ScheduleGrain and ScheduleGrainWithCron take a
*GrainIdentity instead of a *PID and share the management API, options and cluster rules described on this page.
See Grain scheduling.Operations
- ScheduleOnce: Trigger Type: RunOnce. Behavior: Deliver a message once after a delay.
- Schedule: Trigger Type: Simple. Behavior: Deliver a message at a fixed interval (repeating).
- ScheduleWithCron: Trigger Type: Cron. Behavior: Deliver a message according to a cron expression.
API
- ScheduleOnce: Deliver once after the delay.
- Schedule: Deliver at a fixed interval (repeating).
- ScheduleWithCron: Cron-style scheduling.
- CancelSchedule: Cancel a scheduled message.
- PauseSchedule: Pause delivery.
- ResumeSchedule: Resume a paused schedule.
- ListSchedules: List every schedule currently known to the scheduler.
Schedule options
- WithReference: Set a unique ID for cancel, pause, or resume. Strongly recommended if you plan to manage the
schedule later. Without it, an auto-generated UUID is used and may not be retrievable. Required for
ScheduleWithCronin cluster mode; see Cluster-wide single fire below. - WithSender: Set the sender PID for the scheduled message. Defaults to the system’s
NoSenderactor, so the target actor seesctx.Sender()as that PID (compare it withsystem.NoSender()), not as nil.
Examples
Examples
Pausing and resuming
PauseSchedule holds a schedule back without removing it and ResumeSchedule lets it run again. A resumed interval
or cron schedule picks up its next tick as usual. A one-shot schedule (ScheduleOnce, ScheduleGrainOnce) keeps
the fire time it was scheduled with: resumed before that time, it still fires at that time; resumed after that time
passed while it was paused, it fires immediately.
Cron expression format
ScheduleWithCron uses the Quartz cron expression format as implemented by
go-quartz. This format supports 6 or 7 fields (seconds, minutes, hours,
day-of-month, month, day-of-week, and optionally year).
Field summary
- Seconds: Mandatory: YES. Allowed Values: 0-59. Allowed Special Characters:
,-*/. - Minutes: Mandatory: YES. Allowed Values: 0-59. Allowed Special Characters:
,-*/. - Hours: Mandatory: YES. Allowed Values: 0-23. Allowed Special Characters:
,-*/. - Day of month: Mandatory: YES. Allowed Values: 1-31. Allowed Special Characters:
,-*?/LW. - Month: Mandatory: YES. Allowed Values: 1-12 or JAN-DEC. Allowed Special Characters:
,-*/. - Day of week: Mandatory: YES. Allowed Values: 1-7 or SUN-SAT. Allowed Special Characters:
,-*?/L#. - Year: Mandatory: NO. Allowed Values: empty, 1970-. Allowed Special Characters:
,-*/.
Special characters
*: All values in a field (e.g.,*in minutes = “every minute”).?: No specific value; use when specifying one of two related fields (e.g., “10” in day-of-month,?in day-of-week).-: Range of values (e.g.,10-12in hour = “hours 10, 11, and 12”).,: List of values (e.g.,MON,WED,FRIin day-of-week = “Monday, Wednesday, Friday”)./: Increments (e.g.,0/15in seconds = “0, 15, 30, 45”;1/3in day-of-month = “every 3 days from the 1st”).L: Last; meaning varies by field. Ranges or lists are not allowed withL.- Day-of-month: Last day of the month (e.g.,
L-3= third to last day of the month). - Day-of-week: Last day of the week (7 or SAT) when alone; “last xxx day” when used after another value (e.g.,
6L= “last Friday”).
- Day-of-month: Last day of the month (e.g.,
W: Nearest weekday to the given day (e.g.,15W= “nearest weekday to the 15th”). If1Wfalls on Saturday, it fires Monday the 3rd.Wonly applies to a single day, not ranges or lists.#: Nth weekday of the month (e.g.,6#3= “third Friday”;2#1= “first Monday”). Does not fire if that nth weekday does not exist in the month.
LandWcan be combined in day-of-month asLW= “last weekday of the month”.- Month and day-of-week names are case-insensitive (e.g.,
MON=mon).
Cron examples
0 * * * * *: Every minute at second 0.0 0 * * * *: Every hour at minute 0.0 0 12 * * *: Every day at noon.0 */5 * * * *: Every 5 minutes.0 0 0 * * MON: Every Monday at midnight.0 0 12 1 * *: 1st of every month at noon.0 0 12 L * *: Last day of every month at noon.0 0 12 ? * WED: Every Wednesday at noon.0 0 12 15W * *: Nearest weekday to the 15th at noon.
Timezone
Outside cluster mode, cron expressions are evaluated in the local timezone of the process (time.Now().Location()). In cluster mode they are evaluated in UTC; see
Cluster-wide single fire.
Use cases
- Heartbeats and health checks: Use
Schedulewith a fixed interval. - Timeout notifications: Use
ScheduleOnceto send a timeout message after a delay. - Periodic aggregation or cleanup: Use
ScheduleWithCronfor daily, hourly, or custom schedules. - Scheduled jobs: Use cron expressions for complex recurring patterns (e.g., “every weekday at 9am”).
Cluster-wide single fire
Cluster-wide single fire applies to
ScheduleWithCron only, and only in cluster mode
(WithCluster). Schedule (interval) and ScheduleOnce are always node-local, in cluster
mode or not.ScheduleWithCron call) typically runs on
every node. When the actor system is in cluster mode, single-fire delivery for cron schedules is intrinsic; there is
no option to opt in or out of. Every node still runs its own go-quartz cron trigger locally (so ticks stay in sync
across the cluster with no extra network round trips on the hot path), but immediately before delivery each node races
to claim an exclusive, short-lived slot for that specific tick in the cluster’s shared store, the same put-if-absent
primitive that guarantees a grain is activated on only one node. Only the winner proceeds to deliver; every other node
silently skips that tick.
WithReference is required for ScheduleWithCron in cluster mode. The claim is keyed on the reference plus the
tick, so every node racing for the same tick must register with the same reference. Since a reference left unset is
auto-generated per call (and therefore different on every node), ScheduleWithCron rejects the call outright with
ErrScheduleReferenceRequired rather than silently deliver the message once per node:
Cluster-Wide Single Fire
In cluster mode the cron expression is evaluated in UTC so every node computes the same tick instants regardless
of its local timezone; the claim is keyed on the tick’s fire time, and a wall-clock expression like
"0 0 9 * * *"
evaluated in each node’s own zone would give mixed-timezone nodes different keys and deliver once per timezone instead
of once per cluster. Outside cluster mode the expression is evaluated in the process’s local timezone, as before.
Express cluster cron schedules in UTC accordingly (for example, "0 0 9 * * *" fires at 09:00 UTC).Schedule and ScheduleOnce stay node-local, even in cluster mode: their fire times are anchored to each node’s
own local registration clock rather than a shared, deterministic schedule, so there is no common tick for nodes to
arbitrate over. Registering the same interval or one-shot schedule on every node delivers on every node, exactly as
outside cluster mode.
The cluster-wide claim’s TTL is scaled to the cron schedule’s own period, clamped between one minute and 24 hours. The
TTL must outlive the worst-case delivery lag between nodes handling the same tick: a node that claims after a stale
entry has already expired would win the same tick again and deliver a duplicate. To close that window at its source, a
node that reaches a tick more than the claim TTL late (for example, after a long pause while the scheduler still replays
overdue ticks) skips it without claiming, trading a missed tick that every node was too late for over a duplicate
delivery. Claim entries are reclaimed by their TTL alone; canceling a schedule or stopping a node never deletes them,
since an entry may be the winning claim other nodes are still arbitrating against.
In cluster mode each node registers its own local cron trigger, so
CancelSchedule only stops the trigger on the node
where it is called. The schedule keeps firing cluster-wide from the remaining nodes; to stop it everywhere, cancel it
on every node that registered it.Sender and target actor
For scheduled messages, the target actor receives the message viaTell. By default, ctx.Sender() is the system’s
NoSender actor (system.NoSender()), not nil. Use WithSender(pid) if you want the target to know which actor
scheduled the message.
Errors
- ErrSchedulerNotStarted: Actor system not started; scheduler is started with the system.
- ErrScheduledReferenceNotFound: Cancel, pause, or resume called with an unknown reference, including the reference of a one-shot schedule that has already fired or of a schedule that was already canceled.
- ErrRemotingDisabled: Scheduling to a remote PID when remoting is not enabled.
- ErrScheduleReferenceRequired:
ScheduleWithCronin cluster mode called withoutWithReference.
Delivery semantics
- Fire-and-forget: Scheduling does not provide built-in delivery guarantees (at-least-once or exactly-once). Ensure idempotency where needed.
- No retries: If delivery fails (e.g., actor not found), the message is not retried.
- ScheduleOnce: Delivered at most once after the delay; no further deliveries, and no delivery at all if the target is not running when the schedule fires. Once it has fired, its reference is released and can be used again.
Introspection
ListSchedules() returns a read-only snapshot of every schedule the scheduler currently knows about, actor and grain
schedules alike. Use it to answer “what is currently scheduled, and to whom will it deliver?” - the read-side
counterpart of the reference-based Cancel/Pause/Resume management APIs.
Introspection
Introspection
ListSchedules is purely read-only: it has no effect on the schedules themselves. A schedule stops appearing once it
has been canceled via CancelSchedule or, for one-shot schedules created via ScheduleOnce or ScheduleGrainOnce,
once it has fired: the scheduler releases a fired one-shot’s reference on its own, so there is no need to clean
anything up manually.