-
Notifications
You must be signed in to change notification settings - Fork 1.9k
handleCoroutineException()
prevents crashing JVM threads on fatal errors
#2552
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
Comments
I am a bit concerned about the proposed solution.
Yet the problem with throwing |
It can, yes. That's Exceptions for you. And user-defined functions. When a coroutine throws a fatal exception, I'd like for the error handling path not to execute statements I didn't write into the program. It's a fatal error, so the JVM's process will halt and post-mortem after my error handler executes. Given that, I'm cool with other coroutines in that JVM process halting. Best we can hope is that they're undisturbed for a post-mortem heap snapshot.
Arbitrary? Or just the coroutine that experienced an unhandled Exception, with all the expected failure to advance for any coroutine waiting on the completion of a fatally-excepted coroutine that is no longer live? JVMs continue program execution after effectively-unrecoverable errors, for better or for (mostly) worse. Errors that inherits from Those errors are what I'm trying to deliver to monitoring when an expression in a coroutine abruptly completes and throws one. It's (usually) possible to get a Coroutines effectively wraps all coroutines in this
My understanding of how this works is approximate, but here goes. When an expression abruptly completes ((JLS 15.6) and throws a If a However, if the executing Today, PR 2724 adds locking an intrinsic lock, which is (technically) a new kind of thing that can abruptly complete an expression after the first
The best thing to do on a fatal error is to proceed to halting the JVM by crashing the thread as graciously as possible. Abstractions on top of that crash path make achieving what's still possible within a crashing-and-burning JVM significantly harder to achieve.
It's not possible to avoid this when an error handler executes arbitrary statements. If the user-defined last-ditch error handler isn't exception-safe, the program can crash again and potentially lose state. That's just the risk when overriding At the moment, and with the proposed PR 2724, the Coroutines library adds exception-unsafe statements to the critical path of all errors, which makes exception-safe error handling in a program using the library much more difficult to implement.
It doesn't address my concerns, and I hope I've provided enough context as to why not. I'm happy to improve the default exception handler. However, my requirement isn't a safer exception handler, it's to supply my own exception handler, and to live with the consequences. I'm trying to get from an uncaught exception in a coroutine to an exited JVM process as expeditiously as possible. I'd prefer if Coroutines didn't add catch statements that call exception-unsafe default code around all my functions, because it's quite difficult to work around. If Coroutines tries to safely recover liveness when the JVM is in the middle of blowing up with a The liveness of coroutines isn't recoverable after certain JVM errors have happened in a |
Hi, Regarding throwing service loader and class loading on the exception handling path -- I've reworked the initial solution here 906eae1. First of all, thanks for the detailed explanation and detailed reasoning!
It's partially true. It's indeed try-catch, but it doesn't imply that throwing from the Coroutines are the complex framework that is written in a particular manner: lock-free state machines, plenty of guards for internal invariants, and so on. It does not expect exceptions to be thrown from arbitrary places and handlers. It indeed cannot halt an arbitrary hierarchy, but only the one that is reachable somehow -- via Consider your change being implemented and then the following example:
Even though the coroutine crashed with Apart from that, there are other issues in a way of exceptions -- It seems like the most robust solution for your use-case is to consistently deliver (and crash, if necessary) exceptions from I suspect that users may re-define |
…Kotlin#2957) * Eagerly load CoroutineExceptionHandler and load the corresponding service Partially addresses Kotlin#2552
Considering it fixed in 1.6.0-RC. Feel free to continue the discussion if the implemented solution is not enough |
I'd like to propose reverting 0b65246 That change means that using coroutines triggers a read from disk (I think on whatever thread happens to create the first job). The particular reason I care is that we have error detection checks on which threads are doing IO and these reads now trigger violations. |
@nreid260, the only thing the change does is access a class early. If your system is unhappy with that, it probably means that it's unhappy in general with classes being loaded. |
Do we agree that it may change which thread does the loading? The fact is that we're seeing violations that we didn't before. Maybe those violations just weren't being reported, and now they are, but something has changed. I also wonder why coroutines needs to use ServiceLoader at all. It seems kind of strange to me that a foundational library like this would dynamically load code in this way. If there were a way to disable that behaviour, it would address my concerns. |
If the moment at which a class is loaded is important to you, then the fix is to explicitly ensure it, for example, by accessing the class in a safe-for-class-loading thread before anything happens with the coroutines. |
I don't think it does. In my debugging, the exception gets caught by the
That's roughly as expected in a JVM?
To reiterate, I don't think the error gets lost. Coroutines invoked the last-ditch error handler that the user specified for a coroutine. That last-ditch error handler threw an Exception. Putting aside calling a Coroutines is running as an abstraction on Here's the Javadoc for
The thread isn't abruptly terminating, but Coroutines is calling the handler anyway. Here's the Javadoc for
However, an exception thrown when Coroutines calls this method isn't ignored by the Java Virtual Machine as the handler's interface specifies. Instead, the exception propagates through the still-running Continuing to use a thread after its exception handler has executed is risky. By interface contract, It'd be easier and safer to have Coroutines share pool Executors with other libraries if Coroutines didn't break the contract of It'd be easier to integrate Coroutines into hybrid programs if didn't try to hide a fatal exception from either the It'd be safer to integrate Coroutines into hybrid programs if it weren't programmed so it might return a If Coroutines isn't willing to drop an exception thrown when the If that isn't Coroutines' default behaviour, it'd be nice to be able to configure to work that way. And hopefully also so that it doesn't call a |
…Kotlin#2957) * Eagerly load CoroutineExceptionHandler and load the corresponding service Partially addresses Kotlin#2552
In a JVM, a coroutine should be able to handle a fatal error by crashing its
Thread
.In the JVM default,
handleCoroutineExceptionImpl()
calls aServiceLoader
in the error handling execution path. That's not safe, because the call to theServiceLoader
can itself throw.This prompted investigating coroutine crashes, which showed some
Thread
crashes, but not for all fatal errors. The onlyThread
crashes had the coroutine's fatal error hidden by theServiceLoader
call throwing during error handling - either because the first error was anOutOfMemoryError
, or because theServiceLoader
’s (internally lazy) iterator crashed by reading from disk during error handling, on aThread
that’s prevented from doing I/O.Error handling is customizable with
CoroutineExceptionHandler
. I thought we'd be able to avoid theServiceLoader
problem with a custom handler, and pass fatal exceptions to theExecutor
just by throwing. Unfortunately, if aCoroutineExceptionHandler
tries to crash,handleCoroutineException()
catches and starts executing the defaulthandleCoroutineExceptionImpl()
!So, I see two issues:
handleCoroutineException()
prevents aCoroutineExceptionHandler
from crashing itsThread
if it would like to do thathandleCoroutineExceptionImpl()
can throw more exceptions when trying to handle an existing oneTo fix the first issue, I'd like to propose this implementation of
handleCoroutineException()
:This would make a
CoroutineExceptionHandler
a proper override.In existing (big) JVM applications, crash handlers are configured for
Threads
orExecutors
. Letting coroutines crash would let the one set of loggers, monitoring, test instrumentation, etc. deal with any fatal errors consistently. By preventing coroutines from crashing, unhandled errors in coroutines need to be configured with their own entirely separate error handlers, in addition to any the program has at theExecutor
orThread
level.The text was updated successfully, but these errors were encountered: