Any idea how to fix group.leave() crash report error in dispatch group ('dispatch_group_leave$VARIANT$mp.cold.1 + 36')?

129 views Asked by At

My iOS app in Xcode calls the following method both on app launch and then every 10 minutes. Although I can't reproduce the crash, Crashlytics has it crashing regularly around 'group.leave()', or when each submission task is leaving the dispatch group. Any ideas what could cause it?

My thoughts:

  1. dispatch group enter/leaves are unbalanced. could be the result of completion closure returning multiple times, but I don' think that's happening
  2. race conditions - maybe other parts of the app are trying to access the same data causing invalidations etc. would using a dispatch semaphore help with this?

Below is the code and stack trace. TIA!

Crashing method:

class func checkForSubmissionsToUpload(onSuccess: @escaping () -> Void){
        let realm = Utils.getRealm()
        if let uploadSubmissions = realm?.objects(STLocalSubmission.self).filter("needsUpload = %@", true) {
            if uploadSubmissions.count == 0 {
                onSuccess()
            } else {
                
                let group = DispatchGroup()
               
                for submission in uploadSubmissions{
           
                    let isValidSubmission = !submission.isInvalidated
                    if isValidSubmission, let form = submission.formObject, let projectID = submission.projectID.value {
                        group.enter()
                        
                        submitSubmission(
                            submission,
                            form: form,
                            projectID: projectID,
                            draft: submission.draft,
                            completion: { (response) in
                                
                                DispatchQueue.main.async {
                                    SVProgressHUD.dismiss()
                                    group.leave()
                                    
                                    if let error = response?["error"] as? Bool, let message = APIManager.messageFrom(dictionary: response) {
                                        if error {
                                            print("error: \(message)")
                                            SVProgressHUD.showError(withStatus: message)//.showAlert(withTitle: "Error.", andMessage: message)
                                            
                                            return
                                        }
                                    }
                                    if (response == nil || response!.count <= 1) {
                                        if let extraErrors = response?["extraErrors"] as? [Any], let message = extraErrors.first as? String {
                                            print("error: \(message)")
                                            //                            self.showAlert(withTitle: "Error.", andMessage: message)
                                            SVProgressHUD.showError(withStatus: message)
                                            
                                            return
                                        }
                                    }
                                    
                                    
                                    if let submissionID = response?["submission_id"] as? Int ?? Int(submission.id) {
                                        let serverSubmission = STServerSubmission(id: submissionID,
                                                                                  submission: submission)
                                        
                                        // add the submission data to realm
                                        let realm = Utils.getRealm()
                                        try? realm?.write {
                                            
                                            realm?.delete(submission)
                                            realm?.add(serverSubmission, update: .all)
                                        }
                                    } else {
                                        
                                        print("error - no submission id returned after submitting form data")
                                    }
                                }
                            })
                    }
                }
                group.notify(queue: DispatchQueue.main) {
                    
                    onSuccess()
                }
            }
        }
    }

UPDATE:

Including 'submitSubmission', which then calls 'request':

class func submitSubmission(_ submission: STLocalSubmission, form: STForm, projectID: Int, draft: Bool, completion: @escaping ([AnyHashable : Any]?) -> Void) {
    
    var fields = [[String : Any?]]()
    var approvalUserID = [Int?]()
    
