realm-swift: Deadlock on beginWriteTransaction

Goals

Begin a write transaction

Expected Results

The transaction is started

Actual Results

Deadlock (not every time, but at the same place)

screen shot 2017-04-25 at 01 12 55 screen shot 2017-04-25 at 01 14 14

Steps to Reproduce

Code Sample

[[RLMRealm defaultRealm] transactionWithBlock:block error:nil];

The block itself contains a change of single property.

Version of Realm and Tooling

ProductName:	Mac OS X
ProductVersion:	10.12.4
BuildVersion:	16E195

/Applications/Xcode.app/Contents/Developer
Xcode 8.3.2
Build version 8E2002

/usr/local/bin/pod
1.2.1
Realm (2.6.2)

/bin/bash
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)

/usr/local/bin/carthage
0.16.2
(not in use here)

/usr/local/bin/git
git version 2.10.1

Unfortunately it is close to impossible to send a sample project, but if this might help, I am available for any tests/debugs to do

Might be similar to https://github.com/realm/realm-cocoa/issues/4428

About this issue

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

Most upvoted comments

I have reproduced this deadlock in a sample project, please check it out. Happens even without notifications, just adds and deletes in main and background threads. It easily reproduces in my real project, which is about to be released. There is a way to avoid deadlock by moving all work with realm to main thread, but that’s not a good way. Hope to hear from you soon.

https://github.com/antonkiselev/realm-2.8-deadlock

@bdash I’ve emailed a reproducible scenario. Hope it helps!

We have had this issue for a while and have tried different approaches to solve it.

The TL;DR, is that we think we have found a way to circumvent it. The root cause for the deadlock should still be identified and dealt with inside Realm.

We would have Realm randomly lock up on RealmCoordinator::wait_for_notifiers on either beginWriteTransaction or refresh if there was a Realm notification listener connected to any objects involved in those operations.

First we mitigated the deadlock on beginWriteTransaction by dispatching write transactions to a background queue. We did use an autoreleasepool inside the background work as suggested by the docs. Then after the transaction had finished we would dispatch back to the main queue in case we needed to process any results from the transaction.

Back on the main dispatch queue we would often need to refresh to see the results of the transaction. This is backed up by the following

From https://github.com/realm/realm-cocoa/issues/4837

[…]when dispatch_async’ing to the main thread immediately after a write transaction it’s possible for the async block to be dequeued and invoked before Realm’s commit notification makes it to the main thread. In this case you may see the Realm as not having refreshed to the latest version, and calling Realm.refresh() is the correct way to deal with that.

This suggest that the auto refresh feature is not synced with the main queue. This could actually be the case. This guy ran some tests https://blackpixel.com/writing/2013/11/performselectoronmainthread-vs-dispatch-async.html and also https://stackoverflow.com/questions/10440412/perform-on-next-run-loop-whats-wrong-with-gcd

At this point refresh would still cause a deadlock with random intervals

This lead us to try to see if we could start execution on the main thread after we knew for sure the Realm had been auto refreshed.

Instead of going with dispatch_async to the main queue we tried using the performSelector family of methods, which are guaranteed to run on the next run loop cycle. So far it’s working for us.

This should also mitigate the need to explicitly refresh as auto refresh should have run at this time.

Sounds good and no problem. I’m glad to help. The way our object hierarchy is setup has one more level of objects. I recommend doing something like: Person has many dogs, dog has many bones. Dog has one owner. Bone’s don’t know about dogs.

(Fwiw, our setup is: Reading has many Recordings. A Recording has many Measurements. A Recording knows its parentReading. Measurements do not know which Recording they are in)

Additionally, FirstVC could be UITableViewController subclass and the notification should watch the first dog’s bones increment in size (count). That’s all we’re really doing with our TVC that watches a list of recordings and updates the label when measurements get added.

It’s worth noting that our code is using a collection (vs. results) based addNotificationBlock.

        _realmToken = [collection addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
            if (error) {
                NSLog(@"Failed to open Realm on background worker: %@", error);
                return;
            }
            
            [weakSelf collectionChangedWithResults:results change:change];
        }];

and then simply…

-(void)collectionChangedWithResults:(RLMResults*)results change:(RLMCollectionChange*)change {
    UITableView *tv = self.tableView;
    [tv beginUpdates];
    [tv deleteRowsAtIndexPaths:[change deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic];
    [tv insertRowsAtIndexPaths:[change insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic];
    [tv reloadRowsAtIndexPaths:[change modificationsInSection:0] withRowAnimation:UITableViewRowAnimationNone];
    [tv endUpdates];
}

There’s no need to increment the schema version when adding linking object properties as those don’t affect the representation of the data in the file in any way. LinkingObjects properties are essentially “labels” that name the inverse relationship so that you can query them at runtime.

@austinzheng I’ve sent email few minutes ago. Thanks for supporting community with nice gifts. Waiting for a bug fix in upcoming release 😃

Hi @antonkiselev. Thanks again for providing a working reproduction case. We’re trialling a new program: to express our appreciation for your time and effort, if you’d like a free Realm T-shirt please email your address and T-shirt size to help@realm.io, then post back here so we know it’s you.

We experience a temporary deadlock problem using Realm 2.6.1 (lock for about 13 seconds). There is a workaround by removing all addNotificationBlock methods. What I wanted to add is that when we upgrade from realm 2.6.1 to >= 2.6.2 we experience a permanent deadlock and can’t get out of this state.

Hi @o15a3d4l11s2 and @kevincador!

A sample app to reproduce this issue would be really valuable to us. Would you be able to isolate your Realm models to show us howe you’ve set up those circular links?

Thanks!