Skip to content

Commit

Permalink
[browser][MT] improve abort on web worker (#100610)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Apr 9, 2024
1 parent 001d60a commit 552b5e9
Show file tree
Hide file tree
Showing 16 changed files with 171 additions and 93 deletions.
1 change: 1 addition & 0 deletions src/mono/browser/browser.proj
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@
<EmccExportedFunction Include="stackAlloc" />
<EmccExportedFunction Include="stackRestore" />
<EmccExportedFunction Include="stackSave" />
<EmccExportedFunction Include="_emscripten_force_exit" />
</ItemGroup>
<!-- for the jiterpreter -->
<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/mono/browser/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ const fn_signatures: SigLine[] = [
[true, "mono_wasm_intern_string_ref", "void", ["number"]],

[false, "mono_wasm_exit", "void", ["number"]],
[false, "mono_wasm_abort", "void", []],
[true, "mono_wasm_getenv", "number", ["string"]],
[true, "mono_wasm_set_main_args", "void", ["number", "number"]],
// These two need to be lazy because they may be missing
Expand Down Expand Up @@ -192,7 +191,6 @@ export interface t_Cwraps {
mono_wasm_intern_string_ref(strRef: MonoStringRef): void;

mono_wasm_exit(exit_code: number): void;
mono_wasm_abort(): void;
mono_wasm_getenv(name: string): CharPtr;
mono_wasm_set_main_args(argc: number, argv: VoidPtr): void;
mono_wasm_exec_regression(verbose_level: number, image: string): number;
Expand Down
6 changes: 0 additions & 6 deletions src/mono/browser/runtime/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,6 @@ mono_wasm_exit (int exit_code)
emscripten_force_exit (exit_code);
}

EMSCRIPTEN_KEEPALIVE int
mono_wasm_abort ()
{
abort ();
}

EMSCRIPTEN_KEEPALIVE void
mono_wasm_set_main_args (int argc, char* argv[])
{
Expand Down
2 changes: 1 addition & 1 deletion src/mono/browser/runtime/invoke-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function mono_wasm_invoke_jsimport_MT (signature: JSFunctionSignature, ar
}
return;
} catch (ex2: any) {
runtimeHelpers.nativeExit(ex2);
runtimeHelpers.nativeAbort(ex2);
return;
}
}
Expand Down
64 changes: 41 additions & 23 deletions src/mono/browser/runtime/loader/exit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,40 @@ export function uninstallUnhandledErrorHandler () {
}
}

let originalOnAbort: ((reason: any, extraJson?:string)=>void)|undefined;
let originalOnExit: ((code: number)=>void)|undefined;

export function registerEmscriptenExitHandlers () {
if (!emscriptenModule.onAbort) {
emscriptenModule.onAbort = onAbort;
}
if (!emscriptenModule.onExit) {
emscriptenModule.onExit = onExit;
}
originalOnAbort = emscriptenModule.onAbort;
originalOnExit = emscriptenModule.onExit;
emscriptenModule.onAbort = onAbort;
emscriptenModule.onExit = onExit;
}

function unregisterEmscriptenExitHandlers () {
if (emscriptenModule.onAbort == onAbort) {
emscriptenModule.onAbort = undefined;
emscriptenModule.onAbort = originalOnAbort;
}
if (emscriptenModule.onExit == onExit) {
emscriptenModule.onExit = undefined;
emscriptenModule.onExit = originalOnExit;
}
}
function onExit (code: number) {
if (originalOnExit) {
originalOnExit(code);
}
mono_exit(code, loaderHelpers.exitReason);
}

function onAbort (reason: any) {
mono_exit(1, loaderHelpers.exitReason || reason);
if (originalOnAbort) {
originalOnAbort(reason || loaderHelpers.exitReason);
}
mono_exit(1, reason || loaderHelpers.exitReason);
}

