Deadlock on `AudioDevice deinit`

#63 · closed · 4 comments

View on GitHub ↗

alin23

![image](https://user-images.githubusercontent.com/3819725/152947217-ab3a244e-076e-416b-815a-d673ce80a7f7.png) ## Crash Traceback from [Lunar](https://github.com/alin23/Lunar) ```swift Thread 18 Crashed: 0 libdispatch.dylib 0x305f65160 __DISPATCH_WAIT_FOR_QUEUE__ 1 libdispatch.dylib 0x305f64c8c [inlined] _dispatch_sync_f_slow 2 libdispatch.dylib 0x305f64c8c _dispatch_sync_f_slow 3 libswiftDispatch.dylib 0x32ac897b8 OS_dispatch_queue.sync<T> 4 libswiftDispatch.dylib 0x32ac88cac OS_dispatch_queue._syncHelper<T> 5 libswiftDispatch.dylib 0x32ac896c0 OS_dispatch_queue.sync<T> 6 Lunar 0x2031f00f8 [inlined] AudioObjectPool.remove (AudioObjectPool.swift:43) 7 Lunar 0x2031f00f8 AudioDevice.deinit (AudioDevice.swift:51) 8 Lunar 0x2031f02d0 AudioDevice.__deallocating_deinit 9 libswiftCore.dylib 0x3206c564c _swift_release_dealloc 10 Lunar 0x2031e704c AudioObjectPool.remove (AudioObjectPool.swift:44) 11 Lunar 0x2031f08cc [inlined] thunk for closure 12 Lunar 0x2031f08cc thunk for closure 13 libswiftDispatch.dylib 0x32ac8a92c thunk for closure 14 libswiftDispatch.dylib 0x32ac8aa5c thunk for closure 15 libswiftDispatch.dylib 0x32ac88e6c OS_dispatch_queue._syncHelper<T> 16 libswiftDispatch.dylib 0x32ac8a7d8 thunk for closure 17 libswiftDispatch.dylib 0x32ac84948 thunk for closure 18 libdispatch.dylib 0x305f55ba8 _dispatch_client_callout 19 libdispatch.dylib 0x305f64dfc _dispatch_lane_barrier_sync_invoke_and_complete 20 libswiftDispatch.dylib 0x32ac897b8 OS_dispatch_queue.sync<T> 21 libswiftDispatch.dylib 0x32ac88cac OS_dispatch_queue._syncHelper<T> 22 libswiftDispatch.dylib 0x32ac896c0 OS_dispatch_queue.sync<T> 23 Lunar 0x2031f00f8 [inlined] AudioObjectPool.remove (AudioObjectPool.swift:43) 24 Lunar 0x2031f00f8 AudioDevice.deinit (AudioDevice.swift:51) 25 Lunar 0x2031f02d0 AudioDevice.__deallocating_deinit 26 libswiftCore.dylib 0x3206c564c _swift_release_dealloc 27 libswiftCore.dylib 0x3206b82c0 swift_arrayDestroy 28 libswiftCore.dylib 0x3203d5154 _ContiguousArrayStorage.__deallocating_deinit 29 libswiftCore.dylib 0x3206c564c _swift_release_dealloc 30 libswiftCore.dylib 0x3206b82c0 swift_arrayDestroy 31 libswiftCore.dylib 0x32040376c _DictionaryStorage.deinit 32 libswiftCore.dylib 0x3204037d8 _DictionaryStorage.__deallocating_deinit 33 libswiftCore.dylib 0x3206c564c _swift_release_dealloc 34 libswiftCore.dylib 0x3203fb86c _SwiftDeferredNSDictionary.__deallocating_deinit 35 libswiftCore.dylib 0x3206c564c _swift_release_dealloc 36 Lunar 0x2031e66c0 propertyListener 37 CoreAudio 0x3098d43fc HALObject::PropertiesChanged 38 CoreAudio 0x30974a138 HALSystem::PropertiesChanged 39 CoreAudio 0x309749f30 HALSystem::ObjectsPublishedAndDied 40 CoreAudio 0x30974d9e4 HALSystem::AudioObjectsPublishedAndDied 41 CoreAudio 0x309802128 HALC_ShellPlugIn::ProxyObject_PropertiesChanged 42 CoreAudio 0x30987ff58 HALC_ProxyNotifications::CallListener_f 43 libdispatch.dylib 0x305f55ba8 _dispatch_client_callout 44 libdispatch.dylib 0x305f5d32c _dispatch_lane_serial_drain 45 libdispatch.dylib 0x305f5ded4 _dispatch_lane_invoke 46 libdispatch.dylib 0x305f5f1bc _dispatch_workloop_invoke 47 libdispatch.dylib 0x305f68704 _dispatch_workloop_worker_thread 48 libsystem_pthread.dylib 0x3062cb300 _pthread_wqthread ```

Comments

alin23

Maybe we should do the removal `async` but with a barrier? ```diff extension AudioObjectPool { func get<O: AudioObject>(_ id: AudioObjectID) -> O? { queue.sync { pool[id] as? O } } func set<O: AudioObject>(_ audioObject: O, for id: AudioObjectID) { queue.sync(flags: .barrier) { pool[id] = audioObject } } @discardableResult func remove(_ id: AudioObjectID) -> Bool { - queue.sync(flags: .barrier) { - pool.removeValue(forKey: id) != nil - } + queue.asyncAfter(deadline: .now(), flags: .barrier) { [weak self] in + guard let self = self, self.pool[id] != nil else { return } + let _ = self.pool.removeValue(forKey: id) + } + return true } } ```

rnine

How about this: ```swift func remove(_ id: AudioObjectID) { queue.async(flags: .barrier) { [weak self] in self?.pool.removeValue(forKey: id) } } ``` - 1st change: No need to return any value in `AudioObjectPool.remove(_:)` since we are not really using the returned value. - 2nd change: No need to check if `pool` contains the key before trying to remove it. According to the docs for Dictionary: "If the key isn’t found in the dictionary, removeValue(forKey:) returns nil."

alin23

Yes, much cleaner. I was just taking precautions with the `self.pool[id] != nil` to avoid any deinit recursion. I just tested my solution above in production on Lunar and after 4.2k app launches there are 0 crashes related to SimplyCoreAudio. I submitted a PR here: #64

rnine

@alin23 awesome! Thanks for spotting this!