BluetoothService: hop blocking IOBluetooth calls off the main actor#1
Open
imaznation wants to merge 1 commit into
Open
BluetoothService: hop blocking IOBluetooth calls off the main actor#1imaznation wants to merge 1 commit into
imaznation wants to merge 1 commit into
Conversation
Symptom: EdgeControl beachballed whenever the cursor entered the
window. Beachball cursor only shows over an unresponsive window, so
hover was the trigger of the *observation* — the actual freeze was the
main thread itself.
Root cause (confirmed by sample(1) trace, 100% of 3s on this stack):
Main → BluetoothService.sample()
→ +[IOBluetoothDevice pairedDevices]
→ +[IOBluetoothCoreBluetoothCoordinator sharedInstance]
→ -[IOBluetoothCoreBluetoothCoordinator init]
→ _dispatch_semaphore_wait_slow → semaphore_wait_trap
IOBluetooth.pairedDevices() is synchronous and parks on a semaphore
held by CoreBluetooth's subsystem-init coordinator until that's done
bringing the stack up. Worst after wake/unlock; the 10-second repeat
timer also re-hung the UI periodically thereafter. BluetoothService
was @mainactor so every call sat on the main runloop.
Fix: extract a nonisolated static `collectPairedDevices()` and dispatch
it onto a dedicated utility-QoS queue. The result is a tuple of
Sendable BTDevice values that hops back to the main actor for the
@published assignment — no non-Sendable IOBluetooth objects cross
actor boundaries.
Verified post-fix: main thread sample shows only NSApplication.run +
SwiftUI layout frames — no IOBluetooth on main.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
EdgeControl beachballs whenever the cursor enters its window. Beachball cursor only shows over an unresponsive window, so hover was the trigger of the observation — the actual freeze is the main thread itself.
Root cause
Confirmed via
sample(1)— 100% of a 3-second sample on this stack:IOBluetoothDevice.pairedDevices()is synchronous and parks on a semaphore held by CoreBluetooth's subsystem-init coordinator until that's done bringing the stack up. Worst after wake/unlock; the 10-second repeat timer also re-hangs the UI periodically thereafter.BluetoothServicewas@MainActorso every call sat on the main runloop.Fix
Extract a
nonisolated static collectPairedDevices()and dispatch it onto a dedicated utility-QoS queue. The result is a tuple of SendableBTDevicevalues that hops back to the main actor for the@Publishedassignment — no non-Sendable IOBluetooth objects cross actor boundaries.Verification
Post-fix main-thread sample shows only
NSApplication.run+ SwiftUI layout frames — no IOBluetooth on main.