Skip to content

Commit

Permalink
Introduce private DiagnosticCoroutineContextException and add it to t…
Browse files Browse the repository at this point in the history
…he original exception prior to passing it to the Thread.currentThread().uncaughtExceptionHandler()

Fixes #3153
  • Loading branch information
qwwdfsad committed Feb 1, 2022
1 parent 76989f7 commit c1129b2
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 0 deletions.
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)
}
}

0 comments on commit c1129b2

Please sign in to comment.