realm-swift: Intermittent crash while running Realm in a background thread after upgrading to 5.0.3

Goals

Run Realm write code that runs in a background queue without crashing, as was possible in Realm 4.4.0. After upgrading to Realm 5.0.3, the sample code below crashes intermittently, but frequently enough to become a problem.

In our project, we have a Realm extension that implements several helper methods, one of which closely resembles the code sample found in this Realm documentation: https://realm.io/docs/cookbook/swift/object-to-background/

I’m not sure if it’s related, but after coming across #6555, #6559, #6574, I have attempted implementing the fix from #6576, but the same crash still occurs. EXC_BAD_ACCESS KERN_INVALID_ADDRESS

Expected Results

No crash, as in Realm 4.4.0.

Actual Results

Crashed: job_progress_queue
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x00000003d7fd8a7c
0  Realm                          0x1064071f0 realm::Array::init_from_mem(realm::MemRef) + 12
1  Realm                          0x1064a8294 realm::Group::attach(unsigned long, bool, bool) + 196
2  Realm                          0x1064af054 realm::Group::advance_transact(unsigned long, unsigned long, realm::_impl::NoCopyInputStream&, bool) + 316
3  Realm                          0x10635a9dc void realm::Transaction::rollback_and_continue_as_read<realm::_impl::NullInstructionObserver>(realm::_impl::NullInstructionObserver*) + 588
4  Realm                          0x10635a030 realm::Transaction::rollback_and_continue_as_read() + 28
5  Realm                          0x106359e8c realm::_impl::transaction::cancel(realm::Transaction&, realm::BindingContext*) + 140
6  Realm                          0x1062b3c5c realm::Realm::cancel_transaction() + 264
7  Realm                          0x1061fedd0 -[RLMRealm cancelWriteTransaction] + 36
8  Realm                          0x1061ff3c0 -[RLMRealm dealloc] + 80
9  libobjc.A.dylib                0x1bc9d1358 AutoreleasePoolPage::releaseUntil(objc_object**) + 184
10 libobjc.A.dylib                0x1bc9d1244 objc_autoreleasePoolPop + 232
11 libswiftObjectiveC.dylib       0x1f37ccc2c autoreleasepool<A>(invoking:) + 76
12 MyJobApp                       0x101ad1b98 closure #1 in static Realm.runAsync(in:errorHandler:block:) + 15 (Realm+Helpers.swift:15)
13 MyJobApp                       0x100f97360 thunk for @escaping @callee_guaranteed () -> () + 56 (<compiler-generated>:56)
14 libdispatch.dylib              0x1bc93eec4 _dispatch_call_block_and_release + 32
15 libdispatch.dylib              0x1bc94033c _dispatch_client_callout + 20
16 libdispatch.dylib              0x1bc94685c _dispatch_lane_serial_drain + 568
17 libdispatch.dylib              0x1bc947290 _dispatch_lane_invoke + 400
18 libdispatch.dylib              0x1bc950928 _dispatch_workloop_worker_thread + 584
19 libsystem_pthread.dylib        0x1bc9a7714 _pthread_wqthread + 276
20 libsystem_pthread.dylib        0x1bc9ad9c8 start_wqthread + 8

Steps to Reproduce

See code sample below, but this is basically an intermittent but fairly frequent crash. In our code, we instantiate a Job object multiple times, as needed, to perform different tasks that we need completed. For the purposes of the code sample below, assume code similar to the following is run multiple times:

let job: Job = Job()
job.start()

Code Sample

// Job.swift
public class Job: Object {
    
    @objc public dynamic var id: String = UUID().uuidString
    @objc public dynamic var progress: Double = 0.0
    
    public let tasks: List<Task> = .init()
    @objc public dynamic var runningTask: Task?
    
    public override static func primaryKey() -> String? {
        return "id"
    }
    
    private let jobProgressQueue: DispatchQueue = DispatchQueue(
        label: "job_progress_queue",
        qos: .utility,
        attributes: [],
        autoreleaseFrequency: .inherit,
        target: nil)
    
    public func start() {
        let primaryKey: String = self.id
        let jobProgressQueue: DispatchQueue = self.jobProgressQueue
        //
        // The specifics of `runningTask` are not important, other than the fact that
        // the `progressHandler` block is called frequently to update the caller on
        // its progress from another DispatchQueue.
        //
        runningTask.run(progressHandler: { (progress: Double) in
            Job.update(primaryKey, progress: progress, in: jobProgressQueue)
        })
    }
    
    public static func update(_ primaryKey: String, progress: Double, in dispatchQueue: DispatchQueue) {
        Realm.runAsync(in: dispatchQueue) { (realm: Realm) in
            guard let job: Job = realm.object(ofType: Job.self, forPrimaryKey: primaryKey) else { return }
            try? realm.write {
                guard !job.isInvalidated else { return }
                job.progress = progress
            }
        }
    }
    
}
// Realm+Helpers.swift
extension Realm {
    
    public static func runAsync(in dispatchQueue: DispatchQueue, errorHandler: @escaping (_ error: Swift.Error) -> Void = { _ in return }, block: @escaping (Realm) -> Void) {
        dispatchQueue.async {
            autoreleasepool { // <--- Thread 49: EXC_BAD_ACCESS (code=1, address=0x3d7fd8a7c)
                do {
                    let realm: Realm = try Realm()
                    block(realm)
                } catch {
                    errorHandler(error)
                }
            }
        }
    }
    
}

Version of Realm and Tooling

Realm framework version: 5.0.3

Realm Object Server version: N/A

Xcode version: 11.5 (11E608c)

iOS version: 13.5 (17F75)

Dependency manager + version: CocoaPods 1.9.3

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 5
  • Comments: 18 (9 by maintainers)

Most upvoted comments

@goa sorry to hear this and thank you for getting back to us. Realm Core team is prioritizing working on v5.x crashes right now.

@marinofaggiana This looks like a separate issue, please open a new issue and fill out the template accordingly. Thanks!