Skip to content

Commit d6d2d3e

Browse files
committed
Fix ConnectionManager.timerQueue deadlock
1 parent fb97b97 commit d6d2d3e

File tree

2 files changed

+39
-22
lines changed

2 files changed

+39
-22
lines changed

AirMessage/Connection/ConnectionManager.swift

+19-16
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class ConnectionManager {
1010
public static let shared = ConnectionManager()
1111

1212
private let timerQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".connection.timer", qos: .utility)
13+
private let timerQueueKey = DispatchSpecificKey<Bool>()
1314

1415
private var dataProxy: DataProxy?
1516
private var keepaliveTimer: DispatchSourceTimer?
@@ -29,6 +30,10 @@ class ConnectionManager {
2930
!PreferencesManager.shared.password.isEmpty
3031
}
3132

33+
init() {
34+
timerQueue.setSpecific(key: timerQueueKey, value: true)
35+
}
36+
3237
/**
3338
Sets the data proxy to use for future connections.
3439
Only call this function when the server isn't running.
@@ -1044,20 +1049,18 @@ class ConnectionManager {
10441049

10451050
//Set the timer callback
10461051
downloadRequest.timeoutCallback = { [weak self, weak client] in
1047-
DispatchQueue.global(qos: .default).async { [weak self, weak client] in
1048-
//Clean up leftover files
1049-
try? downloadRequest.cleanUp()
1050-
1051-
//Clean up task reference
1052-
guard let self = self else { return }
1053-
self.fileDownloadRequestMapLock.withWriteLock {
1054-
self.fileDownloadRequestMap[requestID] = nil
1055-
}
1056-
1057-
//Send a message to the client
1058-
guard let client = client, client.isConnected.value else { return }
1059-
self.send(basicResponseOfCode: .sendResult, requestID: requestID, resultCode: NSTSendResult.requestTimeout.rawValue, details: nil, to: client)
1052+
//Clean up leftover files
1053+
try? downloadRequest.cleanUp()
1054+
1055+
//Clean up task reference
1056+
guard let self = self else { return }
1057+
self.fileDownloadRequestMapLock.withWriteLock {
1058+
self.fileDownloadRequestMap[requestID] = nil
10601059
}
1060+
1061+
//Send a message to the client
1062+
guard let client = client, client.isConnected.value else { return }
1063+
self.send(basicResponseOfCode: .sendResult, requestID: requestID, resultCode: NSTSendResult.requestTimeout.rawValue, details: nil, to: client)
10611064
}
10621065
} catch DownloadRequestCreateError.alreadyExists {
10631066
send(basicResponseOfCode: .sendResult, requestID: requestID, resultCode: NSTSendResult.badRequest.rawValue, details: "Request ID \(requestID) already exists", to: client)
@@ -1563,7 +1566,7 @@ extension ConnectionManager: DataProxyDelegate {
15631566
NotificationNames.postUpdateConnectionCount(0)
15641567

15651568
//Stop the keepalive timer
1566-
timerQueue.sync {
1569+
runOnQueue(queue: timerQueue, key: timerQueueKey) {
15671570
keepaliveTimer?.cancel()
15681571
keepaliveTimer = nil
15691572
}
@@ -1618,7 +1621,7 @@ extension ConnectionManager: DataProxyDelegate {
16181621
dataProxy.send(message: packer.data, to: client, encrypt: false, onSent: nil)
16191622

16201623
//Start the expiry timer
1621-
timerQueue.sync {
1624+
runOnQueue(queue: timerQueue, key: timerQueueKey) {
16221625
client.startTimer(ofType: .handshakeExpiry, interval: CommConst.handshakeTimeout, queue: timerQueue) { [weak self] client in
16231626
LogManager.log("Handshake response for client \(client.readableID) timed out, disconnecting", level: .debug)
16241627
self?.dataProxy?.disconnect(client: client)
@@ -1641,7 +1644,7 @@ extension ConnectionManager: DataProxyDelegate {
16411644
NotificationNames.postUpdateConnectionCount(totalCount)
16421645

16431646
//Clean up pending timers
1644-
timerQueue.sync {
1647+
runOnQueue(queue: timerQueue, key: timerQueueKey) {
16451648
client.cancelAllTimers()
16461649
}
16471650
}

AirMessage/Helper/DispatchHelper.swift

+20-6
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77

88
import Foundation
99

10-
/**
11-
Runs work on the main thread synchronously, skipping the dispatch queue if we're already on the main thread
12-
*/
10+
///Runs work on the main thread synchronously, skipping the dispatch queue if we're already on the main thread
1311
func runOnMain<T>(execute work: () throws -> T) rethrows -> T {
1412
if Thread.isMainThread {
1513
return try work()
@@ -18,13 +16,29 @@ func runOnMain<T>(execute work: () throws -> T) rethrows -> T {
1816
}
1917
}
2018

21-
/**
22-
Runs work on the main thread asynchronously, skipping the dispatch queue if we're already on the main thread
23-
*/
19+
/// Runs work on the main thread asynchronously, skipping the dispatch queue if we're already on the main thread
2420
func runOnMainAsync(execute work: @escaping () -> Void) {
2521
if Thread.isMainThread {
2622
work()
2723
} else {
2824
DispatchQueue.main.async(execute: work)
2925
}
3026
}
27+
28+
///Runs work on the specified queue synchronously, skipping the dispatch queue if the key matches
29+
func runOnQueue<T, K>(queue: DispatchQueue, key: DispatchSpecificKey<K>, execute work: () throws -> T) rethrows -> T {
30+
if DispatchQueue.getSpecific(key: key) != nil {
31+
return try work()
32+
} else {
33+
return try queue.sync(execute: work)
34+
}
35+
}
36+
37+
///Runs work on the specified queue asynchronously, skipping the dispatch queue if the key matches
38+
func runOnQueueAsync<K>(queue: DispatchQueue, key: DispatchSpecificKey<K>, execute work: @escaping () -> Void) {
39+
if DispatchQueue.getSpecific(key: key) != nil {
40+
return work()
41+
} else {
42+
return queue.async(execute: work)
43+
}
44+
}

0 commit comments

Comments
 (0)