A couple of days ago I outlined what I had discovered of a new and apparently undocumented subsystem in macOS Catalina: RunningBoard. As no one has yet come back and told me that they know all about it, and there are several detailed accounts available, I have been looking at its function in more depth. This article shows how a fairly ordinary app, my SilentKnight, is handled by RunningBoard, through its life cycle.
Coupled with the observations in my previous article, I suggest that:
- RunningBoard is involved with the entire life cycle of apps, from launch to exit.
- When an app crashes, is forced to exit, or the user quits it, RunningBoard is not only involved, but may well appear in crash dumps and logs.
- RunningBoard doesn’t itself appear to be responsible for forced exits, but responds to them.
- If you want to watch what’s going on with apps in the log, filtering entries by RunningBoard’s subsystem
com.apple.runningboard
is very convenient. - Unlike other subsystems, because RunningBoard is so new, its log entries are profuse, detailed, and largely uncensored for privacy.
SilentKnight is a modest app built in Swift on AppKit: all good basic stuff, and it’s hardened and notarized. When run from the Finder (by a double-click), RunningBoard is first contacted not by the app, but by LaunchServices, which has been handling its launch.
00.879032 launchservicesd RunningBoardServices Acquiring assertion: <RBSAssertionDescriptor; foregroundApp:579; ID: 0x0; target: 579>
00.879165 RunningBoard Received message from [daemon<com.apple.coreservices.launchservicesd>:133] (euid 0): acquireAssertionWithDescriptor:error:
The first thing that RunningBoard does is work out which process the target is (PID 579), and its effective user ID (euid).
00.880324 RunningBoard _resolveProcessWithIdentifier sysctl pid 579 is euid 501
00.880331 RunningBoard Unable to obtain process properties from launchd for pid=579
00.881358 RunningBoard Resolved pid 579 to [executable<SilentKnight(501)>:579]
It then decides, presumably after obtaining information about the process, that it’s not going to manage it. I don’t know what distinguishes managed processes; SilentKnight doesn’t run in a Sandbox, nor does it have any special entitlements.
00.881767 RunningBoard [executable<SilentKnight(501)>:579] This process will not be managed.
Instead, it tracks it.
00.881770 RunningBoard Now tracking process: [executable<SilentKnight(501)>:579]
00.881926 RunningBoard Acquiring assertion targeting executable<SilentKnight(501)> from originator [daemon<com.apple.coreservices.launchservicesd>:133] with description <RBSAssertionDescriptor; foregroundApp:579; ID: 259-133-95; target: 579> attributes = {
<RBSDomainAttribute: 0x7f932ef16140; domain: com.apple.launchservicesd; name: RoleUserInteractiveNonFocal; sourceEnvironment: 0x0>;
}
It then makes this an assertion not of the new app, but of LaunchServices, with the PID 133.
00.882038 RunningBoard Assertion 259-133-95 (target:executable<SilentKnight(501)>) will be created as active
It records that the app is in the foreground.
00.882042 RunningBoard explanation=foregroundApp:579;target=[executable<SilentKnight(501)>:579];attributes=
00.882118 RunningBoard executable<SilentKnight(501)>:579 is now targeted by 1 assertions
00.882302 RunningBoardServices Acquiring assertion: <RBSAssertionDescriptor; foregroundApp:579; ID: 0x0; target: 579>
00.882487 RunningBoard [executable<SilentKnight(501)>:579] Applying updated state
It then establishes what it is going to manage for the app. This could include memory management, lifecycle management, GPU management, and limits on CPU use. It does this through an assertion.
00.882488 RunningBoard [executable<SilentKnight(501)>:579] Ignoring jetsam update because this process is not memory-managed
00.882489 RunningBoard [executable<SilentKnight(501)>:579] Ignoring resume because this process is not lifecycle managed
00.882495 RunningBoard [executable<SilentKnight(501)>:579] Set darwin role to: UserInteractiveNonFocal
00.882496 RunningBoard [executable<SilentKnight(501)>:579] Ignoring GPU update because this process is not GPU managed
00.882496 RunningBoard [executable<SilentKnight(501)>:579] Ignoring CPU limits because this process is not CPU limit managed
00.882506 RunningBoard Finished acquiring assertion 259-133-95 (target:executable<SilentKnight(501)>)
It receives another message from LaunchServices, from which it makes the app active.
00.882580 RunningBoard Received message from [daemon<com.apple.coreservices.launchservicesd>:133] (euid 0): acquireAssertionWithDescriptor:error:
00.882688 RunningBoard Acquiring assertion targeting executable<SilentKnight(501)> from originator [daemon<com.apple.coreservices.launchservicesd>:133] with description <RBSAssertionDescriptor; foregroundApp:579; ID: 259-133-96; target: 579> attributes = {
<RBSDomainAttribute: 0x7f932ee27cb0; domain: com.apple.launchservicesd; name: RoleUserInteractiveNonFocal; sourceEnvironment: 0x0>;
}
00.882791 RunningBoard Assertion 259-133-96 (target:executable<SilentKnight(501)>) will be created as active
00.882795 RunningBoard explanation=foregroundApp:579;target=[executable<SilentKnight(501)>:579];attributes=
00.882853 RunningBoard executable<SilentKnight(501)>:579 is now targeted by 2 assertions
It now invalidates the first assertion from LaunchServices (259-133-95) that the app is active, leaving the second (259-133-96).
00.883052 RunningBoard Received message from [daemon<com.apple.coreservices.launchservicesd>:133] (euid 0): async_invalidateAssertionWithIdentifier:
00.883134 RunningBoard Invalidating assertion 259-133-95 (target:executable<SilentKnight(501)>) from originator 133
00.883139 RunningBoard Finished acquiring assertion 259-133-96 (target:executable<SilentKnight(501)>)
Next is an assertion from LaunchServices again that the app is active with the given attributes.
00.924018 RunningBoardServices Acquiring assertion: <RBSAssertionDescriptor; frontmost:579; ID: 0x0; target: 579>
00.924233 RunningBoard Received message from [daemon<com.apple.coreservices.launchservicesd>:133] (euid 0): acquireAssertionWithDescriptor:error:
00.924367 RunningBoard Acquiring assertion targeting executable<SilentKnight(501)> from originator [daemon<com.apple.coreservices.launchservicesd>:133] with description <RBSAssertionDescriptor; frontmost:579; ID: 259-133-97; target: 579> attributes = {
<RBSDomainAttribute: 0x7f932ee1f0e0; domain: com.apple.launchservicesd; name: RoleUserInteractiveFocal; sourceEnvironment: 0x0>;
}
00.924513 RunningBoard Assertion 259-133-97 (target:executable<SilentKnight(501)>) will be created as active
00.924518 RunningBoard explanation=frontmost:579;target=[executable<SilentKnight(501)>:579];attributes=
RunningBoard repeats the management decisions which it made earlier.
00.924630 RunningBoard executable<SilentKnight(501)>:579 is now targeted by 3 assertions
00.925309 RunningBoard Finished acquiring assertion 259-133-97 (target:executable<SilentKnight(501)>)
00.925432 RunningBoard [executable<SilentKnight(501)>:579] Applying updated state
00.925434 RunningBoard [executable<SilentKnight(501)>:579] Ignoring jetsam update because this process is not memory-managed
00.925449 RunningBoard [executable<SilentKnight(501)>:579] Set darwin role to: UserInteractiveFocal
00.925450 RunningBoard [executable<SilentKnight(501)>:579] Ignoring GPU update because this process is not GPU managed
00.925450 RunningBoard [executable<SilentKnight(501)>:579] Ignoring CPU limits because this process is not CPU limit managed
There are no further interactions between the app and RunningBoard until the app is quit (normally). This is marked by a very distinctive log entry referring once again to Star Wars, I believe. Correction: this isn’t an external reference, but simply marks the death of a process.
56.937682 RunningBoard [executable<SilentKnight(501)>:579] Death sentinel fired!
56.938992 RunningBoard Received message from [daemon<com.apple.coreservices.launchservicesd>:133] (euid 0): async_invalidateAssertionWithIdentifier:
56.939015 RunningBoard Invalidating assertion 259-133-97 (target:executable<SilentKnight(501)>) from originator 133
Tearing the app down follows.
56.941937 RunningBoard Invalidating assertion 259-133-96 (target:executable<SilentKnight(501)>) from originator 133
57.044131 RunningBoard Removing process: [executable<SilentKnight(501)>:579]
57.044133 RunningBoard Processing invalidation queue with 2 assertions
57.044154 RunningBoard Removing 2 assertions
57.044380 RunningBoard executable<SilentKnight(501)>:579 is now targeted by 1 assertions
57.044396 RunningBoard executable<SilentKnight(501)>:579 is now targeted by 0 assertions
57.044469 RunningBoard Removing assertions for terminated process: [executable<SilentKnight(501)>:579]
57.044486 RunningBoard Removing 0 assertions
57.044555 RunningBoard Resolved state for exited process: <RBProcessState: 0x7f932ed24fa0; identity: executable<SilentKnight(501)>; role: None>
57.044602 RunningBoard Ended tracking state for process identity executable<SilentKnight(501)>:579
Recall that, when RunningBoard started up, it announced in the log “Battlecruiser operational”; each time that it’s notified that an app which it has been tracking has exited – whether through user choice or because the app has been forced to exit, for example when it fails Gatekeeper checks of notarization – it rejoins with “Death sentinel fired!”
My next task in getting to grips with what RunningBoard does is to find an app which it manages rather than just tracks.