    if (form.isInvalidated || submission.isInvalidated) { return }
    submission.uploadAllImages() {
        response in
        
        if response == false {
            print("error submitting submission")
            
            var errors = [String : Any]()
            errors["error"] = true
            errors["message"] = "Error uploading images"
            completion(errors)
            return
        }
        
        for row in submission.dataRows {
            if (row.isInvalidated) { continue }
            let rowObject = form.rowByTag(row.intTag)
            
            if rowObject != nil {
                if !draft {
                    
                    // only perform data validation if it is not a draft
                    if rowObject?.required == true && row.serverValue == nil {
                        
                        print("error submitting submission - no row server value for required row \(rowObject?.tag ?? 0)")
                        
                        var errors = [String : Any]()
                        errors["error"] = true
                        
                        if let title = rowObject?.title,
                           let formID = submission.formID.value,
                           let formVersionId = submission.formVersionID.value {
                            errors["message"] = "\nIncomplete Form.\nThe following field is required: \n\n \(title) \n\nForm ID: \(formID) \nForm Version ID: \(formVersionId)"
                        }
                        completion(errors)
                        return
                    }
                }
                
                var field = [String : Any?]()
                field["id"] = row.intTag
                field["label"] = rowObject?.title
                
                print("row title = \(rowObject?.title) value = \(row.serverValue)")
                
                if rowObject?.multiple ?? true{
                    
                    if row.serverValue is [Any]{
                        field["value"] = row.serverValue
                    }else{
                        var array = [Any?]()
                        array.append(row.serverValue)
                        
                        field["value"] = array
                    }
                    
                }else{
                    field["value"] = row.serverValue
                }
                
                field["required"] = rowObject?.required
                
                if rowObject?.type == .register {
                    
                    var array = ""
                    if row.registerSignIns.count != 0 {
                        let selectedUsers = row.registerSignIns
                        for user in selectedUsers {
                            if user.id != "" {
                                if array == "" {
                                    array = "\(user.id)"
                                } else {
                                    array = array + ",\(user.id)"
                                }
                            }
                            
                        }
                        field["value"] = array
                    }
                }
                
                //check if array contains field for that id already and append the value part
                if rowObject?.multiple ?? true {
                    var existCount = 0
                    for index in 0..<fields.count {
                        
                        if fields[index]["id"] as? Int == row.intTag{
                            if (fields[index]["value"] as? [Any?]) != nil{
                                
                                fields[index]["value"] = row.serverValue
                            }
                        }else{
                            existCount += 1
                        }
                        
                    }
                    
                    if existCount == fields.count {
                        let jsonData = try? JSONSerialization.data(withJSONObject: field, options: [])
                        let jsonString = String(data: jsonData!, encoding: .utf8)
                        print(jsonString ?? 0)
                        fields.append(field)
                    }
                } else{
                    fields.append(field)
                }
            }
        }
        
        for approversID in submission.selectedUsers {
            approvalUserID.append(approversID.id)
        }
        
        
        let parameters: [String : Any] = [ "fields" : fields,
                                           "project_id" : projectID,
                                           "draft" : draft,
                                           "approval_users" : approvalUserID]
        
        var stringId: String? = nil
        
        if submission.isNewForm, let id = submission.formVersionID.value {
            stringId = String(id)
        }
        else {
            stringId = submission.id
        }
        guard let unwrappedID = stringId else {
            print("error - submission has no id!")
            return
        }
        
        let target = submission.isNewForm ? "forms" : "submissions"
        APIManager.request(to: "api/\(target)/\(unwrappedID)/submit",
                           parameters: parameters,
                           needsAuthToken: true,
                           post: true,
                           completion: completion)
    }
}


