From 745ee78ba8520738b88b420f4d1df1025d508e07 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 23 Apr 2026 11:01:16 +0200 Subject: [PATCH 1/2] Align CLR completeTaskWrapper to call exit on exception, matching Mono behavior Fixes dotnet/runtime#125587 The CLR implementation of completeTaskWrapper silently swallowed exceptions during Promise-to-Task marshalling, while the Mono implementation called mono_exit(1, ex) to terminate the runtime. This aligns the CLR side by calling dotnetLoaderExports.exit(1, ex), which normalizes the error, notifies exit listeners, tears down timers, and then quits. Also adds exit() to the LoaderExports cross-module interface so it can be called from the interop module. --- src/native/libs/Common/JavaScript/cross-module/index.ts | 7 ++++--- src/native/libs/Common/JavaScript/loader/index.ts | 2 ++ src/native/libs/Common/JavaScript/types/exchange.ts | 4 +++- .../interop/marshaled-types.ts | 6 +++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/native/libs/Common/JavaScript/cross-module/index.ts b/src/native/libs/Common/JavaScript/cross-module/index.ts index 27e4bf51b4e0c4..aa9e2b3f66a2e7 100644 --- a/src/native/libs/Common/JavaScript/cross-module/index.ts +++ b/src/native/libs/Common/JavaScript/cross-module/index.ts @@ -136,9 +136,10 @@ export function dotnetUpdateInternalsSubscriber() { addOnExitListener: table[14], abortStartup: table[15], quitNow: table[16], - normalizeException: table[17], - fetchSatelliteAssemblies: table[18], - fetchLazyAssembly: table[19], + exit: table[17], + normalizeException: table[18], + fetchSatelliteAssemblies: table[19], + fetchLazyAssembly: table[20], }; Object.assign(dotnetLoaderExports, loaderExportsLocal); Object.assign(logger, loggerLocal); diff --git a/src/native/libs/Common/JavaScript/loader/index.ts b/src/native/libs/Common/JavaScript/loader/index.ts index 6230e80c594152..81d4ecddea5c19 100644 --- a/src/native/libs/Common/JavaScript/loader/index.ts +++ b/src/native/libs/Common/JavaScript/loader/index.ts @@ -66,6 +66,7 @@ export function dotnetInitializeModule(): RuntimeAPI { addOnExitListener, abortStartup, quitNow, + exit, normalizeException, fetchSatelliteAssemblies, fetchLazyAssembly, @@ -115,6 +116,7 @@ export function dotnetInitializeModule(): RuntimeAPI { dotnetLoaderExports.addOnExitListener, dotnetLoaderExports.abortStartup, dotnetLoaderExports.quitNow, + dotnetLoaderExports.exit, dotnetLoaderExports.normalizeException, dotnetLoaderExports.fetchSatelliteAssemblies, dotnetLoaderExports.fetchLazyAssembly, diff --git a/src/native/libs/Common/JavaScript/types/exchange.ts b/src/native/libs/Common/JavaScript/types/exchange.ts index 4da02ce6917023..376d33e09bb103 100644 --- a/src/native/libs/Common/JavaScript/types/exchange.ts +++ b/src/native/libs/Common/JavaScript/types/exchange.ts @@ -5,7 +5,7 @@ import type { EmsAmbientSymbolsType } from "../types"; import type { check, error, info, warn, debug, fastCheck, normalizeException } from "../loader/logging"; import type { resolveRunMainPromise, rejectRunMainPromise, getRunMainPromise, abortStartup } from "../loader/run"; -import type { addOnExitListener, isExited, isRuntimeRunning, quitNow } from "../loader/exit"; +import type { addOnExitListener, exit, isExited, isRuntimeRunning, quitNow } from "../loader/exit"; import type { initializeCoreCLR } from "../host/host"; import type { instantiateWasm, installVfsFile, registerDllBytes, loadIcuData, registerPdbBytes, instantiateWebcilModule } from "../host/assets"; @@ -77,6 +77,7 @@ export type LoaderExports = { addOnExitListener: typeof addOnExitListener, abortStartup: typeof abortStartup, quitNow: typeof quitNow, + exit: typeof exit, normalizeException: typeof normalizeException, fetchSatelliteAssemblies: typeof fetchSatelliteAssemblies, fetchLazyAssembly: typeof fetchLazyAssembly, @@ -100,6 +101,7 @@ export type LoaderExportsTable = [ typeof addOnExitListener, typeof abortStartup, typeof quitNow, + typeof exit, typeof normalizeException, typeof fetchSatelliteAssemblies, typeof fetchLazyAssembly, diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts index b7f9dd98ad28a1..3bd7823bede9e5 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts @@ -254,7 +254,11 @@ export class PromiseHolder extends ManagedObject { // so that managed user code running in the continuation could allocate the same GCHandle number and the local registry would be already ok with that completeTask(this.gc_handle, reason, data, this.res_converter); } catch (ex) { - // there is no point to propagate the exception into the unhandled promise rejection + try { + dotnetLoaderExports.exit(1, ex); + } catch (ex2) { + // there is no point to propagate the exception into the unhandled promise rejection + } } } } From 2b94f3b1631ca55f951b92975c4d372a70acdc98 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Thu, 23 Apr 2026 11:34:08 +0200 Subject: [PATCH 2/2] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../interop/marshaled-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts index 3bd7823bede9e5..1920e54b5a39a2 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts @@ -256,7 +256,7 @@ export class PromiseHolder extends ManagedObject { } catch (ex) { try { dotnetLoaderExports.exit(1, ex); - } catch (ex2) { + } catch { // there is no point to propagate the exception into the unhandled promise rejection } }