From 4ba989a0c9fa762305b573dc7996d9b5367cddef Mon Sep 17 00:00:00 2001 From: Natasha Murashkina Date: Wed, 20 Aug 2025 14:52:03 +0100 Subject: [PATCH 1/6] propagateExceptionFinalResort - use reportError in Node.js #4451 --- .../js/src/internal/CoroutineExceptionHandlerImpl.kt | 4 ++++ .../src/internal/CoroutineExceptionHandlerImpl.kt | 4 ---- .../src/internal/CoroutineExceptionHandlerImpl.kt | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) 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/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..fceefe5e3d 100644 --- a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt @@ -2,6 +2,8 @@ package kotlinx.coroutines.internal internal actual typealias JsAny = kotlin.js.JsAny +internal external val reportError: ((error: JsAny) -> Unit)? + internal actual fun Throwable.toJsException(): JsAny = toJsError(message, this::class.simpleName, stackTraceToString()) @@ -14,3 +16,12 @@ internal fun toJsError(message: String?, className: String?, stack: String?): Js return error; """) } + +internal actual fun propagateExceptionFinalResort(exception: Throwable) { + if (reportError != null) { + // In Node.js runtime, use reportError. + reportError(exception.toJsException()) + } else { + throwAsync(exception.toJsException()) + } +} From 4feef8eae84259a31638fbbc9d5ba195476a6d7f Mon Sep 17 00:00:00 2001 From: Natasha Murashkina Date: Wed, 20 Aug 2025 15:00:59 +0100 Subject: [PATCH 2/6] extract variable jsException --- .../wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt index fceefe5e3d..d0c979baf7 100644 --- a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt @@ -18,10 +18,11 @@ internal fun toJsError(message: String?, className: String?, stack: String?): Js } internal actual fun propagateExceptionFinalResort(exception: Throwable) { + val jsException = exception.toJsException() if (reportError != null) { // In Node.js runtime, use reportError. - reportError(exception.toJsException()) + reportError(jsException) } else { - throwAsync(exception.toJsException()) + throwAsync(jsException) } } From cffc36b190ca983ab797adf58a30c3d5c199a121 Mon Sep 17 00:00:00 2001 From: Natasha Murashkina Date: Wed, 20 Aug 2025 15:15:44 +0100 Subject: [PATCH 3/6] add globalThis and comments by @turansky --- .../src/internal/CoroutineExceptionHandlerImpl.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt index d0c979baf7..2d48ef8df7 100644 --- a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt @@ -2,7 +2,9 @@ package kotlinx.coroutines.internal internal actual typealias JsAny = kotlin.js.JsAny -internal external val reportError: ((error: JsAny) -> Unit)? +internal external object globalThis : JsAny { + val reportError: ((error: JsAny) -> Unit)? +} internal actual fun Throwable.toJsException(): JsAny = toJsError(message, this::class.simpleName, stackTraceToString()) @@ -19,10 +21,11 @@ internal fun toJsError(message: String?, className: String?, stack: String?): Js internal actual fun propagateExceptionFinalResort(exception: Throwable) { val jsException = exception.toJsException() - if (reportError != null) { - // In Node.js runtime, use reportError. - reportError(jsException) + if (globalThis.reportError != null) { + // modern browsers, Deno + globalThis.reportError(jsException) } else { + // Node.js, old Safari throwAsync(jsException) } } From 1fc5783daeeded909b072b10440d0dfbccb05493 Mon Sep 17 00:00:00 2001 From: Natasha Murashkina Date: Wed, 27 Aug 2025 14:00:55 +0100 Subject: [PATCH 4/6] report behavior change in CHANGES.md --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c289a5d8b0..edbc149a80 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Change log for kotlinx.coroutines +## Version 1.10.3 +* `propagateExceptionFinalResort` now throws instead of logging 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! From 824b8a43fa146d89bf457b33552773ba8b776ed8 Mon Sep 17 00:00:00 2001 From: Natasha Murashkina Date: Wed, 27 Aug 2025 14:01:17 +0100 Subject: [PATCH 5/6] add comments --- .../js/test/PropagateExceptionFinalResortTest.kt | 1 + .../wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt | 5 +++-- .../wasmJs/test/PropagateExceptionFinalResortTest.kt | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) 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/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt index 2d48ef8df7..94587cc78e 100644 --- a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt @@ -21,11 +21,12 @@ internal fun toJsError(message: String?, className: String?, stack: String?): Js 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 + // Modern browsers, Deno, Bun, etc. globalThis.reportError(jsException) } else { - // Node.js, old Safari + // 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) { From 9931acdadce1c2c1df649cf74b09b0ef2c9e8ff7 Mon Sep 17 00:00:00 2001 From: Natasha Murashkina Date: Wed, 27 Aug 2025 15:01:11 +0100 Subject: [PATCH 6/6] remove impl details from CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index edbc149a80..de8490cfe0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ # Change log for kotlinx.coroutines ## Version 1.10.3 -* `propagateExceptionFinalResort` now throws instead of logging on the `js` and `wasmJS` targets. +* 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