kotlinx.coroutines icon indicating copy to clipboard operation
kotlinx.coroutines copied to clipboard

Dispatcher failures may leave coroutines uncompleted

Open dkhalanskyjb opened this issue 1 year ago • 3 comments
trafficstars

Describe the bug

During the review of https://github.com/Kotlin/kotlinx.coroutines/pull/4181, it became evident that we don't properly handle failures in coroutine dispatchers, and this can surface in ways other than just strange-looking exceptions.

Provide a Reproducer

val dispatcher = newSingleThreadContext("unreliable friend")
runTest {
    launch(dispatcher, start = CoroutineStart.UNDISPATCHED) {
        try {
            println("This code runs...")
            suspendCancellableCoroutine<Int> { cont ->
                // we launch a separate thread and wait a bit,
                // because we want the coroutine to actually suspend
                // and go through a dispatch.
                launch(Dispatchers.Default) {
                    delay(100)
                    // close the dispatcher, now it will throw on `dispatch`
                    dispatcher.close()
                    // try dispatching the coroutine
                    cont.resume(3)
                }
            }
        } catch (e: Throwable) {
            println("Caught $e")
            throw e
        } finally {
            println("... therefore, this code must run.")
        }
    }
}

This code will hang after printing This code runs..., as the launched coroutine never finishes.

dkhalanskyjb avatar Aug 12 '24 09:08 dkhalanskyjb

It's unclear what to do in this scenario, though. Here are some options:

  • Try to fail the whole program by calling handleCoroutineException. We don't call it a mechanism of last-resort exception propagation for nothing.
  • On the JVM, Executor.asCoroutineDispatcher tries cancelling the task and redispatching it to Dispatchers.IO if the executor rejected it. We can't do literally that in common code, as there is no Dispatchers.IO on platforms with no threads, but if there are no threads, we can just use DefaultExecutor. The problem is, what if finalizers contain some code that must run on a specific thread? If Dispatchers.Main fails, it's usually a contract violation to try to run its code on Dispatchers.IO.
  • We can pretend that the coroutine that couldn't get dispatched did finish with an error. This way, finalizers won't be run (which is a contract violation), but at least the whole coroutine hierarchy won't deadlock because of an incorrect dispatch.
  • Something else?

dkhalanskyjb avatar Sep 02 '24 11:09 dkhalanskyjb

What's the current status? I usually meet coroutines hang-up, especially when I create many and many coroutines. For me, crash the JVM is better than hang-up, if it's possibile.

iseki0 avatar Jun 11 '25 08:06 iseki0

@iseki0, this issue is about CoroutineDispatcher.dispatch failing with an exception, which is quite unlikely to happen if you stick to the standard CoroutineDispatcher implementations provided by us. You are probably facing some other problem, or you have a custom CoroutineDispatcher that can throw exceptions, in which case, note that this is not supported and should be avoided.

dkhalanskyjb avatar Jun 11 '25 09:06 dkhalanskyjb