Failed "Dictionary.subscript.getter" all within same dispatchQueue

1.3k Views Asked by At

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!

0

There are 0 best solutions below