Skip to content

Instantly share code, notes, and snippets.

@dabrahams
Last active January 23, 2024 21:18
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dabrahams/4092951 to your computer and use it in GitHub Desktop.
Save dabrahams/4092951 to your computer and use it in GitHub Desktop.
Notes on Apple's under-documented launchd

Notes on Apple’s Under-Documented LaunchD

Start triggers fire regardless of other conditions

For example, StartOnMount=true will cause the job to start when anything is mounted even if other conditions, e.g. QueueDirectories, say the job should not run

Rhythm of repeating jobs

The StartInterval timer begins ticking at the moment the plist is loaded. If something like QueueDirectories is preventing the job from running initially, but the queue becomes available, the job will not be started until the StartInterval timer expires. The StartInterval timer starts over when the job exits, so a 20 second job with a 10 second StartInterval will run every 30 seconds.

Start Triggers and <StartInterval>

If a trigger such as <StartOnMount> causes the job to run, it doesn’t restart the <StartInterval> timer. If it was about to expire when the Trigger happened, the job may run again immediately.

<ThrottleInterval> is about handling failures

<ThrottleInterval> says how long to wait if the job fails. If launchd thinks the job succeeded, <ThrottleInterval> will be irrelevant.

Set <ThrottleInterval> to zero for short <StartInterval> jobs

The console will say, “ThrottleInterval set to zero. You’re not that important. Ignoring.” But in fact it doesn’t ignore your setting: it stops trying to respawn the job when it exits in less than 10 seconds.

A non-existent <WorkingDirectory> is a soft error

If you specify a non-existent <WorkingDirectory>, the job runs in /

Meaning of <QueueDirectories>

If you set <QueueDirectories> and launchd sees that the directories are not empty after the job runs, it considers that to be a failure and runs the job again after the <ThrottleInterval> expires. Therefore, if you’re trying to set up a repeating job that runs only when a given directory exists, you can’t use <QueueDirectories> for that purpose.

<WatchPaths> will be triggered by disk mounts/unmounts

You can use this to respond when a particular directory has gets an image mounted/unmounted on it. This works regardless of whether the directory exists before/after the mount/unmount.

Loading and unloading with -w

If you unload a plist with -w and then subsequently try to load it without -w, launchctl will just tell you “Nothing found to load.” I’m not sure exactly what this implies yet.

@kjohnsen
Copy link

Thank you. By the way, it appears StartInterval no longer works, and the agent simply defaults to the ThrottleInterval instead.

@huyz
Copy link

huyz commented Jun 26, 2020

What's the default ThrottleInterval?

@chistogo
Copy link

chistogo commented Mar 4, 2022

What's the default ThrottleInterval?

From other sources I read it seems to be 10 seconds.

@wlbr
Copy link

wlbr commented Oct 5, 2022

You should check this: https://launchd.info/

@RJVB
Copy link

RJVB commented Dec 23, 2023

"If you set and launchd sees that the directories are not empty after the job runs, it considers that to be a failure and runs the job again after the expires. Therefore, if you’re trying to set up a repeating job that runs only when a given directory exists, you can’t use for that purpose."

I don't understand why not? The way I read the key's description suggests it's perfect for creating a KeepAlive job that will be kept alive as long as the directory isn't empty. Why wouldn't this work to start a daemon from a directory on a non-boot volume that may not yet be available when launchd is started?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment