>>Question about avoiding deadlocks<< ...
# coroutines
m
>Question about avoiding deadlocks<<
My team and I are using Kotlin for the development of a Minecraft server. We’ve implemented our own coroutine dispatcher that executes on the main server thread, which basically looks like this:
Copy code
private class MinecraftCoroutineDispatcher : CoroutineDispatcher() {

    @InternalCoroutinesApi
    override fun isDispatchNeeded(context: CoroutineContext): Boolean = !Bukkit.isPrimaryThread()

    /** Dispatches the coroutine on the main server thread. */
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        BukkitExecutor.schedule(action = block::run)
    }
}
This is all fine and dandy, until we use
runBlocking
to block the main server thread, which causes a deadlock when switching back to the main thread. Minimal example:
Copy code
fun myFun() {
    // we are already on the server thread here, so that thread is blocked by runBlocking

    runBlocking(Dispatchers.Minecraft) {
        val result = withContext(<http://Dispatchers.IO|Dispatchers.IO>) { /* some I/O operation */ }
        // <-- deadlock RIGHT HERE when switching back to Minecraft dispatcher

        // perform some code on the server thread
    }
}
The reason this deadlocks is because
BukkitExecutor.schedule
used by
Dispatchers.Minecraft
requires the server thread to run, as scheduled tasks are executed at the end of each “tick”. If the server thread is blocked, however, as it is in our case thanks to
runBlocking
, the task is never executed, meaning the switch back to
Dispatchers.Minecraft
deadlocks. In kotlinx.coroutines 1.4.0, I wrote the following code that messes with coroutine internals to determine whether the server thread is currently being blocked by a parent coroutine: