Skip to content

Commit

Permalink
chore: review all concurrency in the app
Browse files Browse the repository at this point in the history
  • Loading branch information
lwouis committed Nov 10, 2024
1 parent 16b9122 commit 690e2f6
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 9 deletions.
4 changes: 1 addition & 3 deletions src/logic/AppCenterCrashes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@ class AppCenterCrash: NSObject, CrashesDelegate {
if defaults.string(forKey: "crashPolicy") == nil {
defaults.register(defaults: ["crashPolicy": "1"])
}
if BackgroundWork.crashReportsQueue == nil {
BackgroundWork.crashReportsQueue = DispatchQueue.globalConcurrent("crashReportsQueue", .utility)
}
}

// periphery:ignore
func confirmationHandler(_ errorReports: [ErrorReport]) -> Bool {
self.initNecessaryFacilities()
let shouldSend = checkIfShouldSend()
BackgroundWork.startCrashReportsQueue()
BackgroundWork.crashReportsQueue.async {
AppCenter.networkRequestsAllowed = shouldSend
Crashes.notify(with: shouldSend ? .send : .dontSend)
Expand Down
19 changes: 18 additions & 1 deletion src/logic/BackgroundWork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class BackgroundWork {
static var crashReportsQueue: DispatchQueue!
static var accessibilityEventsThread: BackgroundThreadWithRunLoop!
static var mouseEventsThread: BackgroundThreadWithRunLoop!
static var keyboardEventsThread: BackgroundThreadWithRunLoop!
static var systemPermissionsThread: BackgroundThreadWithRunLoop!
static var repeatingKeyThread: BackgroundThreadWithRunLoop!
static var missionControlThread: BackgroundThreadWithRunLoop!
Expand All @@ -19,17 +20,33 @@ class BackgroundWork {

// swift static variables are lazy; we artificially force the threads to init
static func start() {
// TODO: clarify how this works
mainQueueConcurrentWorkQueue = DispatchQueue.globalConcurrent("mainQueueConcurrentWorkQueue", .userInteractive)
// calls to act on windows (e.g. AXUIElementSetAttributeValue, AXUIElementPerformAction) are done off the main thread
accessibilityCommandsQueue = DispatchQueue.globalConcurrent("accessibilityCommandsQueue", .userInteractive)
// calls to the AX APIs are blocking. We dispatch those on a globalConcurrent queue
axCallsQueue = DispatchQueue.globalConcurrent("axCallsQueue", .userInteractive)
crashReportsQueue = DispatchQueue.globalConcurrent("crashReportsQueue", .utility)
// we observe app and windows notifications. They arrive on this thread, and are handled off the main thread initially
accessibilityEventsThread = BackgroundThreadWithRunLoop("accessibilityEventsThread", .userInteractive)
// we observe mouse clicks when thumbnailsPanel is open. They arrive on this thread, and are handled off the main thread initially
mouseEventsThread = BackgroundThreadWithRunLoop("mouseEventsThread", .userInteractive)
// some instances of events can be handled off the main thread; maybe not worth moving to a background thread
keyboardEventsThread = BackgroundThreadWithRunLoop("keyboardEventsThread", .userInteractive)
// not 100% sure this shouldn't be on the main-thread; it doesn't do anything except dispatch to main.async
repeatingKeyThread = BackgroundThreadWithRunLoop("repeatingKeyThread", .userInteractive)
// not 100% sure this shouldn't be on the main-thread; it doesn't do anything except dispatch to main.async
missionControlThread = BackgroundThreadWithRunLoop("missionControlThread", .userInteractive)
}

static func startCrashReportsQueue() {
if crashReportsQueue == nil {
// crash reports can be sent off the main thread
crashReportsQueue = DispatchQueue.globalConcurrent("crashReportsQueue", .utility)
}
}

static func startSystemPermissionThread() {
// not 100% sure this shouldn't be on the main-thread; it doesn't do anything except dispatch to main.async
systemPermissionsThread = BackgroundThreadWithRunLoop("systemPermissionsThread", .utility)
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/logic/events/DockEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ class DockEvents {

fileprivate let handleEvent: AXObserverCallback = { _, _, notificationName, _ in
logger.d(notificationName)
MissionControl.setState(MissionControlState(rawValue: notificationName as String)!)
DispatchQueue.main.async {
MissionControl.setState(MissionControlState(rawValue: notificationName as String)!)
}
}
12 changes: 8 additions & 4 deletions src/logic/events/KeyboardEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class KeyboardEvents {
addCgEventTapForModifierFlags()
}

// TODO: handle this on a background thread?
private static func addLocalMonitorForKeyDownAndKeyUp() {
NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .keyUp]) { (event: NSEvent) in
let someShortcutTriggered = handleEvent(nil, nil, event.type == .keyDown ? UInt32(event.keyCode) : nil, cocoaToCarbonFlags(event.modifierFlags), event.type == .keyDown ? event.isARepeat : false)
Expand All @@ -91,7 +92,7 @@ class KeyboardEvents {
userInfo: nil)
if let eventTap = eventTap {
let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
CFRunLoopAddSource(BackgroundWork.keyboardEventsThread.runLoop, runLoopSource, .commonModes)
} else {
App.app.restart()
}
Expand Down Expand Up @@ -145,10 +146,13 @@ fileprivate func handleEvent(_ id: EventHotKeyID?, _ shortcutState: ShortcutStat

fileprivate let cgEventFlagsChangedHandler: CGEventTapCallBack = {_, type, cgEvent, _ in
if type == .flagsChanged {
let modifiers = cocoaToCarbonFlags(NSEvent.ModifierFlags(rawValue: UInt(cgEvent.flags.rawValue)))
handleEvent(nil, nil, nil, modifiers, false)
DispatchQueue.main.async {
let modifiers = cocoaToCarbonFlags(NSEvent.ModifierFlags(rawValue: UInt(cgEvent.flags.rawValue)))
handleEvent(nil, nil, nil, modifiers, false)
}
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) {
CGEvent.tapEnable(tap: eventTap!, enable: true)
}
return Unmanaged.passUnretained(cgEvent) // focused app will receive the event
// we always return this because we want to let these event pass through to the currently focused app
return Unmanaged.passUnretained(cgEvent)
}
1 change: 1 addition & 0 deletions src/logic/events/RunningApplicationsEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class RunningApplicationsEvents {
appsObserver = NSWorkspace.shared.observe(\.runningApplications, options: [.old, .new], changeHandler: handleEvent)
}

// TODO: handle this on a separate thread?
private static func handleEvent<A>(_: NSWorkspace, _ change: NSKeyValueObservedChange<A>) {
let workspaceApps = Set(NSWorkspace.shared.runningApplications)
// TODO: symmetricDifference has bad performance
Expand Down

0 comments on commit 690e2f6

Please sign in to comment.