How do I schedule a job from the shell, and watch it fire?

Table of Contents

This file documents the scheduler resource at the shell, one section per command. Every command is a subcommand of scheduler. Three of them read the component's generated protocol; watch subscribes instead, because a job firing is an event and not a question. The commands are hand-written: the generated command units project one entity onto one submenu, and the operation reads and the subscription are not shapes a generated unit carries.

1. Commands

1.1. jobs

Reads the job definitions the session can see, with each one's cron expression, action type and command. The read states no scope of its own: the session decides what is visible.

The command is shaped like this:

scheduler jobs

It is addressed at scheduler.v1.job_definitions.list.

scheduler jobs

1.2. schedule

Creates a job definition, which is what arms a job. The name and the cron expression are positional and everything else is a flag. The expression is validated before anything is sent, so a mistyped cron is refused without a round trip, and the create claims the row does not already exist, so a second run of the same name is refused rather than silently replacing the first.

--action selects the handler: execute_sql runs --command, and nats_publish and send_mq_message read --payload. --inactive creates the definition without arming it, which is what makes the example below safe to run.

The command is shaped like this:

scheduler schedule <job_name> <cron> [--action <type>] [--command <sql>] [--payload <json>] [--description <text>] [--inactive] [--reason <code>]

It is addressed at scheduler.v1.job_definitions.put.

scheduler schedule shell-example "*/5 * * * *" --action execute_sql --command "select 1" --description "created by the shell script library" --inactive

1.3. remove

Deletes a definition by the name a person types. The protocol addresses a removal by identifier, so the name is resolved through a list first and a name the session cannot see is refused before anything is deleted.

The command is shaped like this:

scheduler remove <job_name>

It is addressed at scheduler.v1.job_definitions.delete, after the list that resolves the name.

scheduler remove shell-example

1.4. instances

Reads the executions the scheduler recorded, newest first, with each one's state, when it was triggered, how long it took and why it failed when it did.

The page is ordered by time and --job filters the page the service returned, so a narrow --limit can hide an older execution of the job being asked about. Raise the limit to widen the page.

The command is shaped like this:

scheduler instances [--job <name>] [--limit <n>]

It is addressed at scheduler.v1.job-instances.list.

scheduler instances --limit 5

1.5. status

Reads the live status of every job the session can see: its cron expression, whether it is active, when it last ran and how that went, when it fires next, and how many of its executions are still starting.

The command is shaped like this:

scheduler status

It is addressed at scheduler.v1.status.

scheduler status

1.6. watch

Prints the execution events as they arrive, for the number of seconds asked for. This is the one command that holds the connection open: the scheduler publishes when a job fires and nothing answers a question about that, so the shell subscribes instead of asking. The default is sixty seconds and the longest it will listen is an hour.

To see the whole flow, create an armed job in one session – the schedule example with --inactive left out – and run this in another. The running service reloads its schedule from the definition change event, so a job created while it runs fires at the next boundary without a restart, which is what makes the two sessions meet.

It subscribes to scheduler.v1.job-instance-events, which the scheduler loop publishes and which no model declares yet, because the entity it reports on has no model of its own.

The command is shaped like this:

#+example scheduler watch [–seconds <n>] #+end_example

scheduler watch --seconds 60

Emacs 29.3 (Org mode 9.6.15)