I have a handful of production crashes on iOS that look like this:
Crashed: com.superdupertango.Server.CallbackRunQueue
0 SDTServer 0x102b78ed0 specialized Dictionary.subscript.getter + 4378562256 (<compiler-generated>:4378562256)
1 SDTServer 0x102a22fec closure #1 in Server.processError(_:) + 4377161708 (<compiler-generated>:4377161708)
2 SDTServer 0x10257ce90 thunk for @escaping @callee_guaranteed () -> () + 4372287120 (<compiler-generated>:4372287120)
This is the offending code (all code in this area is Swift5):
private let callbackRunQueue = DispatchQueue(label: "com.superdupertango.Server.CallbackRunQueue", qos: DispatchQoS.userInitiated, target: nil)
var bodyCompletionBlocks: [String: ((Data?, Error?, String) -> Void)] = [:]
...
private init() {
NotificationCenter.default.addObserver(self, selector: #selector(self.processError(_:)), name: Notification.Name.SDTServerWriteFailed, object: nil)
}
...
@objc private func processError(_ notification: Notification) {
guard let userInfo = notification.userInfo, let requestId = userInfo["requestId"] as? String else {
return
}
self.callbackRunQueue.async {
DLog("Server.processError (\(requestId)): Got Error for request. Shutting down request.")
guard let bodyCompletionBlock = self.bodyCompletionBlocks[requestId] else {
DLog("Server.processError (\(requestId)): Failed getting body completion block. Returning")
return
}
bodyCompletionBlock(Data(), nil, requestId)
self.bodyCompletionBlocks.removeValue(forKey: requestId)
self.initialCompletionBlocks.removeValue(forKey: requestId)
}
}
Note that the callbackRunQueue
is a serial queue.
The only dictionary value that's being retrieved inside the callbackRunQueue
is the self.bodyCompletionBlocks
:
guard let bodyCompletionBlock = self.bodyCompletionBlocks[requestId] else {
I've found a few references to this error, but they all say that this may be a multi-thread access issue.
However, in my code, there are only 3 places where there is access to self.bodyCompletionBlocks
, and they are all within callbackRunQueue.async
or callbackRunQueue.sync
blocks. Also note that some of this code is running inside threads within GCDWebServer, but as I mentioned, I'm always ensuring the code runs in my callbackRunQueue
queue, so I don't think this is GCDWebServer related.
I thought that enclosing thread sensitive code inside a serial queue's async or sync blocks would protect against multi-thread access problems like this.
Any ideas?
Thanks!