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

Introduce private DiagnosticCoroutineContextException and add it to t… #3170

Merged
merged 1 commit into from Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
Expand Up @@ -27,6 +27,24 @@ internal actual fun initializeDefaultExceptionHandlers() {
CoroutineExceptionHandler
}

/**
* Private exception without stacktrace that is added to suppressed exceptions of the original exception
* when it is reported to the last-ditch current thread 'uncaughtExceptionHandler'.
*
* The purpose of this exception is to add an otherwise inaccessible diagnostic information and to
* be able to poke the failing coroutine context in the debugger.
*/
private class DiagnosticCoroutineContextException(private val context: CoroutineContext) : RuntimeException() {
override fun getLocalizedMessage(): String {
return context.toString()
}

override fun fillInStackTrace(): Throwable {
// Prevent Android <= 6.0 bug, #1866
stackTrace = emptyArray()
return this
}
}

internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
// use additional extension handlers
Expand All @@ -42,5 +60,8 @@ internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exce

// use thread's handler
val currentThread = Thread.currentThread()
// addSuppressed is never user-defined and cannot normally throw with the only exception being OOM
// we do ignore that just in case to definitely deliver the exception
runCatching { exception.addSuppressed(DiagnosticCoroutineContextException(context)) }
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
Expand Up @@ -39,4 +39,16 @@ class CoroutineExceptionHandlerJvmTest : TestBase() {

finish(3)
}

@Test
fun testLastDitchHandlerContainsContextualInformation() = runBlocking {
expect(1)
GlobalScope.launch(CoroutineName("last-ditch")) {
expect(2)
throw TestException()
}.join()
assertTrue(caughtException is TestException)
assertContains(caughtException.suppressed[0].toString(), "last-ditch")
finish(3)
}
}