【问题标题】:OS X Crash: pointer being freed was not allocatedOS X 崩溃:未分配被释放的指针
【发布时间】:2019-05-11 12:30:34
【问题描述】:

需要帮助以了解崩溃的真正原因。我已经制作了应该 24/7 全天候运行的应用程序。它从设备读取并准备一些数据,并创建一个 Web 服务器以将数据发送到探测器。 崩溃是在不同的时间后发生的,所以我无法在模拟器中真正重现它。

这是最后一个崩溃文件的详细信息:

自启动以来的唤醒时间:110000 秒

系统完整性保护:启用

崩溃的线程:37 调度队列:com.apple.root.default-qos

异常类型:EXC_CRASH (SIGABRT)

异常代码:0x0000000000000000, 0x0000000000000000

异常说明:EXC_CORPSE_NOTIFY

已达到调度线程软限制:64(同步操作中阻塞的调度线程过多)

应用特定信息: abort() 调用

*** 对象 0x247032000 的错误:未分配被释放的指针

线程 37 的详细信息:

线程 37 崩溃::调度队列:com.apple.root.default-qos

0 libsystem_c.dylib 0x00007fff8dab3298 usleep$NOCANCEL + 0

1 libsystem_c.dylib 0x00007fff8dae16e9 中止 + 139

2 libsystem_malloc.dylib 0x00007fff965fe041 免费 + 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

代码:

这是处理 Web 服务器 (GCDWebServer) 的单音代码。它根据http查询中的id读取存储在内存中的数据

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")            
    }

}

这是一个调用 modbus(C Modbus 库)连接(每 30 秒)到设备列表并读取数据的单例代码:

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
}

【问题讨论】:

  • 这只是一个崩溃。是什么原因造成的?什么代码?除非您可以创建展示此行为的最小测试用例,否则我们无法帮助您。如果你能做到,那么你应该提交一份错误报告。
  • @tadman,这就是问题所在。我不知道是什么代码造成的。该应用程序不会立即崩溃,它可以运行几个小时,而这个崩溃日志是我现在唯一的东西。
  • 你有日志和代码。我们只有日志。仅此而已,真的无法确定这里发生了什么。我会看一下“同步操作中阻塞的调度线程太多”的措辞,看看是否有很多线程最终由于某种原因被阻塞。
  • @tadman,添加了一些可能导致崩溃的代码
  • 您可能希望记录您发起的这些请求中有多少以及完成了多少。如果是频率问题,请设置一个测试模式,使其运行速度比现实生活中快 10 倍或 100 倍,以便更快地出现这些问题。

标签: swift macos


【解决方案1】:

“已达到调度线程软限制:64”将是一个不错的起点。看来您正在耗尽工作线程池(只有 64 个)。

例如,如果modbusQueue 是一个并发队列,则以下内容很容易耗尽您的工作线程:

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

有几个解决方案:

  1. 你可以使用一个操作队列,并设置它的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. 如果您想留在调度队列世界中,您可以使用具有非零起始值的信号量来限制并发程度。一般模式是:

    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
        }
    }
    

    因此,这将尝试一次不超过四个。而且你会将整个事情分派到某个后台队列,所以当你这样做时你不会阻塞调用线程。无论如何,你最终得到:

    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. 或者你可以使用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)
            }
        }
    }
    

我可能倾向于操作队列模式,但这些只是几个选项。


FWIW,我对使用计时器进行轮询持谨慎态度。您冒着让您的队列严重积压的风险。例如,如果在计时器再次触发时它还没有处理完事件怎么办?

如果您确实必须进行轮询,我建议您移除计时器并在队列末尾添加一个屏障项,以触发下一次设备轮询。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-30
    • 2016-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 2019-03-13
    相关资源
    最近更新 更多