private class func request(to path: String,
                           parameters: [String: Any],
                           needsAuthToken: Bool, post: Bool,
                           completion: @escaping ([AnyHashable : Any]?) -> Void) {
    
    // always set the apitoken in the URL, whether it's a GET or POST request
    var getParameters: [String : Any]?
    var postParameters: [String : Any]?
    
    if post {
        getParameters = [String : Any]()
        postParameters = parameters
    } else {
        getParameters = parameters
    }
    
    if needsAuthToken {
        guard let authToken = FXKeychain.default().object(forKey: "authToken") as? String else {
            print("error with request to path: \(path) - no auth token")
            FXKeychain.default().removeObject(forKey: "loggedIn")
            let storyBoard = UIStoryboard(name: "Install+Login", bundle: nil)
            var viewController: UIViewController? = nil
            if UserDefaults.standard.object(forKey: "installCode") == nil {
                viewController = storyBoard.instantiateViewController(withIdentifier: "InstallCodeVC")
            } else {
                viewController = storyBoard.instantiateViewController(withIdentifier: "LoginVC")
            }
            UIApplication.shared.delegate?.window??.rootViewController = viewController
            UIApplication.shared.delegate?.window??.makeKeyAndVisible()
            return
        }
        getParameters!["api_token"] = authToken
        // print("Auth Token \(authToken)")
    } else {
        getParameters!["api_token"] = apiToken
    }
    
    // add code to completion closure. log out when returns unauthorised
    let finalCompletion: ([AnyHashable : Any]?) -> (Void) = {
        response in

        DispatchQueue.main.async {
            if let extraErrors = response?["extraErrors"] as? [Any] {
                for object in extraErrors {
                    if let message = object as? String {
                        if message.contains("HTTP Status Code: 401") {
                            // log user out
                            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                            FXKeychain.default().removeObject(forKey: "authToken")
                            FXKeychain.default().removeObject(forKey: "userID")
                            let storyBoard = UIStoryboard(name: "Login", bundle: nil)
                            var viewController: UIViewController? = nil
                            viewController = storyBoard.instantiateViewController(withIdentifier: "loginVC")
                            UIApplication.shared.delegate?.window??.rootViewController = viewController
                            UIApplication.shared.delegate?.window??.makeKeyAndVisible()
                            return
                        }
                    }
                }
            }
            completion(response)
        }
    }
    
    print("API Request = \(path) - Params = \(getParameters)")
    

    if path == "check-install-code" {
        STNetworkManager.request(toURL: baseURLString + path, withGetParameters: getParameters, postParameters: postParameters, completion: finalCompletion)
    } else {
        STNetworkManager.request(toURL: ((UserDefaults.standard.object(forKey: "baseUrl") as? String ?? nil) ?? baseURLString) + path, withGetParameters: getParameters, postParameters: postParameters, completion: finalCompletion)
    }
}

Crashlytics stack trace:

Crashed: com.apple.main-thread
0  libdispatch.dylib              0x689fc dispatch_group_leave$VARIANT$mp.cold.1 + 36
1  libdispatch.dylib              0x4d5c _dispatch_group_wake + 138
2  SitePlex                       0xfc3f8 closure #1 in closure #1 in static APIManager.checkForSubmissionsToUpload(onSuccess:) + 1683 (APIManager.swift:1683)
3  SitePlex                       0xf1a8c thunk for @escaping @callee_guaranteed () -> () + 4299774604 (<compiler-generated>:4299774604)
4  libdispatch.dylib              0x637a8 _dispatch_call_block_and_release + 24
5  libdispatch.dylib              0x64780 _dispatch_client_callout + 16
6  libdispatch.dylib              0x10bdc _dispatch_main_queue_drain + 928
7  libdispatch.dylib              0x1082c _dispatch_main_queue_callback_4CF$VARIANT$mp + 36
8  CoreFoundation                 0x91a2c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
9  CoreFoundation                 0x756c8 __CFRunLoopRun + 2080
10 CoreFoundation                 0x79da0 CFRunLoopRunSpecific + 584
11 GraphicsServices               0x1998 GSEventRunModal + 160
12 UIKitCore                      0x37180c -[UIApplication _run] + 868
13 UIKitCore                      0x371484 UIApplicationMain + 312
14 SitePlex                       0x880c main + 15 (AppDelegate.swift:15)
15 ???                            0x1d2f90344 (Missing)

com.apple.uikit.eventfetch-thread
0  libsystem_kernel.dylib         0x1030 mach_msg2_trap + 8
1  libsystem_kernel.dylib         0x12b18 mach_msg2_internal + 76
2  libsystem_kernel.dylib         0x12db8 mach_msg_overwrite + 484
3  libsystem_kernel.dylib         0x1524 mach_msg + 20
4  CoreFoundation                 0x741c8 __CFRunLoopServiceMachPort + 156
5  CoreFoundation                 0x75360 __CFRunLoopRun + 1208
6  CoreFoundation                 0x79da0 CFRunLoopRunSpecific + 584
7  Foundation                     0x3e138 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 208
8  Foundation                     0x3e034 -[NSRunLoop(NSRunLoop) runUntilDate:] + 60
9  UIKitCore                      0x493cd8 -[UIEventFetcher threadMain] + 404
10 Foundation                     0x55c9c __NSThread__start__ + 704
11 libsystem_pthread.dylib        0x30ec _pthread_start + 116
12 libsystem_pthread.dylib        0x172c thread_start + 8

