Skip to content

Deadlock in case of multiple nested runBlocking #1679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
evgzakharov opened this issue Nov 28, 2019 · 10 comments
Open

Deadlock in case of multiple nested runBlocking #1679

evgzakharov opened this issue Nov 28, 2019 · 10 comments
Assignees
Labels

Comments

@evgzakharov
Copy link

evgzakharov commented Nov 28, 2019

I have find an infinite awaiting of execution of runBlocking.
Minimal reproducible code:

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import kotlin.coroutines.coroutineContext

class BugCheck {
    @Test
    fun `check new player is created successfully`() = runBlocking {
        val completed = CompletableDeferred<Boolean>()
        val leagueTask = async { leagueCheck() }

        launch { playerCheck(completed) }

        leagueTask.await()
        completed.complete(true)
        println("stop")
        Unit
    }

    private suspend fun playerCheck(completed: CompletableDeferred<Boolean>) {
        executeOnceTest {
            while (coroutineContext.isActive && !completed.isCompleted) {
                println("...")
                delay(100)
            }

            true
        }
    }

    private suspend fun leagueCheck() {
//        delay(1) //uncomment and all works fine

        executeOnceTest {
            println("inner done")
        }
        println("555") // never print

        delay(1000)
    }

    private fun executeOnceTest(scenario: suspend () -> Unit): Unit = runBlocking {
        scenario()

        runBlocking {
            println("test")
        }
        println("after runBlocking")
    }
}

If you running test, code will never complete. Execution freeze on executeOnceTest. But if uncomment delay(1) all will works fine.

kotlin 1.3.61
kotlin-coroutines 1.3.2
gradle 6.0.1

@evgzakharov evgzakharov changed the title Error with infinite awaiting of execution of runBlocking Error with infinite awaiting of execution of code Nov 28, 2019
@qwwdfsad
Copy link
Member

qwwdfsad commented Dec 5, 2019

Could you please verify your example one more time? I've copypasted it as is and it seems to work properly with Kotlin 1.3.61 and coroutine 1.3.2

@evgzakharov
Copy link
Author

Yes, it reproduce in jvm 8, 12, 13. But sometimes version without delay(1) complete after 5+ seconds. But if uncomment line with delay code will complete very fast.
And it reproduce not only in my machine.

@qwwdfsad
Copy link
Member

qwwdfsad commented Dec 5, 2019

Thanks, I will try to run it on a different machine and/or analyze more thoughtfully

@evgzakharov
Copy link
Author

@qwwdfsad thank you. If it will could help, I have tried it at macOS and run this test in Idea and through gradle.

@qwwdfsad qwwdfsad added the bug label Dec 9, 2019
@qwwdfsad
Copy link
Member

qwwdfsad commented Dec 9, 2019

Looks like it has the same root cause as

@Test
fun testDeadlock() = runBlocking<Unit> {
    val job = launch {
        runBlocking {

        }
    }

    yield() // Comment yield and test starts to pass
    runBlocking {
        while (job.isActive) yield()
    }
}

Thanks for the reproducer!

@evgzakharov
Copy link
Author

@qwwdfsad yes, this smaller test has same effect.

@qwwdfsad qwwdfsad changed the title Error with infinite awaiting of execution of code Deadlock in case of multiple nested runBlocking Dec 12, 2019
@qwwdfsad
Copy link
Member

Implementation note:

  • runBlocking start should be undispatched
  • nested runBlocking when processing an event loop
  • documentation should clarify that dependency on the outer runBlocking from the nested one is prohibited

@evgzakharov
Copy link
Author

documentation should clarify that dependency on the outer runBlocking from the nested one is prohibited

@qwwdfsad what could be done in cases where it need to have such dependency? Does it have any workarounds?

@qwwdfsad
Copy link
Member

qwwdfsad commented Dec 12, 2019

You can replace runBlocking with coroutines.
A situation where nested runBlocking awaits for outer runBlocking is a deadlock by its very definition: outer runBlocking cannot complete until its inner runBlocking completes, but inner one awaits for it

@evgzakharov
Copy link
Author

Thanks @qwwdfsad. I have misunderstood about meaning of "dependency".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants