realm / realm-kotlin

Kotlin Multiplatform and Android SDK for the Realm Mobile Database: Build Better Apps Faster.
Apache License 2.0
889 stars 52 forks source link

Deadlock when using both writeBlocking and Flows #1606

Closed cmelchior closed 6 months ago

cmelchior commented 6 months ago

I ran into this behavior when I was trying to write up a sample that showed how to interop between Java and Kotlin. It looks like somehow we end up deadlocking our internals when using writeBlocking and Flow. Not 100% sure how it is happening though.

This JVM unit test demonstrates the behavior:

    @Test
    fun writeBlockingAndFlows() {
        val tmpDir = PlatformUtils.createTempDir()
        val configuration = RealmConfiguration.Builder(setOf(Parent::class, Child::class))
            .directory(tmpDir)
            .build()
        val realmScope = CoroutineScope(CoroutineName("RealmScope") + Dispatchers.Default)
        val realm = Realm.open(configuration)

        // Do initial write
        realm.writeBlocking {
            copyToRealm(Parent())
        }
        assertEquals(1, realm.query<Parent>().count().find())
        println("Wrote 1st data")

        // Start notifications
        val updateLatch = CountDownLatch(1)
        val job = realmScope.launch {
            realm.query<Parent>().asFlow().collect {
                println(it)
                if (it is UpdatedResults) {
                    updateLatch.countDown()
                }
            }
        }
        println("Started observer")

        // Do a 2nd write
        realm.writeBlocking {
            copyToRealm(Parent())
        }
        println("Wrote 2nd data")

        // Wait for the Update notification. This will currently fail, and all Coroutines are in
        // the WAITING state.
        // Note: If we move the notification block to run as the first thing, the writes do
        // not block it.
        if (!updateLatch.await(10, TimeUnit.SECONDS)) {
            fail("Did not receive update notification in time")
        }
        job.cancel()
        realm.close()
    }

The thread dump look like this:

image

cmelchior commented 6 months ago

I made a mistake in reasoning about the flow of events. The 2nd write could happen before the listener fired the first event, which meant that no update was ever coming.

Closing