swift 'RUNNINGBOARD 0xdead10cc' error during Realm database writes when app is in background

315 Views Asked by At

I'm getting a "RUNNINGBOARD 0xdead10cc" error intermittently when my app is in the background and I'm using Realm database. I suspect the error is related to file locking during database writes. I have tried adding sleep to the write block, but the error has not occurred during testing.

Here's a code snippet of the function I'm using to update my Realm database:

func update(completion: @escaping () -> ()) {
    let dispatchGroup = DispatchGroup()
    array1 = Array(Set(self.arrayData))
    for element in array1 {
        dispatchGroup.enter()
        element.updateRealmModel {
                dispatchGroup.leave()
        }
    }
    dispatchGroup.notify(queue: .main) {
        completion()
    }
    
}

and the element function:

func updateRealmModel(completion: @escaping(() -> Void)) {
    let primaryKey = self.id
    DispatchQueue.global(qos: .userInitiated).async {
        let realm = try! Realm()
        if let user = realm.object(ofType: Mytype, forPrimaryKey: primaryKey) {
            do {
                try realm.write {
                    user.boolField = false
                }
            } catch let error {
                debugPrint(error)
            }
        }
        completion()

    }

}

Can anyone help me reproduce and resolve this error? Are there any known issues related to Realm database writes when an app is in the background? I would appreciate any insights or suggestions on how to debug and fix this issue.

2

There are 2 best solutions below

0
Jay On

I don't believe the RUNNINGBOARD error is directly related to Realm in this use case.

It doesn't appear those DispatchQueues are needed and in some cases, can be incredibly hard to manage - along with the completion handler, whoaaa.

Since the primary key of each user to be updated is known, there's no reason to query for the user - you already know who they are by their primary key.

It appears to be a small amount of data and if he array is reasonably sized, my first suggestion is to simply update each user object directly within the loop via their primary key.

array1 = Array(Set(self.arrayData))
for element in array1 {
   let _id = element.id
   try! realm.write {
      realm.create(UserClass, value: ["_id": _id, "boolField": false], update: .modified)
   }
}

The above will update the boolField property to false on each user with a primary key found in array1, and not modify any other properties.

That being said, if it's a large loop, that can tie up the UI so you could also dump the whole process onto a single background thread

DispatchQueue(label: "background").async {
    autoreleasepool {
       //write the data

which is a technique we use when the dataset is huge.

Another option is to leverage the writeAsync function to update objects asynchronously

A nice explanation and example code can be found here Update Objects Asynchronously

0
Rob On

Apple’s Understanding the exception types in a crash report describes the 0xdead10cc as follows:

  • 0xdead10cc (pronounced “dead lock”). The operating system terminated the app because it held on to a file lock or SQLite database lock during suspension. Request additional background execution time on the main thread with beginBackgroundTask(withName:expirationHandler:). Make this request well before starting to write to the file in order to complete those operations and relinquish the lock before the app suspends. In an app extension, use beginActivity(options:reason:) to manage this work.

See Extending your app’s background execution time for guidance on how to keep your app running in the background for a short period (30s, last time I checked) after the user leaves the app.

For example, you could call your existing update method, and use its completion handler to determine when to call endBackgroundTask(_:). Perhaps:

func updateAndContinueInBackground() {
    var identifier: UIBackgroundTaskIdentifier = .invalid
    identifier = UIApplication.shared.beginBackgroundTask(withName: Bundle.main.bundleIdentifier! + ".realm.updates") {
        if identifier != .invalid {
            UIApplication.shared.endBackgroundTask(identifier)
            identifier = .invalid
        }
    }

    update {
        if identifier != .invalid {
            UIApplication.shared.endBackgroundTask(identifier)
            identifier = .invalid
        }
    }
}

This is the bare minimum (informing the OS that you want a little more time with beginBackgroundTask(withName:expirationHandler:); telling it when you are done with endBackgroundTask(_:).


Frankly, while it might be a bit of an edge-case scenario, you probably want to handle the timeout scenario, where that expirationHandler might get called. If the OS calls that closure because the app has run out of time, you might want to cancel/rollback updates that are still in progress and did not complete in time. Maybe you should use the id returned by writeAsync to cancel just those which have not yet completed. Or perhaps wrap the updates in a transaction (with beginWrite followed by either commitWrite or cancelWrite). There are a number of cancelation patterns that Realm offers.