diff --git a/CHANGES.md b/CHANGES.md index c289a5d8b0..de8490cfe0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Change log for kotlinx.coroutines +## Version 1.10.3 +* Unhandled exceptions in Kotlin coroutines are now re-thrown into the environment instead of being logged into the console on the `js` and `wasmJS` targets. + ## Version 1.10.2 * Fixed the `kotlinx-coroutines-debug` JAR file including the `module-info.class` file twice, resulting in failures in various tooling (#4314). Thanks, @RyuNen344! diff --git a/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt index 0cf8d9bb1b..946663060c 100644 --- a/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt @@ -5,3 +5,7 @@ import kotlin.js.unsafeCast internal actual external interface JsAny internal actual fun Throwable.toJsException(): JsAny = this.unsafeCast() + +internal actual fun propagateExceptionFinalResort(exception: Throwable) { + throwAsync(exception.toJsException()) +} diff --git a/kotlinx-coroutines-core/js/test/PropagateExceptionFinalResortTest.kt b/kotlinx-coroutines-core/js/test/PropagateExceptionFinalResortTest.kt index 33efb7eff2..41e12a2181 100644 --- a/kotlinx-coroutines-core/js/test/PropagateExceptionFinalResortTest.kt +++ b/kotlinx-coroutines-core/js/test/PropagateExceptionFinalResortTest.kt @@ -8,6 +8,7 @@ class PropagateExceptionFinalResortTest : TestBase() { @BeforeTest private fun removeListeners() { // Remove a Node.js's internal listener, which prints the exception to stdout. + // https://nodejs.org/api/process.html#event-uncaughtexception js(""" globalThis.originalListeners = process.listeners('uncaughtException'); process.removeAllListeners('uncaughtException'); diff --git a/kotlinx-coroutines-core/jsAndWasmJsShared/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jsAndWasmJsShared/src/internal/CoroutineExceptionHandlerImpl.kt index 8f3bbd56e9..b6ae67babd 100644 --- a/kotlinx-coroutines-core/jsAndWasmJsShared/src/internal/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/jsAndWasmJsShared/src/internal/CoroutineExceptionHandlerImpl.kt @@ -11,7 +11,3 @@ internal expect fun Throwable.toJsException(): JsAny * rather than in the current execution branch. */ internal fun throwAsync(e: JsAny): Unit = js("setTimeout(function () { throw e }, 0)") - -internal actual fun propagateExceptionFinalResort(exception: Throwable) { - throwAsync(exception.toJsException()) -} diff --git a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt index 52c9a827a1..94587cc78e 100644 --- a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt @@ -2,6 +2,10 @@ package kotlinx.coroutines.internal internal actual typealias JsAny = kotlin.js.JsAny +internal external object globalThis : JsAny { + val reportError: ((error: JsAny) -> Unit)? +} + internal actual fun Throwable.toJsException(): JsAny = toJsError(message, this::class.simpleName, stackTraceToString()) @@ -14,3 +18,15 @@ internal fun toJsError(message: String?, className: String?, stack: String?): Js return error; """) } + +internal actual fun propagateExceptionFinalResort(exception: Throwable) { + val jsException = exception.toJsException() + // https://github.com/JetBrains/kotlin-wrappers/blob/master/kotlin-browser/src/webMain/generated/web/errors/reportError.kt + if (globalThis.reportError != null) { + // Modern browsers, Deno, Bun, etc. + globalThis.reportError(jsException) + } else { + // Old Safari, Node.js + throwAsync(jsException) + } +} diff --git a/kotlinx-coroutines-core/wasmJs/test/PropagateExceptionFinalResortTest.kt b/kotlinx-coroutines-core/wasmJs/test/PropagateExceptionFinalResortTest.kt index 2449b72760..665e095bd3 100644 --- a/kotlinx-coroutines-core/wasmJs/test/PropagateExceptionFinalResortTest.kt +++ b/kotlinx-coroutines-core/wasmJs/test/PropagateExceptionFinalResortTest.kt @@ -15,9 +15,9 @@ class PropagateExceptionFinalResortTest : TestBase() { } /* - * Test that `propagateExceptionFinalResort` re-throws the exception on Wasm/JS. + * Test that `propagateExceptionFinalResort` re-throws the exception on wasmJS. * - * It is checked by setting up an exception handler within Wasm/JS. + * It is checked by setting up an exception handler within wasmJS. */ @Test fun testPropagateExceptionFinalResortReThrowsOnWasmJS() = runTest { @@ -31,6 +31,7 @@ class PropagateExceptionFinalResortTest : TestBase() { } private fun addUncaughtExceptionHandlerHelper() { + // https://nodejs.org/api/process.html#event-uncaughtexception js(""" globalThis.exceptionCaught = false; globalThis.exceptionHandler = function(e) {