Woof
Woof

Reputation: 1443

OS X Crash: pointer being freed was not allocated

Need a help to understand the real reason of a crash. I've made the app that should work 24/7. It reads and prepares some data from an equipment and creates a web-server to send data to the probe. The crash happens after a various amount of time, so I can't really reproduce it in the simulator.

Here are the last crash file details:

Time Awake Since Boot: 110000 seconds

System Integrity Protection: enabled

Crashed Thread: 37 Dispatch queue: com.apple.root.default-qos

Exception Type: EXC_CRASH (SIGABRT)

Exception Codes: 0x0000000000000000, 0x0000000000000000

Exception Note: EXC_CORPSE_NOTIFY

Dispatch Thread Soft Limit Reached: 64 (too many dispatch threads blocked in synchronous operations)

Application Specific Information: abort() called

*** error for object 0x247032000: pointer being freed was not allocated

Details for Thread 37:

Thread 37 Crashed:: Dispatch queue: com.apple.root.default-qos

0 libsystem_c.dylib 0x00007fff8dab3298 usleep$NOCANCEL + 0

1 libsystem_c.dylib 0x00007fff8dae16e9 abort + 139

2 libsystem_malloc.dylib 0x00007fff965fe041 free + 425

3 libswiftCore.dylib 0x0000000106fed219 $Ss19_SwiftStringStorageCfD + 9

4 libswiftCore.dylib 0x0000000106ffba00 _swift_release_dealloc + 16

...

46 org.cocoapods.GCDWebServer 0x0000000106c67316 -[GCDWebServerConnection(Subclassing) processRequest:completion:] + 128

47 org.cocoapods.GCDWebServer 0x0000000106c6392e -[GCDWebServerConnection _startProcessingRequest] + 146

48 org.cocoapods.GCDWebServer 0x0000000106c64e13 __45-[GCDWebServerConnection _readRequestHeaders]_block_invoke + 1781

49 org.cocoapods.GCDWebServer 0x0000000106c65935 __64-[GCDWebServerConnection(Read) readHeaders:withCompletionBlock:]_block_invoke + 290

50 org.cocoapods.GCDWebServer 0x0000000106c65613 __68-[GCDWebServerConnection(Read) readData:withLength:completionBlock:]_block_invoke + 307

51 libdispatch.dylib 0x00007fff8e73c7a8 __dispatch_read_block_invoke_252 + 39

52 libdispatch.dylib 0x00007fff8e72a93d _dispatch_call_block_and_release + 12

53 libdispatch.dylib 0x00007fff8e71f40b _dispatch_client_callout + 8

54 libdispatch.dylib 0x00007fff8e72329b _dispatch_root_queue_drain + 1890

55 libdispatch.dylib 0x00007fff8e722b00 _dispatch_worker_thread3 + 91

56 libsystem_pthread.dylib 0x00007fff8ea934de _pthread_wqthread + 1129

57 libsystem_pthread.dylib 0x00007fff8ea91341 start_wqthread + 13

Code:

This is a code of a singletone that handles the web-server (GCDWebServer). It reads stored in memory data according to an id in an http query

private let queue = DispatchQueue(label: "gcdwebserver_queue")

private func setupServer(){

    webServer.delegate = self
    webServer.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self) { (req, completion) in

        if let resp = self.response(for: req) {
            return completion(resp)
        }
    }

    queue.async {
        self.webServer.start(withPort: 8521, bonjourName: "GCD Web Server")            
    }

}

And here is the code of a singleton that calls modbus (C Modbus library) connection (every 30 sec) to a list of devices and reads data:

private let modbusQueue = DispatchQueue(label: "modbus_queue")
private func initiateTimer() {
    polling()

    timer?.invalidate()
    if #available(OSX 10.12, *) {
        timer = Timer.scheduledTimer(withTimeInterval: pollingInterval, repeats: true) { (_) in
            self.polling()
        }
    } else {
        timer = Timer.scheduledTimer(timeInterval: pollingInterval, target: self, selector: #selector(polling), userInfo: nil, repeats: true)
    }
}

@objc private func polling() {

    for device in self.devices {

        if !device.isInProcess && device.isEnabled {
            self.modbusQueue.async {
                self.connectAndReadValues(device: device)
            }
        }

    }
}

private func connectAndReadValues(device: Device) {

    device.isInProcess = true

    let connect = device.modbus.connect()
    //handling connection status
    //...

    readValues(forDevice: device)
}

private func readValues(forDevice device: Device){
    //some constants

    do {

        let registers = try device.modbus.readRegisters(from: startAddress, count: registersCount)
        device.readState = .success
        device.modbus.close()
        //parse and save data to the app memory just as a dictionary. It saves only one small dictionary per device
        parseRegisters(controllerIP: device.modbus.host, vendor: vendor, registers: registers, startAdd: startAddress)
    } catch let error {
        //handling errors
    }

    //refreshing interface in the main queue
}

Upvotes: 1

Views: 564

Answers (1)

Rob
Rob

Reputation: 438122

“Dispatch Thread Soft Limit Reached: 64” would be a good place to start. It would appear that you are exhausting the pool of worker threads (there are only 64).

For example, if modbusQueue is a concurrent queue, the following could easily exhaust your worker threads:

for device in self.devices {
    if !device.isInProcess && device.isEnabled {
        self.modbusQueue.async {
            self.connectAndReadValues(device: device)
        }
    }
}

There are a few solutions:

  1. You could use an operation queue, and set its maxConcurrentOperationCount:

    let queue = OperationQueue()
    queue.name = "modbusQueue"
    queue.maxConcurrentOperationCount = 4
    
    for device in self.devices where !device.isInProcess && device.isEnabled {
        queue.addOperation {
            self.connectAndReadValues(device: device)
        }
    }
    
  2. If you want to stay in the dispatch queue world, you could use a semaphore with a non-zero starting value to constrain the degree of concurrency. The general pattern is:

    let semaphore = DispatchSemaphore(value: 4)
    for object in self.objects {
        semaphore.wait()                       // the first four will start immediately; the subsequent ones will wait for signals from prior ones
        queue.async {
            ...
            semaphore.signal()                 // signal that this is done
        }
    }
    

    So this will try to not do more than four at a time. And you would dispatch the whole thing to some background queue, so you don’t block the calling thread when you do this. Anyway, you end up with:

    DispatchQueue.global().async {
        let semaphore = DispatchSemaphore(value: 4)
        for device in self.devices where !device.isInProcess && device.isEnabled {
            semaphore.wait()
            self.modbusQueue.async {
                self.connectAndReadValues(device: device)
                semaphore.signal()
            }
        }
    }
    
  3. Or you could use concurrentPerform:

    DispatchQueue.global().async {
        DispatchQueue.concurrentPerform(iterations: devices.count) { index in
            let device = self.devices[index]
            if !device.isInProcess && device.isEnabled {
                self.connectAndReadValues(device: device)
            }
        }
    }
    

I’d probably lean towards the operation queue pattern, but those are a few options.


FWIW, I’d be wary about polling using a timer. You risk letting your queue get seriously backlogged. E.g., what if it’s not done processing the events by the time the timer fires again?

If you really must poll, I’d suggest removing the timer and just adding a barrier item to the end of your queue that triggers the next poll of the devices.

Upvotes: 1

Related Questions