What does RunningBoard do? 1 App launching
As Macs are computers, when they become overloaded with demands on their resources, they can slow down to a crawl. When Apple was developing its first iPhone it realised that wouldn’t work with a phone, so built safeguarding systems into iOS to ensure their continuing smooth function. When Apple was preparing for the transition from Intel Macs to using its own chips, it decided to bring similar safeguards to the management of their resources. These arrived in macOS 10.15 Catalina with the introduction of RunningBoard.
Launching apps in macOS had become increasingly complex, and required more than just running the executable using launchd
. For an app to have its GUI, the code it uses has to be wired up with parts of macOS that run the GUI such as WindowServer. When it’s launched, its window(s) have to be created and brought into focus, in front of other windows. It needs its preference file opened, to be added to the Recent Items list, and for a list of its recently opened documents to be made available to its Open Recent menu command. Those latter services have been provided by LaunchServices, and to enable them it maintains a database of exhaustive details about every app it knows.
Prior to Catalina, it was LaunchServices that coordinated many of these aspects of launching an app from the Finder. Since then it has been handing more over to RunningBoard, while retaining many of its functions. RunningBoard has come to monitor and manage the entire life cycle of apps, from launch to exit. For regular macOS apps, its life cycle management remains supervisory, but for some, including Catalyst apps and those built for iPadOS, RunningBoard can manage and control their allocation of resources such as memory and access to the GPU.
As one of the newer and more pervasive services in macOS, RunningBoard writes a lot of detail in the log, indeed it’s garrulous almost to the point of excess. Although Apple documents almost nothing about its background service runningboardd
except stating that it’s “a daemon that manages process assertions to ensure those processes are kept in the appropriate state while assertions are in effect”, and its information about LaunchServices is terse and largely deprecated, we can learn a great deal from the log.
I’ll start this series of articles by explaining how RunningBoard first gets involved in launching an application. I have recently summarised its key stages in the following diagram.
Here, for the sake of simplicity, I’m going to ignore the security side completely, so we’ll assume this app isn’t quarantined, has been run recently in this session, is notarised, and hasn’t changed its CDHashes since it was last run.
As soon as LaunchServices is informed of the action to open the app, it announces it will be launched through RunningBoard, a change from its previous behaviour in Catalina, where LaunchServices did more of the work at the start of the launch process. RunningBoard receives the launch request from CoreServices, and ‘acquires’ an ‘assertion’ targeting the app, with a description to launch the app in a User Interactive role.
RunningBoard works using these assertions, a type of declaration of an intention or intended event. Its next major task is to create a job description, which it helpfully writes to the log as a dictionary. This is a mine of useful information, and has replaced the copious data compiled by LaunchServices in the past. This includes:
- a dictionary of Mach services
- whether Pressured Exit is enabled
- a full listing of environment variables, such as TMPDIR, SHELL, PATH
- RunningBoard properties including another TMPDIR
- whether to materialise dataless files.
A full example is given in the Appendix at the end. If you ever want to obtain a similar summary for an app, just launch it and inspect log entries from the com.apple.runningboard subsystem for the first second or two after launch.
Shortly after that launchd
announces that it will start (spawn) the app, and the user ID (UID) is obtained by OpenDirectory, confirming that ‘divined’ earlier by RunningBoard. This allows launchd
to complete spawning the app, and RunningBoard to decide whether it will be managed, in terms of memory and other resources. RunningBoard goes through further preparations before declaring whether the process is subject to GPU, CPU or memory limits.
LaunchServices creates the ‘pending’ application, and a new LSApplication object for it. But it also expects the imminent death of the app, in two entries that might appear surprising:com.apple.launchservices DEATH: Expecting to hear about the death of app App:"AsmAttic" asn:0x0-5b05b pid:3083 refs=4 @ 0x55402ae00, adding to sRunningBoardDeathNotificationsSetRef (pid=3083}.
com.apple.launchservices DEATH: Listening for death via runningboard notification for pending application, pid=3083.
Its fears are unfounded, though, and RunningBoard continues to receive assertions as the launch proceeds. Eventually you should see log entries confirming success:com.apple.launchservices LAUNCH: Starting application with ASN 0x0-0x5b05b co.eclecticlight.AsmAttic because it was launched and still stopped.
com.apple.processmanager LAUNCH: 0x0-0x5b05b co.eclecticlight.AsmAttic starting stopped process.
com.apple.launchservices LAUNCH: Sending 0x0-0x5b05b 3083 co.eclecticlight.AsmAttic a SIGCONT to get process started ( it was launched in the stopped state )
This is the cue for launchd
to ‘uncork’ the executable and create the processlaunchd pid/3083 [AsmAttic] uncorking exec source upfront
launchd pid/3083 [AsmAttic] created
After that, you should see log entries from the app at last, retrieving the UID and loading its preferencesAsmAttic Retrieve User by ID
AsmAttic Loading Preferences From User CFPrefsD
Key points
- RunningBoard monitors and may manage the life cycle of apps, from launch to exit, and does so by acquiring assertions about the app’s status.
- RunningBoard now plays an active part in app launch, and fills the log with its entries.
- Soon after the start of the launch process, its job description is a mine of useful information about the app being launched.
- It’s normal for app launch entries to expect the app’s imminent death before it’s launched successfully.
- Don’t be surprised or concerned to see RunningBoard mentioned in early crash reports.
Appendix: Example RunningBoard job description
<dictionary: 0x896c7dda0> { count = 23, transaction: 0, voucher = 0x0, contents =
“Platform” => <int64: 0x9f2093afcb6817e7>: 1
“ProcessType” => <string: 0x896c70de0> { length = 3, contents = “App” }
“EnableTransactions” => <bool: 0x1fd757390>: false
“_ManagedBy” => <string: 0x896c72490> { length = 22, contents = “com.apple.runningboard” }
“CFBundleIdentifier” => <string: 0x896c729a0> { length = 25, contents = “co.eclecticlight.AsmAttic” }
“_ResourceCoalition” => <string: 0x896c71740> { length = 61, contents = “app<application.co.eclecticlight.AsmAttic.753771.753789(501)>” }
“_DisablePointerAuth” => <bool: 0x1fd757370>: true
“ThrottleInterval” => <int64: 0x9f2093ac3497e817>: 2147483647
“MachServices” => <dictionary: 0x89696b120> { count = 0, transaction: 0, voucher = 0x0, contents =
}
“EnablePressuredExit” => <bool: 0x1fd757390>: false
“LimitLoadToSessionType” => <array: 0x896c70c90> { count = 2, capacity = 8, contents =
0: <string: 0x896c71680> { length = 4, contents = “Aqua” }
1: <<string: 0x896c71920> { length = 11, contents = “LoginWindow” }
}
“InitialTaskRole” => <int64: 0x9f2093afcb6817ff>: 2
“EnvironmentVariables” => <dictionary: 0x896c7e220> { count = 12, transaction: 0, voucher = 0x0, contents =
“__CF_USER_TEXT_ENCODING” => <string: 0x896c72df0> { length = 13, contents = “0x1F5:0x0:0x2” }
“TMPDIR” => <string: 0x896c722e0> { length = 49, contents = “/var/folders/x4/x00kny5x0_5dsnmmxhtw6hc80000gn/T/” }
“SHELL” => <string: 0x896c715f0> { length = 8, contents = “/bin/zsh” }
“HOME” => <string: 0x896c72370> { length = 14, contents = “/Users/hoakley” }
“SSH_AUTH_SOCK” => <string: 0x896c71b60> { length = 51, contents = “/private/tmp/com.apple.launchd.kofHVtGWoW/Listeners” }
“LOGNAME” => <string: 0x896c723d0> { length = 7, contents = “hoakley” }
“PATH” => <string: 0x896c70ae0> { length = 29, contents = “/usr/bin:/bin:/usr/sbin:/sbin” }
“XPC_SERVICE_NAME” => <string: 0x896c71560> { length = 16, contents = “com.apple.Finder” }
“__CFBundleIdentifier” => <string: 0x896c72c10> { length = 25, contents = “co.eclecticlight.AsmAttic” }
“COMMAND_MODE” => <string: 0x896c72070> { length = 8, contents = “unix2003” }
“USER” => <string: 0x896c726a0> { length = 7, contents = “hoakley” }
“XPC_FLAGS” => <string: 0x896c725e0> { length = 3, contents = “0x0” }
}
“_AdditionalProperties” => <dictionary: 0x896c7e100> { count = 1, transaction: 0, voucher = 0x0, contents =
“RunningBoard” => <dictionary: 0x896c7eb20> { count = 4, transaction: 0, voucher = 0x0, contents =
“TMPDIR” => <string: 0x896c72820> { length = 49, contents = “/var/folders/x4/x00kny5x0_5dsnmmxhtw6hc80000gn/T/” }
“HOME” => <string: 0x896c72430> { length = 14, contents = “/Users/hoakley” }
“RunningBoardLaunchedIdentity” => <dictionary: 0x896c7f1e0> { count = 5, transaction: 0, voucher = 0x0, contents =
“AJL” => <string: 0x896c727c0> { length = 51, contents = “application.co.eclecticlight.AsmAttic.753771.753789” }
“TYPE” => <int64: 0x9f2093afcb6817e7>: 1
“AUID” => <uint64: 0x9fa093afcb681847>: 501
“EAI” => <string: 0x896c717d0> { length = 25, contents = “co.eclecticlight.AsmAttic” }
“PLAT” => <uint64: 0x9fa093afcb6817e7>: 1
}
“RunningBoardLaunched” => <bool: 0x1fd757370>: true
}
}
“ExitTimeOut” => <int64: 0x9f2093afcb6817e7>: 1
“Label” => <string: 0x896c70ea0> { length = 51, contents = “application.co.eclecticlight.AsmAttic.753771.753789” }
“WaitForDebugger” => <bool: 0x1fd757370>: true
“MaterializeDatalessFiles” => <bool: 0x1fd757370>: true
“WorkingDirectory” => <string: 0x896c72760> { length = 1, contents = “/” }
“_LaunchType” => <int64: 0x9f2093afcb6817f7>: 3
“AbandonProcessGroup” => <bool: 0x1fd757370>: true
“ProgramArguments” => <array: 0x896c71080> { count = 1, capacity = 8, contents =
0: <string: 0x896c716b0> { length = 50, contents = “/Applications/AsmAttic.app/Contents/MacOS/AsmAttic” }
}
“Program” => <string: 0x896c71c20> { length = 50, contents = “/Applications/AsmAttic.app/Contents/MacOS/AsmAttic” }
}