Monades

Scheduling jobs with launchd and friends

September 2nd, 2019 xnix

When I tried to schedule a job for the first time in macOS, I faced the old news that Apple deprecated the good old cron in favor of launchd and a family of utilities to launch and debug jobs.

While I believe cron is going to outlive us, I decided to use launchd because it allows running jobs after the computer wakes up from sleep.

Here are the notes I took during the process:

Defining jobs

In the launchd lexicon, a ‘daemon’ is, by definition, a system-wide service of which there is one instance for all clients. An ‘agent’ is a service that runs on a per-user basis. Daemons should not attempt to display UI or interact directly with a user’s login session. Any and all work that involves interacting with a user should be done through agents.

To describe jobs, you use ‘Property List Files’ (.plist files from now on.)

.plist files use XML syntax to describe a job, and depending on where are located they fulfill a different purpose:

Location Purpose
~/Library/LaunchAgents Per-user agents provided by the user.
/Library/LaunchAgents Per-user agents provided by the administrator.
/Library/LaunchDaemons System-wide daemons provided by the administrator.
/System/Library/LaunchAgents Per-user agents provided by Apple.
/System/Library/LaunchDaemons System-wide daemons provided by Apple.

After the system boots and the kernel is running, launchd is run to finish the system initialization. As part of that initialization, it goes through the following steps:

  1. Loads the parameters for each launch-on-demand system-level daemon from the property list files found in /System/Library/LaunchDaemons/ and /Library/LaunchDaemons/.
  2. Register the sockets and file descriptors requested by those daemons.
  3. Launches any daemons that requested to be running all the time.
  4. As requests for a particular service arrive, launches the corresponding daemon and passes the request to it.
  5. When the system shuts down, it sends a SIGTERM signal to all the daemons.

Structure of a Property List File

In its most basic form a plist file only requires a few keys defined:

A description of all valid keys is located in the launchd.plist man page.

Notable keys are StartInterval and StartCalendarInterval, that allows you to define time intervals to run the process.

<key>StartCalendarInterval</key>
<dict>
 <key>Minute</key>
 <integer>45</integer>
 <key>Hour</key>
 <integer>13</integer>
 <key>Day</key>
 <integer>7</integer>
</dict>

Tips

Logging

One of the first things you want to do is enable logging to know what is going on in your process.

To do this you need to set Debug to true and provide paths for standard output and error output:

<key>StandardOutPath</key>
<string>/var/log/myjob.log</string>

<key>StandardErrorPath</key>
<string>/var/log/myjob.log</string>

<key>Debug</key>
<true/>

tip: the files must be in a directory with writing access.

Loading / Reloading

Once a job is in the right folder, you have to either restart your computer to make it load, or instruct launchctl to load the process with:

$ launchctl load ~/Library/LaunchAgents/com.myprocess.plist

If you make changes to your .pid file, you’ll want to reload the script in a two-step process:

$ launchctl unload ~/Library/LaunchAgents/com.myprocess.plist
$ launchctl load ~/Library/LaunchAgents/com.myprocess.plist

Debugging

This is a convenient alternative to editing the launchd.plist for the service and then reloading. To use launchctl to trigger a debug process:

$ sudo launchctl debug gui/$UID/com.myprocess.plist --stdout --stderr

note: $UID is your user ID, it’s a variable automatically set for you.

Once launchctl is listening, you need to open another terminal tab and start your process as described below. Everything is logged in the listening tab.

note: launchctl debug allows you to do many more than this, check out the man page.

Starting a process

To kick-start a process at any time:

$ launchctl start com.myprocess.plist

Resources



Subscribe

I try to post once a week interesting stuff about programming, *nix, and the web. If you’d like to be notified when a new post goes out, you can subscribe with the form below.


Roberto Dip