Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ internal actual fun ensurePlatformExceptionHandlerLoaded(callback: CoroutineExce
internal actual fun propagateExceptionFinalResort(exception: Throwable) {
// use the thread's handler
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
val exceptionHandler = currentThread.uncaughtExceptionHandler
try {
exceptionHandler.uncaughtException(currentThread, exception)
} catch (_: Throwable) {
/* Do nothing.
* From https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.UncaughtExceptionHandler.html :
* > Any exception thrown by this method will be ignored by the Java Virtual Machine.
*
* This means the authors of the thread exception handlers have the right to throw exceptions indiscriminately.
* We have no further channels for propagating the fatal exception, so we give up. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can add something like val _ = "Welcome, weary traveler. To my breakpoint place" which is both self-explanatory and funny (okay, maybe only self-explanatory)

}
}

// This implementation doesn't store a stacktrace, which is good because a stacktrace doesn't make sense for this.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,15 @@ import kotlin.test.*

class CoroutineExceptionHandlerJvmTest : TestBase() {

private val exceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
private lateinit var caughtException: Throwable

@Before
fun setUp() {
Thread.setDefaultUncaughtExceptionHandler({ _, e -> caughtException = e })
}

@After
fun tearDown() {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
}

@Test
fun testFailingHandler() = runBlocking {
expect(1)
val job = GlobalScope.launch(CoroutineExceptionHandler { _, _ -> throw AssertionError() }) {
expect(2)
throw TestException()
val caughtException = catchingUncaughtException {
GlobalScope.launch(CoroutineExceptionHandler { _, _ -> throw AssertionError() }) {
expect(2)
throw TestException()
}.join()
}

job.join()
assertIs<RuntimeException>(caughtException)
assertIs<AssertionError>(caughtException.cause)
assertIs<TestException>(caughtException.suppressed[0])
Expand All @@ -40,12 +27,47 @@ class CoroutineExceptionHandlerJvmTest : TestBase() {
@Test
fun testLastDitchHandlerContainsContextualInformation() = runBlocking {
expect(1)
GlobalScope.launch(CoroutineName("last-ditch")) {
expect(2)
throw TestException()
}.join()
val caughtException = catchingUncaughtException {
GlobalScope.launch(CoroutineName("last-ditch")) {
expect(2)
throw TestException()
}.join()
}
assertIs<TestException>(caughtException)
assertContains(caughtException.suppressed[0].toString(), "last-ditch")
finish(3)
}

@Test
fun testFailingUncaughtExceptionHandler() = runBlocking {
expect(1)
withUncaughtExceptionHandler({ _, e ->
expect(3)
throw TestException("uncaught")
}) {
launch(Job()) {
expect(2)
throw TestException("to be reported")
}.join()
}
finish(4)
}
}

private inline fun catchingUncaughtException(action: () -> Unit): Throwable? {
var caughtException: Throwable? = null
withUncaughtExceptionHandler({ _, e -> caughtException = e }) {
action()
}
return caughtException
}

private inline fun <T> withUncaughtExceptionHandler(handler: Thread.UncaughtExceptionHandler, action: () -> T): T {
val exceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(handler)
try {
return action()
} finally {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
}
}