fix(mac): avoid provider presence deadlock - #1212
Conversation
A background credential read held the provider-presence lock while UserDefaults synchronously notified SwiftUI. SwiftUI could re-read presence while holding its own update lock, leaving each thread waiting on the other. Serialize presence writes on the main queue and release the presence lock before publishing defaults changes.
|
The |
…ck test cleanup The lock no longer covers the defaults write, so the main-queue hop is what keeps the read-modify-write atomic; say so where the next reader would be tempted to make it async. The regression test now cleans up its observer and defaults domain on every exit and waits up to 5 seconds, so a loaded runner cannot fake a deadlock.
|
Reviewed in depth and merging. Verified independently: your regression test fails on origin/main (both flags false, the writing thread is wedged) and passes on this branch, so the deadlock is real and the fix is at the root: the reentrant contains() from the synchronous UserDefaults notification now finds the lock free. Full suite 349 tests in 44 suites green. Two small things patched on top rather than sent back: a comment explaining that the main-queue hop is now what keeps the read-modify-write atomic (so nobody flips it to async later), and the test cleanup moved into defer with a 5 second bound so a loaded runner cannot fake a deadlock. One note on the description: the mechanism is single-thread reentrancy on a non-recursive NSLock, not two locks taken in opposite order. |
Problem
Checking whether a provider's credentials are present could deadlock the menubar app — the presence check and the credential store's mutation path took the same lock in opposite order (reproducer:
presenceWritesDoNotDeadlockSwiftUICallbacks).Fix
Reorder the lock acquisition in
CapacityDockProviderCredentialso presence reads never wait on the write path.Tests
ProviderSettingsEditorStateTestsregression coverage (37 lines)