How macOS runs background activities: 1 From within an app

Every few minutes, or even more often, your Mac runs tasks in the background. The most obvious for many users is the making of Time Machine backups every hour. But that is one of dozens – typically more than seventy – different background activities which take place, some every few minutes, others only once every now and again.

Because macOS is something of a mongrel, with various flavours of Unix at its heart, and has roots in both the original Mac operating system and NeXT, it has several different mechanisms for running and managing concurrent, background, and scheduled tasks. Some are quite well-known and described in many different sources, such as LaunchAgents and LaunchDaemons, which rely on launchd, the master task launcher in macOS. But most of the background activities which are run by macOS itself, and closely-fused apps like Safari, use different mechanisms.

This article, and its subsequent parts, attempt to explain on the basis of evidence how macOS is running and managing its own concurrent, background, and scheduled tasks.

Creating and scheduling background activity

In NSBackgroundActivityScheduler, Apple provides a mechanism by which any app can create an activity which it then hands over to macOS to manage in the background. These include one-off, on-demand, and repeated activities. This is how my own app DispatchRider works: it packages up the code to run a command tool in a completion, and hands it over to macOS to deal with.

This package is then registered in Centralized Task Scheduling (CTS) as what it terms a DASActivity, and is added to the list of activities which is managed by Duet Activity Scheduler (DAS).

Running background activity

DAS maintains a scored list of background activities which usually consists of more than seventy items. Periodically, it rescores each item in its list, according to various criteria such as whether it is now due to be performed, i.e. clock time is now within the time period in which CTS calculated it should next be run.

If its CurrentScore exceeds the ThresholdScore, and is closer to 1.0, then DAS sets the DecisionToRun for that activity to 1, which is the signal to CTS to run the activity. If the CurrentScore is below the ThresholdScore, then its DecisionToRun is set to 0, and the activity is left to the next rescoring.

With DAS giving an activity the instruction to run, that action is passed to CTS, which writes in the log that “DAS told us to run” the activity. It changes the activity’s state from 1 to 2, and initiates inter-process communication by XPC to the running activity.

When the activity is complete, its state is changed from 2 to 5, XPC activity finished, and CTS then works out the next time period in which that activity should be run, if it is due to repeat. This is then passed back to DAS for its list of activities and their scores.

Removing background activity

When the app which registered the activity in the first place decides that the activity is no longer required, it signals this through activity.Invalidate(), back to CTS. There it is unregistered, its state changed from 1 to -1, and DAS is told to remove it from its list of activities.

I have summarised these events and processes in the following diagram, drawn using Scapple:


If you want a copy in PDF, you can obtain it from here: gcd01a

Is this Grand Central Dispatch?

To understand what is going on, in the absence of any coherent account from Apple, we have to examine its other descriptions of related parts of macOS. As that documentation is aimed at developers, it primarily addresses how they can build concurrency into their apps.

Concurrency within applications allows them to make the best use of multiple processor cores, to maintain responsiveness to the user, and to cope with jobs which take noticeable lengths of time. Apple’s Concurrency Programming Guide (last updated in December 2012) details recommended approaches. These have now converged on the use of Operation Objects and Queues, implemented in NSOperation and its relatives, which Apple describes as being part of GCD, “a technology for executing asynchronous tasks concurrently”.

Apple provides more recent insights into these matters in its Energy Efficiency Guide for Mac Apps (last updated in September 2016 or March 2017 perhaps). This covers the prioritisation of work at app and task levels using NSOperation and relatives, as part of GCD.

Centralized Task Scheduling (CTS) and GCD are then advocated as means of managing background activity. CTS is described as a set of interfaces which “allow you to designate criteria for when a task should be performed, such as when a user plugs the computer into power or when the system is not performing higher-priority tasks.”

Later, Apple explains another programming interface, XPC Activity, which “can be used to request centralized scheduling of discretionary tasks in your app.” This is part of a much more general lightweight mechanism for inter-process communication known as XPC, which for once doesn’t seem to be a simple acronym. In its Daemons and Services Programming Guide, Apple explains how XPC Services are “integrated with Grand Central Dispatch (GCD) and launchd“, but makes no mention of CTS.

As far as I can see, no Apple developer document uses any abbreviation or term which might refer to Duet, Duet Activity Scheduler, or DAS. Equally, although Apple mentions GCD in many different sections of documentation, there is no named part, service, etc., within macOS which identifies with GCD.

I have shown how an app running background activities, which fall within the scope of the facilities of GCD, uses two named systems, DAS and CTS, in a close and highly interconnected manner. Until such time as Apple makes its terminology clearer, it seems logical to assume that DAS and CTS are at least functionally part of GCD. If you can think of a better suggestion, please make it.