Realm notification listener
0  libsystem_kernel.dylib         0x2800 kevent + 8
1  Realm                          0x39c6d8 realm::_impl::ExternalCommitHelper::listen() + 156
2  Realm                          0x39c828 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0>>(void*) + 52
3  libsystem_pthread.dylib        0x30ec _pthread_start + 116
4  libsystem_pthread.dylib        0x172c thread_start + 8

com.google.firebase.crashlytics.MachExceptionServer
0  FirebaseCrashlytics            0x1a4e4 FIRCLSProcessRecordAllThreads + 184
1  FirebaseCrashlytics            0x1a8c4 FIRCLSProcessRecordAllThreads + 1176
2  FirebaseCrashlytics            0x14250 FIRCLSHandler + 48
3  FirebaseCrashlytics            0x16bc8 FIRCLSMachExceptionServer + 1248
4  libsystem_pthread.dylib        0x30ec _pthread_start + 116
5  libsystem_pthread.dylib        0x172c thread_start + 8

com.apple.CoreMotion.MotionThread
0  libsystem_kernel.dylib         0x1030 mach_msg2_trap + 8
1  libsystem_kernel.dylib         0x12b18 mach_msg2_internal + 76
2  libsystem_kernel.dylib         0x12db8 mach_msg_overwrite + 484
3  libsystem_kernel.dylib         0x1524 mach_msg + 20
4  CoreFoundation                 0x741c8 __CFRunLoopServiceMachPort + 156
5  CoreFoundation                 0x75360 __CFRunLoopRun + 1208
6  CoreFoundation                 0x79da0 CFRunLoopRunSpecific + 584
7  CoreFoundation                 0xb8c58 CFRunLoopRun + 60
8  CoreMotion                     0x12fac CLMotionActivity::isTypeInVehicle(CLMotionActivity::Type) + 23288
9  libsystem_pthread.dylib        0x30ec _pthread_start + 116
10 libsystem_pthread.dylib        0x172c thread_start + 8

com.apple.NSURLConnectionLoader
0  libsystem_kernel.dylib         0x1030 mach_msg2_trap + 8
1  libsystem_kernel.dylib         0x12b18 mach_msg2_internal + 76
2  libsystem_kernel.dylib         0x12db8 mach_msg_overwrite + 484
3  libsystem_kernel.dylib         0x1524 mach_msg + 20
4  CoreFoundation                 0x741c8 __CFRunLoopServiceMachPort + 156
5  CoreFoundation                 0x75360 __CFRunLoopRun + 1208
6  CoreFoundation                 0x79da0 CFRunLoopRunSpecific + 584
7  CFNetwork                      0x228520 _CFURLStorageSessionDisableCache + 50860
8  Foundation                     0x55c9c __NSThread__start__ + 704
9  libsystem_pthread.dylib        0x30ec _pthread_start + 116
10 libsystem_pthread.dylib        0x172c thread_start + 8

Thread
0  libsystem_kernel.dylib         0x14f0 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x1c1c _pthread_wqthread + 360
2  libsystem_pthread.dylib        0x1720 start_wqthread + 8

Thread
0  libsystem_kernel.dylib         0x14f0 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x1c1c _pthread_wqthread + 360
2  libsystem_pthread.dylib        0x1720 start_wqthread + 8

Thread
0  libsystem_kernel.dylib         0x14f0 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x1c1c _pthread_wqthread + 360
2  libsystem_pthread.dylib        0x1720 start_wqthread + 8

Thread
0  libsystem_pthread.dylib        0x1718 start_wqthread + 270

Thread
0  libsystem_kernel.dylib         0x14f0 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x1c1c _pthread_wqthread + 360
2  libsystem_pthread.dylib        0x1720 start_wqthread + 8
0

There are 0 answers