Неочевидный cra sh из-за несбалансированного звонка в группу отправки - PullRequest
0 голосов
/ 16 января 2020

Я получил отчет cra sh BUG IN CLIENT OF LIBDISPATCH: Unbalanced call to dispatch_group_leave().

Анонимный код, на который указывает трассировка стека, выглядит следующим образом:

 func updateHealthKitData(from startDate: Date, to endDate: Date, completion: @escaping () -> ()) {
     let group = DispatchGroup()
     group.enter()
     self.hkDataStore.getData1(startDate: startDate, endDate: endDate, completion: {(data1) in
         if let samplesToProcess = data1 {
             self.processData1(data: samplesToProcess, completion: {() in
                 group.leave()
             })
         } else {
             group.leave()
         }
     })
     group.enter()
     self.hkDataStore.getData2(startDate: startDate, completion: {(data2) in
         if let samplesToProcess = data2 {
             self.processData2(data: samplesToProcess, completion: {() in
                 group.leave()
             })
         } else {
             group.leave()
         }
     })

     group.notify(queue: .main) {
         completion()
     }
 }

Исключение пришло из очереди com.apple.HealthKit.HKHealthStore.client. Задачи getData2 и getData1 запрашивают аптечку в фоновой очереди.

Я прочитал, что группа отправки достаточно поточнобезопасна, т.е. ее можно использовать из других очередей без synchronize. Групповая отправка входит дважды перед выполнением задач, и только после этого она ожидает баланса, чтобы уведомить, когда задачи будут выполнены. Я дважды проверил, что любые дополнения (и вложенные) не вызываются дважды, что может привести к дополнительному вызову в группу рассылки.

Что здесь может быть не так? Заранее спасибо за любую помощь и мысли!

1 Ответ

0 голосов
/ 17 января 2020

Во-первых, вы правы, DispatchGroup действительно поточно-ориентирован. Это не проблема.

Вы получаете эту ошибку «Несбалансированный вызов», если вы leave больше раз, чем вы enter.

Итак, одна или несколько из этих четырех подпрограмм, getData1, processData1, getData2 или processData2 должны вызывать соответствующий обработчик завершения более одного раза. В этом сценарии для одного звонка enter у вас есть более одного звонка leave.

Нам понадобятся эти подпрограммы getData и processData для дальнейшей диагностики, но я подозреваю, что Вы сможете найти его.


Вы сказали:

Я дважды проверил, что любые дополнения (и вложенные) не вызываются дважды, что может привести к дополнительный звонок в группу отправки.

Тем не менее, это должно быть проблемой. Я бы предложил проверить это эмпирически, возможно, временно добавив операторы журналирования и, возможно, логический тест, например:

func updateHealthKitData(from startDate: Date, to endDate: Date, completion: @escaping () -> ()) {
    let group = DispatchGroup()
    group.enter()
    var hasRunAlready1 = false
    self.hkDataStore.getData1(startDate: startDate, endDate: endDate, completion: {(data1) in
        if let samplesToProcess = data1 {
            self.processData1(data: samplesToProcess, completion: {() in
                if hasRunAlready1 { fatalError("Has run 1 already - a") }
                hasRunAlready1 = true
                print("1a")
                group.leave()
            })
        } else {
            if hasRunAlready1 { fatalError("Has run 1 already - b") }
            hasRunAlready1 = true
            print("1b")
            group.leave()
        }
    })

    group.enter()
    var hasRunAlready2 = false
    self.hkDataStore.getData2(startDate: startDate, completion: {(data2) in
        if let samplesToProcess = data2 {
            self.processData2(data: samplesToProcess, completion: {() in
                if hasRunAlready2 { fatalError("Has run 2 already - a") }
                hasRunAlready2 = true
                print("2a")
                group.leave()
            })
        } else {
            if hasRunAlready2 { fatalError("Has run 2 already - b") }
            hasRunAlready2 = true
            print("2b")
            group.leave()
        }
    })

    group.notify(queue: .main) {
        completion()
    }
}
...