Skip to content
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

Dispatcher failures may leave coroutines uncompleted #4209

Open
dkhalanskyjb opened this issue Aug 12, 2024 · 1 comment
Open

Dispatcher failures may leave coroutines uncompleted #4209

dkhalanskyjb opened this issue Aug 12, 2024 · 1 comment
Labels

Comments

@dkhalanskyjb
Copy link
Collaborator

dkhalanskyjb commented Aug 12, 2024

Describe the bug

During the review of #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
Copy link
Collaborator Author

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?

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

1 participant