shogo4405 / HaishinKit.swift

Camera and Microphone streaming library via RTMP and SRT for iOS, macOS, tvOS and visionOS.
BSD 3-Clause "New" or "Revised" License
2.76k stars 612 forks source link

Random "dispatch_sync called on queue already owned by current thread" crash #429

Closed psegalen closed 6 years ago

psegalen commented 6 years ago

My app randomly crashes when using HaishinKit, here is the stack:

Application Specific Information:
BUG IN CLIENT OF LIBDISPATCH: dispatch_sync called on queue already owned by current thread
...
Thread 11 name:  Dispatch queue: com.haishinkit.HaishinKit.NetStream.lock
Thread 11 Crashed:
0   libdispatch.dylib               0x0000000186a430d0 _dispatch_sync_wait + 600
1   HaishinKit                      0x0000000100f2c6f8 _T010HaishinKit9NetStreamC13audioSettingss10DictionaryVySSypGvgTm + 156
2   HaishinKit                      0x0000000100f2c6f8 _T010HaishinKit9NetStreamC13audioSettingss10DictionaryVySSypGvgTm + 156
3   Seize                           0x00000001008cdb38 specialized SZBroadcastView.observeValue(forKeyPath:of:change:context:) + 56120 (SZBroadcastView.swift:108)
4   Seize                           0x00000001008c8850 @objc SZBroadcastView.observeValue(forKeyPath:of:change:context:) + 34896 (SZBroadcastView.swift:0)
5   Foundation                      0x00000001879ab9f0 NSKeyValueNotifyObserver + 304
6   Foundation                      0x00000001879ab518 NSKeyValueDidChange + 404
7   Foundation                      0x0000000187a65324 -[NSObject+ 865060 (NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:] + 668
8   Foundation                      0x00000001879aa9b4 -[NSObject+ 100788 (NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 60
9   Foundation                      0x0000000187a62b40 _NSSetUnsignedShortValueAndNotify + 272
10  HaishinKit                      0x0000000100f546cc _T010HaishinKit10RTMPStreamC10readyStateAC05ReadyE0OvW + 542412 (RTMPStream.swift:258)
11  HaishinKit                      0x0000000100f56880 closure #1 in RTMPStream.publish(_:type:) + 551040 (RTMPStream.swift:422)
12  HaishinKit                      0x0000000100efff8c _T0Ieg_IeyB_TR + 196492 (SoundSpliter.swift:0)
13  libdispatch.dylib               0x0000000186a35088 _dispatch_call_block_and_release + 24
14  libdispatch.dylib               0x0000000186a35048 _dispatch_client_callout + 16
15  libdispatch.dylib               0x0000000186a3ee48 _dispatch_queue_serial_drain$VARIANT$mp + 528
16  libdispatch.dylib               0x0000000186a3f7d8 _dispatch_queue_invoke$VARIANT$mp + 340
17  libdispatch.dylib               0x0000000186a40200 _dispatch_root_queue_drain_deferred_wlh$VARIANT$mp + 400
18  libdispatch.dylib               0x0000000186a484a0 _dispatch_workloop_worker_thread$VARIANT$mp + 644
19  libsystem_pthread.dylib         0x0000000186cdafe0 _pthread_wqthread + 932
20  libsystem_pthread.dylib         0x0000000186cdac30 start_wqthread + 4

It seems to be caused by my adaptive bitrate algorithm based on rtmpStream.currentFPS observer. Here is my code of the observer:

  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    let stream = (object as! RTMPStream)
    let currentBitrate = stream.videoSettings["bitrate"] as! Int
    if (stream.currentFPS < 25) {
      let newBitrate = stream.currentFPS > 20 ? Int(Double(currentBitrate) * 0.9) : stream.currentFPS > 10 ? Int(Double(currentBitrate) * 0.75) :  Int(Double(currentBitrate) * 0.5)
      stream.videoSettings["bitrate"] = newBitrate
    }
    if (stream.currentFPS > 29 && currentBitrate != SZBroadcastView.initialVideoBitRate!) {
      let newBitrate = Int(Double(currentBitrate) * 1.1)
      stream.videoSettings["bitrate"] = newBitrate > SZBroadcastView.initialVideoBitRate! ? SZBroadcastView.initialVideoBitRate! : newBitrate
    }
  }

Should I check something about the thread I'm running on before modifying rtmpStream.videoSettings?

psegalen commented 6 years ago

I just realized that the line in my code causing the crash is not the update of stream.videoSettings but a reading of it:

    let currentBitrate = stream.videoSettings["bitrate"] as! Int

That's odd... I'll save my bitrate in an instance variable to avoid reading it but I'm afraid that updating the value will cause the same crash sooner or later.

psegalen commented 6 years ago

It crashes on updating the value too, I have it on my Xcode, it shows a "EXC_BREAKPOINT" at this line: https://github.com/shogo4405/HaishinKit.swift/blob/master/Sources/Net/NetStream.swift#L86

psegalen commented 6 years ago

It seems to be a deadlock, following blogpost reports deadlocks being possible when using dispatch_sync, a friend of mine (@clmntcrl) pointed a possible solution to me, I'll try it.

http://blog.benjamin-encz.de/post/main-queue-vs-main-thread/

psegalen commented 6 years ago

@shogo4405 FYI, I've just tested the code I submitted in the PR by streaming for more than an hour from my app (with my adaptive bitrate algorithm) on an iPod Touch. Before this test, the app always crashed at some point during the 10 first minutes of the stream.