realm-java: Exception when trying to start write transaction after committing a previous one.

In my code base, I am running all of my Realm queries that display on the UI via an async read transaction and all background queries via realm sync queries. I am running all realm writes in the background thread using:

    protected fun performRealmTransaction(changeData: (realm: Realm, done: () -> Unit) -> Unit) {
        if (ThreadUtil.isOnMainThread()) {
            throw RuntimeException("Don't perform transaction from UI thread.")
        }
        val realm = Realm.getDefaultInstance()
        realm.beginTransaction()

        changeData(realm, {
            realm.commitTransaction()
            realm.close()
        })
    }

Using this in my code:

                    performRealmTransaction { realm, commitToRealm ->
                        // do work with realm instance. Query, update values, copy/update models. 
                        commitToRealm()
                    }

I use these transaction blocks to do all of my writes. This code works just fine where I am creating a new instance, having the chance to write to it, then committing synchronously and closing the realm instance when I am done.

Here is where I am having issues. I am wondering if Realm is really running commitTransaction() synchronously.

Whenever I have code like this:

fun writeDataToRealm(): Completable {
    return Completable.create { subscriber ->
        performRealmTransaction { realm, commitToRealm ->
            // update user of app's name to foo. 
            // realm.getUser() is a Kotlin extension I created to get the 1 and only UserModel of app.
            realm.getUser().name = "foo"
            commitToRealm()
           subscriber.onCompleted()
        }
    }
}

fun doSomethingElseWithRealmAfterWriteToItAlreadyOnce() {
    writeDataToRealm().subscribe({
        // Here, we can assume that writeDataToRealm is complete, user's name has been changed to "foo" and realm has committed successfully. 
        // But it doesn't. We actually throw an exception in this line of code below because we are now trying to create a *new* realm transaction. We get an exception saying Realm is already in a write transaction but that's false. We called `commitTransaction()` before we called `subscriber.onCompleted()` which got us to this point. 
        performRealmTransaction { realm, commitToRealm ->
            // this will not run because exception thrown while creating to create new realm transaction. 
            commitToRealm()
        }
    }, { error -> })
}

If I take this exact same code but add a small RxJava delay, it works successfully. No realm exception thrown:

fun writeDataToRealm(): Completable {
    return Completable.create { subscriber ->
        performRealmTransaction { realm, commitToRealm ->
            // update user of app's name to foo. 
            // realm.getUser() is a Kotlin extension I created to get the 1 and only UserModel of app.
            realm.getUser().name = "foo"
            commitToRealm()
           subscriber.onCompleted()
        }
    }
}

// *HERE* adding `.delay(300, TimeUnit.MILLISECONDS)` and it works. 
fun doSomethingElseWithRealmAfterWriteToItAlreadyOnce() {
    writeDataToRealm().delay(300, TimeUnit.MILLISECONDS).subscribe({
        // Here, we can assume that writeDataToRealm is complete, user's name has been changed to "foo" and realm has committed successfully. 
        // But it doesn't. We actually throw an exception in this line of code below because we are now trying to create a *new* realm transaction. We get an exception saying Realm is already in a write transaction but that's false. We called `commitTransaction()` before we called `subscriber.onCompleted()` which got us to this point. 
        performRealmTransaction { realm, commitToRealm ->
            // this will not run because exception thrown while creating to create new realm transaction. 
            commitToRealm()
        }
    }, { error -> })
}

If I give realm a small bit of time, no exceptions thrown. This has been my method of getting this working in various parts of my app. It fixes it, but this cannot be right. Must be a better way, something I am doing wrong.

Goal

What do you want to achieve?

I do not expect realm to throw an exception about already being in a realm write transaction. It should have ended via commitTransaction() and .close(). Realm is not in the middle of a write transaction. It ended.

Actual Results

IllegalStateException: The Realm is already in a write transaction in ...

Version of Realm and tooling

Realm version(s): 2.2.1

Realm sync feature enabled: no

Android Studio version: 2.2.3

Which Android version and device: Nougat, Marshmallow

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 40 (21 by maintainers)

Most upvoted comments

You’re reusing the Realm instance multiple times for multiple transactions on a threadpool.

Realm instances are cached in the RealmCache for a thread, afterwards reference-counted.

So if you start opening multiple transactions on the same thread, then you’ll be telling the same instance multiple times to start a transaction - which will result not in a “blocking”, but in an IllegalStateException.

I think realm.executeTransaction() would be easier to manage.

I read the docs for isLoaded. I think I know what you mean for using it:

doRealmWriteTransactionInBackgroundThread().andThen(Completable.create { subscriber ->
            val realm = Realm.getDefaultInstance()
            realm.where(FooModel::class.java).equalTo("realm_id", newRealmId).findFirstAsync().asObservable()
                    .subscribe { data ->
                        if (data.isLoaded() && data.isValid() && data != null) {
                            realm.close()
                            subscriber.onCompleted()
                        }
                    }
        }).subscribeOn(AndroidSchedulers.mainThread())

Is this the use you were talking about @Zhuinden? It didn’t make the code much more simple but it was another way of writing it. I would have to test the above code to see if it works the same way as my previous example.