Description
What do we have now?
The following deadlocks because nested runBlocking
tries to process tasks from the outer runBlocking
, and one of the tasks actually depends on the completion of the nested runBlocking
:
import kotlinx.coroutines.*
class Container(private val scope: CoroutineScope) {
private val instances = HashMap<Class<*>, Deferred<Any>>() // basically a map
suspend fun <T> instance(key: Class<out T>): T {
return instances
.computeIfAbsent(key, ::instanceDeferred)
.await() as T
}
private fun instanceDeferred(clazz: Class<*>): Deferred<Any> = scope.async {
val constructor = clazz.getConstructor(Container::class.java)
constructor.isAccessible = true
constructor.newInstance(this@Container)
}
}
class MyService(container: Container) {
init {
runBlocking {
// this can ask container for an unrelated service,
// or in this case it's enough for it to be empty
}
}
}
class MyService2(container: Container) {
val service1 = runBlocking {
container.instance(MyService::class.java)
}
}
fun main() {
runBlocking {
val container = Container(this@runBlocking)
launch {
println(container.instance(MyService::class.java))
}
launch {
println(container.instance(MyService2::class.java).service1)
}
}
}
What should be instead?
runBlocking
should not steal tasks from outer runBlocking
.
Alternatively, please provide an API to make runBlocking
not steal tasks. It may be a boolean, or a special coroutine context element. Currently we have to use internals.
Why?
Because otherwise it deadlocks.
Notes
Originally, the "stealing" was added to fix a very specific use-case #860.
I believe that if nested runBlocking
"steals" tasks while being inside a limited dispatcher, then this stealing logic should apply to all limited dispatchers, i.e. runBlocking
on a thread inside Dispatchers.Default
should steal tasks from Dispatchers.Default
(#3439). If that's not possible for any reason, then nested runBlocking
should not steal tasks from the outer runBlocking
for the very same reason whatever it might be.