- architecture: introduction, Go modules and workspaces, application lifecycle, request lifecycle - plugins: registration, scheduling, extending, testing - verified Examples for backpack services, towel request context, pact schedules and festival events - TestDocsRequiredPages lists the eight new pages
4.4 KiB
title, description, section, order
| title | description | section | order |
|---|---|---|---|
| Task scheduling | Run a plugin's console commands on a schedule with pact.HasSchedule, and run the scheduler in the worker, as its own process or from system cron. | plugins | 20 |
Task scheduling
WinterCMS plugins schedule work in registerSchedule. A SummerCMS plugin declares the same thing by implementing pact.HasSchedule: it returns a list of its registered console commands, each with the arguments to pass and how often to run it. Only these compiled entries ever run; there is no way to schedule an arbitrary command at runtime.
Defining schedules
Each entry is a pact.ScheduledCommand: the command name in namespace:verb form, its arguments and a pact.Cadence. The command must be registered by some plugin through pact.HasCommands.
// Schedule runs three of the plugin's registered console commands.
func (p *BlogPlugin) Schedule() []pact.ScheduledCommand {
return []pact.ScheduledCommand{
{Command: "blog:prune-drafts", Cadence: pact.Daily()},
{Command: "blog:send-digest", Cadence: pact.DailyAt(7, 30)},
{Command: "blog:sync-feed", Args: []string{"--quiet"}, Cadence: pact.Every(15 * time.Minute)},
}
}
Build a cadence with one of three functions:
| Function | Runs | Laravel equivalent |
|---|---|---|
pact.Daily |
Every day at 00:00. | ->daily() |
pact.DailyAt |
Every day at the given hour and minute. | ->dailyAt('07:30') |
pact.Every |
At every multiple of the interval since midnight, so pact.Every(15 * time.Minute) runs at :00, :15, :30 and :45. |
->everyFifteenMinutes() |
Times are wall-clock times in the app.timezone location (UTC when it is not set). On a daylight saving day a daily entry keeps its wall-clock time.
The scheduler checks every entry when a worker starts, and the start fails with an error naming the plugin ID and the entry index when:
- the command name is empty or the cadence is the zero
pact.Cadence; - a daily hour or minute is out of range;
- an
pact.Everyinterval is shorter than one second or does not divide 24 hours evenly.
A command that no plugin registers does not fail the start: each run logs a warning and is skipped.
You can inspect a cadence with pact.Cadence.At (the hour and minute of a daily cadence) and pact.Cadence.Interval (24 hours for a daily cadence). This example prints the entries above:
var plugin pact.HasSchedule = &BlogPlugin{}
for _, entry := range plugin.Schedule() {
if hour, minute, daily := entry.Cadence.At(); daily {
fmt.Printf("%s %v: daily at %02d:%02d\n", entry.Command, entry.Args, hour, minute)
continue
}
fmt.Printf("%s %v: every %s\n", entry.Command, entry.Args, entry.Cadence.Interval())
}
// Output:
// blog:prune-drafts []: daily at 00:00
// blog:send-digest []: daily at 07:30
// blog:sync-feed [--quiet]: every 15m0s
How schedules run
The schedule runs inside the background job worker from conga. Every worker turns each entry into a periodic job with the ID <plugin id>[<index>]:<command>, for example acme.blog[0]:blog:prune-drafts. When several instances of the application run, one worker is elected leader and only the leader enqueues due runs, so each period runs once across all instances, even when the leader changes mid-period.
Each run is a job on the scheduled queue with a single attempt: an interrupted run is not retried, and the next period runs normally. The worker calls the command in-process and logs its output line by line.
The worker runs in serve by default. When you run workers separately (queue.work_in_serve set to false), ./bin/acme queue:work carries the schedule too.
Running the scheduler on its own
To run only the scheduler in its own process, use schedule:run. It starts a worker on the scheduled queue and runs until it receives SIGINT or SIGTERM:
./bin/acme schedule:run
If you prefer system cron, as in Laravel, use schedule:run --once every minute. It runs, without the job queue, every entry that is due in the current minute and exits:
* * * * * cd /srv/acme && ./bin/acme schedule:run --once
schedule:run --once has no overlap lock: two runs in the same minute run the due entries twice, as Laravel does. From the application directory during development, summer schedule:run --once runs the same command through the built binary.