Move LaunchServices bundle registration off main thread (#513)

Fixes CMUXTERM-MACOS-DS
This commit is contained in:
Lawrence Chen 2026-02-25 18:02:00 -08:00 committed by GitHub
parent 930a6b5bc9
commit f38bc3b775
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 6 deletions

View file

@ -734,6 +734,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
label: "com.cmuxterm.app.sessionPersistence",
qos: .utility
)
private static let launchServicesRegistrationQueue = DispatchQueue(
label: "com.cmuxterm.app.launchServicesRegistration",
qos: .utility
)
private static func enqueueLaunchServicesRegistrationWork(_ work: @escaping @Sendable () -> Void) {
launchServicesRegistrationQueue.async(execute: work)
}
private var lastSessionAutosaveFingerprint: Int?
private var lastSessionAutosavePersistedAt: Date = .distantPast
private var didHandleExplicitOpenIntentAtStartup = false
@ -851,7 +858,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
if !isRunningUnderXCTest {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.registerLaunchServicesBundle()
self.scheduleLaunchServicesBundleRegistration()
self.enforceSingleInstance()
self.observeDuplicateLaunches()
}
@ -5678,14 +5685,54 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
}
}
private func registerLaunchServicesBundle() {
let bundleURL = Bundle.main.bundleURL.standardizedFileURL
let registerStatus = LSRegisterURL(bundleURL as CFURL, true)
if registerStatus != noErr {
NSLog("LaunchServices registration failed (status: \(registerStatus)) for \(bundleURL.path)")
private func scheduleLaunchServicesBundleRegistration(
bundleURL: URL = Bundle.main.bundleURL.standardizedFileURL,
scheduler: @escaping (@escaping @Sendable () -> Void) -> Void = AppDelegate.enqueueLaunchServicesRegistrationWork,
register: @escaping (CFURL) -> OSStatus = { url in
LSRegisterURL(url, true)
},
breadcrumb: @escaping (_ message: String, _ data: [String: Any]) -> Void = { message, data in
sentryBreadcrumb(message, category: "startup", data: data)
}
) {
let normalizedURL = bundleURL.standardizedFileURL
breadcrumb("launchservices.register.schedule", [
"bundlePath": normalizedURL.path
])
scheduler {
let startedAt = CFAbsoluteTimeGetCurrent()
let registerStatus = register(normalizedURL as CFURL)
let durationMs = Int(((CFAbsoluteTimeGetCurrent() - startedAt) * 1000).rounded())
breadcrumb("launchservices.register.complete", [
"bundlePath": normalizedURL.path,
"status": Int(registerStatus),
"durationMs": durationMs
])
if registerStatus != noErr {
NSLog("LaunchServices registration failed (status: \(registerStatus)) for \(normalizedURL.path)")
}
}
}
#if DEBUG
func scheduleLaunchServicesBundleRegistrationForTesting(
bundleURL: URL,
scheduler: @escaping (@escaping @Sendable () -> Void) -> Void,
register: @escaping (CFURL) -> OSStatus,
breadcrumb: @escaping (_ message: String, _ data: [String: Any]) -> Void = { _, _ in }
) {
scheduleLaunchServicesBundleRegistration(
bundleURL: bundleURL,
scheduler: scheduler,
register: register,
breadcrumb: breadcrumb
)
}
#endif
private func enforceSingleInstance() {
guard let bundleId = Bundle.main.bundleIdentifier else { return }
let currentPid = ProcessInfo.processInfo.processIdentifier

View file

@ -684,6 +684,35 @@ final class AppDelegateWindowContextRoutingTests: XCTestCase {
}
}
@MainActor
final class AppDelegateLaunchServicesRegistrationTests: XCTestCase {
func testScheduleLaunchServicesRegistrationDefersRegisterWork() {
_ = NSApplication.shared
let app = AppDelegate()
var scheduledWork: (@Sendable () -> Void)?
var registerCallCount = 0
app.scheduleLaunchServicesBundleRegistrationForTesting(
bundleURL: URL(fileURLWithPath: "/tmp/../tmp/cmux-launch-services-test.app"),
scheduler: { work in
scheduledWork = work
},
register: { _ in
registerCallCount += 1
return noErr
}
)
XCTAssertEqual(registerCallCount, 0, "Registration should not run inline on the startup call path")
XCTAssertNotNil(scheduledWork, "Registration work should be handed to the scheduler")
scheduledWork?()
XCTAssertEqual(registerCallCount, 1)
}
}
final class FocusFlashPatternTests: XCTestCase {
func testFocusFlashPatternMatchesTerminalDoublePulseShape() {
XCTAssertEqual(FocusFlashPattern.values, [0, 1, 0, 1, 0])