RunningBoard: a new subsystem in Catalina to detect errors

Many of the most significant changes in macOS occur deep inside it, out of sight of users and even developers. Catalina brings several new internal features, a few of which have been documented, but others seem to have slipped past silently. Among the latter is an active subsystem to replace an old service assertiond, which can cause apps to unexpectedly terminate – to you and me, crash – in both macOS 10.15 and iOS 13: RunningBoard.

This sub-system doesn’t exist in Mojave, nor in iOS 12. It deals with what are known as assertions. For example, what happens when an app asks for memory which macOS can’t provide? If the app asserts the variable representing that memory, if that allocation of memory fails, then the app will quit immediately. That may seem counterproductive, but it’s much better than the app continuing and assuming the memory was provided, as that risks more serious consequences later.

Apps normally handle expected errors gracefully. If an app tries to write to a file but can’t, then it should display a meaningful error message to the user and let them try again. But there are situations when the developer may not expect an error to occur, for example when an app changes its preferences, which are managed for it by macOS (the service cfprefsd). Assertions can then be a valuable way of handling such errors.

Normally, assertions are used during development and debugging, then switched off when software is released. In macOS 10.15 and 10.15.1, at least, the subsystem for handling assertions, RunningBoard, is fully active in release versions, which suggests a substantial change in software practice by Apple.

This subsystem consists of a small service, /usr/libexec/runningboardd, and two Private Frameworks, RunningBoard.framework and RunningBoardServices.framework. There is also an AssertionServices.framework still, as in Mojave. Minimal access is provided from the public macOS interface, in the form of the NSAssertionHandler class and a few calls such as the NSAssert() macro. At present, RunningBoard and assertions are largely the preserve of macOS and don’t appear to be intended for direct third-party use, except in some Objective-C code.

In any given minute of unified log extract, you’re likely to see plenty of activity from the com.apple.runningboard subsystem, as it tracks and monitors different processes. Although run as root, runningboardd is started relatively late after boot, just after the Login Window app starts up, in preparation for the user to log in.

Its initial log entries are relatively brief:
2.504005 RunningBoardServices Initializing connection
2.504438 RunningBoardServices Removing all cached process handles
2.504472 RunningBoardServices found service <private>
2.505210 RunningBoardServices Sending handshake request attempt #1 to server
2.505247 RunningBoardServices Creating connection to com.apple.runningboard
2.506107 RunningBoardServices Acquiring assertion: <RBSAssertionDescriptor; "loginwindow always on assertion"; ID: 0x0; target: 178>
2.517294 RunningBoard Battlecruiser operational.

Yes, that last entry in the log is real, and presumably the work of a Star Wars fan! Correction: this refers not to Star Wars, but to StarCraft.

RunningBoard then generates a set of ‘restricted entitlements’ for related daemons including com.apple.WhiteBoard, com.apple.PreBoard, com.apple.runningboardd and com.apple.uikitsystemapp, before setting itself up. It has some interesting concerns, about processes which are ‘inetd-compatible’, those which are ‘multi-instance’, and runs its own ‘resource violation server’.

Once running, RunningBoard receives incoming connections from processes to handle assertions. For each it creates a new assertion with an ID such as 287-178-1, then returns a message such as RB assertion success NEW2, for example. The subsystem is capable of handling resource violations, and the management of memory, lifecycle, GPU and CPU limits for individual processes which elect for those features.

RunningBoard assertion IDs appear to consist of two process IDs and a serial number: the first number given is the process ID for runningboardd (or the service which has referred the assertion request, perhaps), and the second is the process ID of the process making the assertion request. So in the case of 287-178-1, this is the first assertion request made by process ID 178 through runningboardd (whose process ID is here 287).

Follow RunningBoard entries in the log, and you’ll discover that it tracks assertions targeting each registered process, that assertions are invalidated from its invalidation queue, and once invalidated they are removed. When processes exit, RunningBoard resolves their state, and stops tracking them. Presumably because of its relative immaturity, at present very few of RunningBoard’s log entries contain censored entries with the dreaded <private>, making this a good time to study this new subsystem.

There is remarkably little information available about RunningBoard at present. I’ve seen a few developers and others who have encountered app crashes which were generated by the subsystem, when an important assertion failed, and I wonder whether some of the issues currently being reported in iOS 13 might be the result of over-zealous responses to assertions. I also wonder whether RunningBoard might be part of a new strategy from Apple to tackle system reliability without having to trudge through fixing the millions of accumulated bug reports.

If you learn anything more about this fascinating new subsystem, please let me know.