Search code examples
androidrealm

Will these two Realm transactions interfere with eachother


I am assuming that two Realm transactions will NOT interfere with each-other as that's what transactions are for right? But given that I am not a database/Realm expert I need confirmation on this so I can get on with other parts of my project asap.

I am making a background up-loader for failed HTTP requests. Each request has an class in Realm who's objects are a failed request of that type. There is also an class called 'RequestUploadStatus' which has a field called 'needsUpload' who's objects maintain whether a given request class has objects that need to be uploaded.

e.g.

Comments
12, "Hello"
45, "Nice blouse"

Images
None

The RequestUploadStatus
[CommentClassId], true
[ImageClassId]. false

I'm not sure if this is the best way to do this yet, but for now let's assume it is.

So, what I want to avoid (given that that there are multiple threads) is having the RequestUploadStatus for one of the request classes having the wrong 'needsUpload' value, e.g. true when there are no objects to upload or false when there are objects to upload. So more specifically given the following code: if the upload transaction is looping through the RealmResults is the schedule transaction blocked from adding new objects to upload and setting the 'needsUpload' for that request class.

Schedule transaction

                    realm.executeTransaction(new Realm.Transaction() {
                        @Override
                        public void execute(Realm realm) {

                            EntityUploadStatus entityUploadStatus = realm.where(EntityUploadStatus.class).equalTo("entityId", entityClassIdMap.entityId).findFirst();
                            entityUploadStatus.uploadNeeded = true;

                            //a comment or image or whatever
                            realm.insertOrUpdate(entity);

                        }
                    });

Upload transaction

                          realmInstance.executeTransaction(new Realm.Transaction() {

                            @Override
                            public void execute(Realm realm) {

                                RealmResults<RealmObject> realmObjects = realmInstance.where(realmClass).findAll();

                                for(int i = 0; i < realmObjects.size(); i++) {

                                    RealmObject realmObject = realmObjects.get(i);

                                    Boolean success = uploadObject(realmObject, classToUpload.entityId);

                                    if(success)
                                    {

                                        realmObject.deleteFromRealm();

                                        if (i == realmObjects.size())
                                        {
                                            //last one successfully uploaded, set status to uploaded
                                            EntityUploadStatus entityUploadStatus = realm.where(EntityUploadStatus.class).equalTo("entityId", entityClassIdMap.entityId).findFirst();
                                            entityUploadStatus.uploadNeeded = false;

                                        }
                                    }
                                    else
                                        break;


                                }

                            }
                        });

Code is not tested and probably wouldn't even compile but you get the idea I'm sure.


Solution

  • This answer applies to any version below 0.88.3 and above 3.0.0.

    Your current proposition has one major problem:

    realmInstance.executeTransaction(new Realm.Transaction() {
      @Override
      public void execute(Realm realm) {
          RealmResults<RealmObject> realmObjects = realmInstance.where(realmClass).findAll();
          for(int i = 0; i < realmObjects.size(); i++) { // <-- !!!
              RealmObject realmObject = realmObjects.get(i);
              Boolean success = uploadObject(realmObject, classToUpload.entityId);
              if(success) {
                  realmObject.deleteFromRealm(); // <-- !!!
    

    In transactions, the RealmResults you see are always the latest possible version, including any change you do that would modify the elements of the result set.

    In this case, you are deleting items while iterating the results directly, with a simple loop: this means you will skip elements, because you will increment the index even when you delete an item (and the underlying result set has that item removed)!

    So you should be doing either:

    RealmResults<RealmObject> realmObjects = realmInstance.where(realmClass).findAll();
    for(RealmObject obj : realmObjects) { // 3.0.0+
    

    or

    RealmResults<RealmObject> realmObjects = realmInstance.where(realmClass).findAll();
    for(int i = realmObjects.size() - 1; i >= 0; i--) { // 0.88.3 and below
    

    or

    RealmResults<RealmObject> realmObjects = realmInstance.where(realmClass).findAll();
    OrderedRealmCollection<RealmObject> snapshot = realmObjects.createSnapshot(); // <-- !!!
    for(int i = 0; i < snapshot.size(); i++) {  // <-- !!!
        RealmObject realmObject = snapshot.get(i);
    

    Anyways, transactions are blocking across threads, and also across processes (2.0.0+), there can only be one open write transaction open at a given time.

    So a transaction cannot read/operate on invalid/outdated data.