// this will also call mono_wasm_exit if available, which will call exitJS -> _proc_exit -> terminateAllThreads
export function mono_exit (exit_code: number, reason?: any): void {
unregisterEmscriptenExitHandlers();
uninstallUnhandledErrorHandler();

// unify shape of the reason object
const is_object = reason && typeof reason === "object";
exit_code = (is_object && typeof reason.status === "number")
Expand All @@ -82,23 +86,27 @@ export function mono_exit (exit_code: number, reason?: any): void {
reason = is_object
? reason
: (runtimeHelpers.ExitStatus
? new runtimeHelpers.ExitStatus(exit_code)
? createExitStatus(exit_code, message)
: new Error("Exit with code " + exit_code + " " + message));
reason.status = exit_code;
if (!reason.message) {
reason.message = message;
}

// force stack property to be generated before we shut down managed code, or create current stack if it doesn't exist
if (!reason.stack) {
reason.stack = new Error().stack || "";
}
const stack = "" + (reason.stack || (new Error().stack));
Object.defineProperty(reason, "stack", {
get: () => stack
});

// don't report this error twice
const alreadySilent = !!reason.silent;
reason.silent = true;

if (!is_exited()) {
try {
unregisterEmscriptenExitHandlers();
uninstallUnhandledErrorHandler();
if (!runtimeHelpers.runtimeReady) {
mono_log_debug("abort_startup, reason: " + reason);
abort_promises(reason);
Expand All @@ -119,19 +127,25 @@ export function mono_exit (exit_code: number, reason?: any): void {
}

try {
logOnExit(exit_code, reason);
appendElementOnExit(exit_code);
if (!alreadySilent) {
logOnExit(exit_code, reason);
appendElementOnExit(exit_code);
}
} catch (err) {
mono_log_warn("mono_exit failed", err);
// don't propagate any failures
}

loaderHelpers.exitCode = exit_code;
loaderHelpers.exitReason = reason.message;
if (!loaderHelpers.exitReason) {
loaderHelpers.exitReason = reason;
}

if (!ENVIRONMENT_IS_WORKER && runtimeHelpers.runtimeReady) {
emscriptenModule.runtimeKeepalivePop();
}
} else {
mono_log_debug("mono_exit called after exit");
}

if (loaderHelpers.config && loaderHelpers.config.asyncFlushOnExit && exit_code === 0) {
Expand All @@ -154,13 +168,11 @@ export function mono_exit (exit_code: number, reason?: any): void {
function set_exit_code_and_quit_now (exit_code: number, reason?: any): void {
if (WasmEnableThreads && ENVIRONMENT_IS_WORKER && runtimeHelpers.runtimeReady && runtimeHelpers.nativeAbort) {
// note that the reason is not passed to UI thread
runtimeHelpers.runtimeReady = false;
runtimeHelpers.nativeAbort(reason);
throw reason;
}

if (runtimeHelpers.runtimeReady && runtimeHelpers.nativeExit) {
runtimeHelpers.runtimeReady = false;
try {
runtimeHelpers.nativeExit(exit_code);
} catch (error: any) {
Expand Down Expand Up @@ -205,7 +217,6 @@ async function flush_node_streams () {
}

function abort_promises (reason: any) {
loaderHelpers.exitReason = reason;
loaderHelpers.allDownloadsQueued.promise_control.reject(reason);
loaderHelpers.afterConfigLoaded.promise_control.reject(reason);
loaderHelpers.wasmCompilePromise.promise_control.reject(reason);
Expand Down Expand Up @@ -256,7 +267,7 @@ function logOnExit (exit_code: number, reason: any) {
}
}
}
if (loaderHelpers.config) {
if (!ENVIRONMENT_IS_WORKER && loaderHelpers.config) {
if (loaderHelpers.config.logExitCode) {
if (loaderHelpers.config.forwardConsoleLogsToWS) {
teardown_proxy_console("WASM EXIT " + exit_code);
Expand Down Expand Up @@ -294,3 +305,10 @@ function fatal_handler (event: any, reason: any, type: string) {
// no not re-throw from the fatal handler
}
}

function createExitStatus (status:number, message:string) {
const ex = new runtimeHelpers.ExitStatus(status);
ex.message = message;
ex.toString = () => message;
return ex;
}
7 changes: 3 additions & 4 deletions src/mono/browser/runtime/loader/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ export function mono_log_error (msg: string, ...data: any) {
if (data[0].silent) {
return;
}
if (data[0].toString) {
console.error(prefix + msg, data[0].toString());
}
if (data[0].toString) {
console.error(prefix + msg, data[0].toString());
return;
Expand Down Expand Up @@ -118,12 +115,13 @@ export function setup_proxy_console (id: string, console: Console, origin: strin
}

export function teardown_proxy_console (message?: string) {
let counter = 30;
const stop_when_ws_buffer_empty = () => {
if (!consoleWebSocket) {
if (message && originalConsoleMethods) {
originalConsoleMethods.log(message);
}
} else if (consoleWebSocket.bufferedAmount == 0) {
} else if (consoleWebSocket.bufferedAmount == 0 || counter == 0) {
if (message) {
// tell xharness WasmTestMessagesProcessor we are done.
// note this sends last few bytes into the same WS
Expand All @@ -136,6 +134,7 @@ export function teardown_proxy_console (message?: string) {
consoleWebSocket.close(1000, message);
(consoleWebSocket as any) = undefined;
} else {
counter--;
globalThis.setTimeout(stop_when_ws_buffer_empty, 100);
}
};
Expand Down
39 changes: 34 additions & 5 deletions src/mono/browser/runtime/logging.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/* eslint-disable no-console */
import { INTERNAL, runtimeHelpers, mono_assert } from "./globals";
import WasmEnableThreads from "consts:wasmEnableThreads";

import { threads_c_functions as tcwraps } from "./cwraps";
import { INTERNAL, runtimeHelpers, mono_assert, loaderHelpers, ENVIRONMENT_IS_WORKER, Module } from "./globals";
import { utf8ToString } from "./strings";
import { CharPtr, VoidPtr } from "./types/emscripten";

Expand All @@ -12,6 +14,7 @@ export function set_thread_prefix (threadPrefix: string) {
prefix = `[${threadPrefix}] MONO_WASM: `;
}

/* eslint-disable no-console */
export function mono_log_debug (msg: string, ...data: any) {
if (runtimeHelpers.diagnosticTracing) {
console.debug(prefix + msg, ...data);
Expand All @@ -27,9 +30,15 @@ export function mono_log_warn (msg: string, ...data: any) {
}

export function mono_log_error (msg: string, ...data: any) {
if (data && data.length > 0 && data[0] && typeof data[0] === "object" && data[0].silent) {
if (data && data.length > 0 && data[0] && typeof data[0] === "object") {
// don't log silent errors
return;
if (data[0].silent) {
return;
}
if (data[0].toString) {
console.error(prefix + msg, data[0].toString());
return;
}
}
console.error(prefix + msg, ...data);
}
Expand Down Expand Up @@ -123,7 +132,27 @@ export function mono_wasm_trace_logger (log_domain_ptr: CharPtr, log_level_ptr:
switch (log_level) {
case "critical":
case "error":
console.error(mono_wasm_stringify_as_error_with_stack(message));
{
const messageWithStack = message + "\n" + (new Error().stack);
if (!loaderHelpers.exitReason) {
loaderHelpers.exitReason = messageWithStack;
}
console.error(mono_wasm_stringify_as_error_with_stack(messageWithStack));
if (WasmEnableThreads) {
try {
tcwraps.mono_wasm_print_thread_dump();
} catch (e) {
console.error("Failed to print thread dump", e);
}
}
if (WasmEnableThreads && ENVIRONMENT_IS_WORKER) {
setTimeout(() => {
mono_log_error("forcing abort 3000ms after last error log message", messageWithStack);
// _emscripten_force_exit is proxied to UI thread and should also arrive in spin wait loop
Module._emscripten_force_exit(1);
}, 3000);
}
}
break;
case "warning":
console.warn(message);
Expand Down
4 changes: 2 additions & 2 deletions src/mono/browser/runtime/managed-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { assert_c_interop, assert_js_interop } from "./invoke-js";
import { monoThreadInfo, mono_wasm_main_thread_ptr } from "./pthreads";
import { _zero_region, copyBytes } from "./memory";
import { stringToUTF8Ptr } from "./strings";
import { mono_log_debug } from "./logging";
import { mono_log_error } from "./logging";

const managedExports: ManagedExports = {} as any;

Expand Down Expand Up @@ -269,7 +269,7 @@ export function install_main_synchronization_context (jsThreadBlockingMode: JSTh
}
return get_arg_gc_handle(res) as any;
} catch (e) {
mono_log_debug("install_main_synchronization_context failed", e);
mono_log_error("install_main_synchronization_context failed", e);
throw e;
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/mono/browser/runtime/pthreads/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { mono_log_warn } from "../logging";
import { utf16ToString } from "../strings";

export {
mono_wasm_main_thread_ptr, mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop,
mono_wasm_main_thread_ptr,
mono_wasm_pthread_ptr, update_thread_info, isMonoThreadMessage, monoThreadInfo,
} from "./shared";
export { mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop } from "./worker-interop";
export {
mono_wasm_dump_threads, cancelThreads,
mono_wasm_dump_threads, postCancelThreads,
populateEmscriptenPool, mono_wasm_init_threads,
waitForThread, replaceEmscriptenPThreadUI
waitForThread, replaceEmscriptenPThreadUI, terminateAllThreads,
} from "./ui-thread";
export {
mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_unregistered,
Expand Down
39 changes: 2 additions & 37 deletions src/mono/browser/runtime/pthreads/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import BuildConfiguration from "consts:configuration";

import type { GCHandle, MonoThreadMessage, PThreadInfo, PThreadPtr } from "../types/internal";

import { ENVIRONMENT_IS_PTHREAD, Module, loaderHelpers, mono_assert, runtimeHelpers } from "../globals";
import { Module, loaderHelpers, runtimeHelpers } from "../globals";
import { set_thread_prefix } from "../logging";
import { bindings_init } from "../startup";
import { forceDisposeProxies } from "../gc-handles";
import { monoMessageSymbol, GCHandleNull, PThreadPtrNull, WorkerToMainMessageType } from "../types/internal";
import { monoMessageSymbol, PThreadPtrNull, WorkerToMainMessageType } from "../types/internal";
import { threads_c_functions as tcwraps } from "../cwraps";
import { forceThreadMemoryViewRefresh } from "../memory";

Expand All @@ -34,39 +32,6 @@ export function isMonoThreadMessage (x: unknown): x is MonoThreadMessage {
return typeof (xmsg.type) === "string" && typeof (xmsg.cmd) === "string";
}

export function mono_wasm_install_js_worker_interop (context_gc_handle: GCHandle): void {
if (!WasmEnableThreads) return;
bindings_init();
mono_assert(!runtimeHelpers.proxyGCHandle, "JS interop should not be already installed on this worker.");
runtimeHelpers.proxyGCHandle = context_gc_handle;
if (ENVIRONMENT_IS_PTHREAD) {
runtimeHelpers.managedThreadTID = runtimeHelpers.currentThreadTID;
runtimeHelpers.isManagedRunningOnCurrentThread = true;
}
Module.runtimeKeepalivePush();
monoThreadInfo.isDirtyBecauseOfInterop = true;
update_thread_info();
if (ENVIRONMENT_IS_PTHREAD) {
postMessageToMain({
monoCmd: WorkerToMainMessageType.enabledInterop,
info: monoThreadInfo,
});
}
}

export function mono_wasm_uninstall_js_worker_interop (): void {
if (!WasmEnableThreads) return;
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "JS interop is not installed on this worker.");
mono_assert(runtimeHelpers.proxyGCHandle, "JSSynchronizationContext is not installed on this worker.");

forceDisposeProxies(true, runtimeHelpers.diagnosticTracing);
Module.runtimeKeepalivePop();

runtimeHelpers.proxyGCHandle = GCHandleNull;
runtimeHelpers.mono_wasm_bindings_is_ready = false;
update_thread_info();
}

// this is just for Debug build of the runtime, making it easier to debug worker threads
export function update_thread_info (): void {
if (!WasmEnableThreads) return;
Expand Down
Loading

0 comments on commit 552b5e9

Please sign in to comment.