Skip to content

Commit 67e3e2b

Browse files
authored
Potential Deadlock Deinit (#36)
* Validate queue before calling stop on deinit * Add dispatch precondition
1 parent 2094aa6 commit 67e3e2b

File tree

3 files changed

+87
-45
lines changed

3 files changed

+87
-45
lines changed

Sources/sACNKit/Receiver/sACNDiscoveryReceiver.swift

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ import Foundation
3030
///
3131
/// An E1.31-2018 sACN Receiver which receives universe discovery messages.
3232
public class sACNDiscoveryReceiver {
33-
33+
3434
// MARK: Socket
35-
35+
36+
/// A key used to identify the socket delegate queue for the current execution context.
37+
private static let socketDelegateQueueSpecificKey = DispatchSpecificKey<Bool>()
38+
3639
/// The Internet Protocol version(s) used by the receiver.
3740
private let ipMode: sACNIPMode
38-
41+
3942
/// The queue on which socket notifications occur (also used to protect state).
4043
private let socketDelegateQueue: DispatchQueue
4144

@@ -120,6 +123,7 @@ public class sACNDiscoveryReceiver {
120123
// sockets
121124
self.ipMode = ipMode
122125
let socketDelegateQueue = DispatchQueue(label: "com.danielmurfin.sACNKit.discoveryReceiverSocketDelegate")
126+
socketDelegateQueue.setSpecific(key: Self.socketDelegateQueueSpecificKey, value: true)
123127
self.socketDelegateQueue = socketDelegateQueue
124128
if interfaces.isEmpty {
125129
let socket = ComponentSocket(type: .receive, ipMode: ipMode, port: UDP.sdtPort, delegateQueue: socketDelegateQueue)
@@ -172,19 +176,29 @@ public class sACNDiscoveryReceiver {
172176
///
173177
/// The receiver will stop listening for sACN Universe Discovery messages.
174178
public func stop() {
175-
socketDelegateQueue.sync {
176-
guard _isListening else { return }
177-
_isListening = false
178-
179-
stopHeartbeat()
180-
181-
sockets.forEach { interface, socket in
182-
socket.delegate = nil
183-
socket.stopListening()
184-
}
185-
186-
sources = [:]
179+
if DispatchQueue.getSpecific(key: Self.socketDelegateQueueSpecificKey) == true {
180+
_stop()
181+
} else {
182+
socketDelegateQueue.sync { _stop() }
183+
}
184+
}
185+
186+
/// Performs the stop logic.
187+
///
188+
/// Must be called on `socketDelegateQueue`.
189+
private func _stop() {
190+
dispatchPrecondition(condition: .onQueue(socketDelegateQueue))
191+
guard _isListening else { return }
192+
_isListening = false
193+
194+
stopHeartbeat()
195+
196+
sockets.forEach { interface, socket in
197+
socket.delegate = nil
198+
socket.stopListening()
187199
}
200+
201+
sources = [:]
188202
}
189203

190204
/// Updates the interfaces on which this receiver listens for sACN Universe Discovery messages.

Sources/sACNKit/Receiver/sACNReceiverRaw.swift

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@ import Foundation
3232
public class sACNReceiverRaw {
3333

3434
// MARK: Socket
35-
35+
36+
/// A key used to identify the socket delegate queue for the current execution context.
37+
private static let socketDelegateQueueSpecificKey = DispatchSpecificKey<Bool>()
38+
3639
/// The Internet Protocol version(s) used by the receiver.
3740
private let ipMode: sACNIPMode
38-
41+
3942
/// The queue on which socket notifications occur (also used to protect state).
4043
private let socketDelegateQueue: DispatchQueue
4144

@@ -170,6 +173,7 @@ public class sACNReceiverRaw {
170173
// sockets
171174
self.ipMode = ipMode
172175
let socketDelegateQueue = DispatchQueue(label: "com.danielmurfin.sACNKit.receiverSocketDelegate-\(universe)")
176+
socketDelegateQueue.setSpecific(key: Self.socketDelegateQueueSpecificKey, value: true)
173177
self.socketDelegateQueue = socketDelegateQueue
174178
if interfaces.isEmpty {
175179
let socket = ComponentSocket(type: .receive, ipMode: ipMode, port: UDP.sdtPort, delegateQueue: socketDelegateQueue)
@@ -238,23 +242,33 @@ public class sACNReceiverRaw {
238242
///
239243
/// The receiver will stop listening for sACN Data messages.
240244
public func stop() {
241-
socketDelegateQueue.sync {
242-
guard _isListening else { return }
243-
self._isListening = false
244-
245-
sampleTimer?.cancel()
246-
sampleTimer = nil
247-
stopHeartbeat()
248-
249-
sockets.forEach { interface, socket in
250-
socket.delegate = nil
251-
socket.stopListening()
252-
}
253-
254-
sampling = false
255-
sources = [:]
245+
if DispatchQueue.getSpecific(key: Self.socketDelegateQueueSpecificKey) == true {
246+
_stop()
247+
} else {
248+
socketDelegateQueue.sync { _stop() }
256249
}
257250
}
251+
252+
/// Performs the stop logic.
253+
///
254+
/// Must be called on `socketDelegateQueue`.
255+
private func _stop() {
256+
dispatchPrecondition(condition: .onQueue(socketDelegateQueue))
257+
guard _isListening else { return }
258+
self._isListening = false
259+
260+
sampleTimer?.cancel()
261+
sampleTimer = nil
262+
stopHeartbeat()
263+
264+
sockets.forEach { interface, socket in
265+
socket.delegate = nil
266+
socket.stopListening()
267+
}
268+
269+
sampling = false
270+
sources = [:]
271+
}
258272

259273
/// Updates the interfaces on which this receiver listens for sACN Data messages.
260274
///

Sources/sACNKit/Source/sACNSource.swift

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ import CocoaAsyncSocket
3131
final public class sACNSource {
3232

3333
private typealias UniverseData = (universeNumber: UInt16, data: Data)
34-
34+
3535
// MARK: Socket
36-
36+
37+
/// A key used to identify the socket delegate queue for the current execution context.
38+
private static let socketDelegateQueueSpecificKey = DispatchSpecificKey<Bool>()
39+
3740
/// The Internet Protocol version(s) used by the source.
3841
private let ipMode: sACNIPMode
39-
42+
4043
/// The queue on which socket notifications occur (also used to protect state).
4144
private let socketDelegateQueue: DispatchQueue
4245

@@ -200,6 +203,7 @@ final public class sACNSource {
200203
// sockets
201204
self.ipMode = ipMode
202205
let socketDelegateQueue = DispatchQueue(label: "com.danielmurfin.sACNKit.sourceSocketDelegate")
206+
socketDelegateQueue.setSpecific(key: Self.socketDelegateQueueSpecificKey, value: true)
203207
self.socketDelegateQueue = socketDelegateQueue
204208
self.socketsShouldTerminate = [:]
205209
if interfaces.isEmpty {
@@ -285,18 +289,28 @@ final public class sACNSource {
285289
/// When stopped, this source will no longer transmit sACN messages.
286290
///
287291
public func stop() {
288-
socketDelegateQueue.sync {
289-
guard _isListening else { return }
290-
self._isListening = false
291-
292-
// stops heartbeats
293-
stopUniverseDiscovery()
294-
stopDataTransmit()
295-
296-
// stop listening on socket occurs after
297-
// final termination is sent
292+
if DispatchQueue.getSpecific(key: Self.socketDelegateQueueSpecificKey) == true {
293+
_stop()
294+
} else {
295+
socketDelegateQueue.sync { _stop() }
298296
}
299297
}
298+
299+
/// Performs the stop logic.
300+
///
301+
/// Must be called on `socketDelegateQueue`.
302+
private func _stop() {
303+
dispatchPrecondition(condition: .onQueue(socketDelegateQueue))
304+
guard _isListening else { return }
305+
self._isListening = false
306+
307+
// stops heartbeats
308+
stopUniverseDiscovery()
309+
stopDataTransmit()
310+
311+
// stop listening on socket occurs after
312+
// final termination is sent
313+
}
300314

301315
/// Updates the interfaces on which this source transmits for sACN Universe Discovery and Data messages.
302316
///

0 commit comments

Comments
 (0)