Skip to content

v0.9.2 - Bluetooth Deadlock Resilience

Latest

Choose a tag to compare

@github-actions github-actions released this 16 Feb 20:36
· 6 commits to master since this release

Improves Bluetooth deadlock resilience with a targeted fix based on spindump evidence from a live deadlock capture.

Root cause (refined)

v0.9.1 dispatched stopRunning() to a background thread to avoid blocking the main thread during Bluetooth teardown. This worked, but the leaked background thread still held a CMIO semaphore that coreaudiod was waiting on, creating a circular deadlock chain:

  1. coreaudiod IO thread stuck in HALB_Guard::WaitFor() on dead Bluetooth device (96% of spindump samples)
  2. BTAudioHALPlugin blocked by mutex held by stuck IO thread
  3. coreaudiod AudioDeviceManager blocked by CMIO semaphore owned by mic-warm
  4. mic-warm's CMIO thread blocked waiting for coreaudiod to complete teardown

Fixes

  • CMIO pipeline teardown: removeInput() / removeOutput() called on the main thread before dispatching stopRunning() to background, releasing the semaphore coreaudiod blocks on. Measured teardown at 30-44ms with no deadlock (vs potentially hanging forever)
  • Stale delegate cleanup: old session's setSampleBufferDelegate(nil, queue: nil) called during teardown to prevent stale callbacks from corrupting new session's sample count
  • Thread-safe logging: replaced DateFormatter with ISO8601DateFormatter (thread-safe by design) to prevent crashes from concurrent logging across main and background threads

Docs

  • README updated: describes both delay triggers (hardware sleep after ~60s idle AND Bluetooth device switching regardless of idle state), documents deadlock resilience approach
  • Apple Feedback draft updated: includes precise spindump call stack evidence and kTCCServiceAudioCapture finding on macOS 26.2

Full Changelog: v0.9.1...v0.9.2