-
Type: Bug
-
Resolution: Fixed
-
Priority: Critical - P2
-
None
-
Affects Version/s: None
-
Component/s: None
-
SDK FY21-Q3, SDK FY21-Q3.
-
6577
Goals
<!--- What do you want to achieve? -->
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
<!--- What did you expect to happen? -->
No crash, as in Realm 4.4.0.
Actual Results
<!--- What happened instead?
e.g. the stack trace of a crash
-->
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
<!--- What are steps we can follow to reproduce this issue? -->
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
<!---
Provide a code sample or test case that highlights the issue.
If relevant, include your model definitions.
For larger code samples, links to external gists/repositories are preferred.
Alternatively share confidentially via mail to help@realm.io.
Full Xcode projects that we can compile ourselves are ideal!
-->
// 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
<!---
In the CONTRIBUTING guidelines, you will find a script,
which will help determining some of these versions.
-->
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