From b56b791ee5304c527a2e0ea39a800aafeab21187 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 27 Oct 2023 14:55:09 +0200 Subject: [PATCH 1/5] rebase and more changes --- .../src/System/ConsolePal.WebAssembly.cs | 3 - .../JavaScript/SecondRuntimeTest.js | 6 + src/mono/wasm/runtime/driver.c | 2 + src/mono/wasm/runtime/es6/dotnet.es6.lib.js | 2 - src/mono/wasm/runtime/loader/exit.ts | 88 ++++++--- src/mono/wasm/runtime/loader/logging.ts | 168 +++++++++++------- src/mono/wasm/runtime/loader/run.ts | 4 +- src/mono/wasm/runtime/polyfills.ts | 3 +- src/mono/wasm/runtime/run.ts | 8 +- src/mono/wasm/runtime/startup.ts | 46 ++--- src/mono/wasm/runtime/types/internal.ts | 2 +- src/mono/wasm/test-main.js | 13 +- src/mono/wasm/wasm.proj | 2 +- src/tests/Common/wasm-test-runner/index.html | 2 +- 14 files changed, 216 insertions(+), 133 deletions(-) diff --git a/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs b/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs index abab79436cac17..0498e973bd0f69 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs @@ -6,9 +6,6 @@ using Microsoft.Win32.SafeHandles; using System.Runtime.InteropServices.JavaScript; -#pragma warning disable CS0612 // using obsolete members until we finish https://github.com/dotnet/runtime/pull/66304/ -#pragma warning disable IDE0060 - namespace System { internal sealed class WasmConsoleStream : ConsoleStream diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js index 7302b1c061bc3b..a4c018258f9b21 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js @@ -2,6 +2,12 @@ export async function runSecondRuntimeAndTestStaticState() { const { dotnet: dotnet2 } = await import('./_framework/dotnet.js?instance=2'); const runtime2 = await dotnet2 .withStartupMemoryCache(false) + .withConfig({ + forwardConsoleLogsToWS: false, + diagnosticTracing: false, + appendElementOnExit: false, + logExitCode: false, + }) .create(); const increment1 = await getIncrementStateFunction(App.runtime); diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index c4e0679579c4a7..8b7979477bbd10 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -1186,6 +1186,8 @@ EMSCRIPTEN_KEEPALIVE int mono_wasm_exit (int exit_code) { mono_jit_cleanup (root_domain); + fflush (stdout); + fflush (stderr); exit (exit_code); } diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index faa44aef4465bb..b46e3630e5dca7 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -25,7 +25,6 @@ function setup(linkerSetup) { updateMemoryViews, pthreadReplacements, scriptDirectory, - noExitRuntime }; // USE_PTHREADS is emscripten's define symbol, which is passed to acorn optimizer, so we could use it here #if USE_PTHREADS @@ -39,7 +38,6 @@ function setup(linkerSetup) { ENVIRONMENT_IS_WORKER = dotnet_replacements.ENVIRONMENT_IS_WORKER; Module.__dotnet_runtime.initializeReplacements(dotnet_replacements); updateMemoryViews = dotnet_replacements.updateMemoryViews; - noExitRuntime = dotnet_replacements.noExitRuntime; fetch = dotnet_replacements.fetch; require = dotnet_replacements.require; _scriptDir = __dirname = scriptDirectory = dotnet_replacements.scriptDirectory; diff --git a/src/mono/wasm/runtime/loader/exit.ts b/src/mono/wasm/runtime/loader/exit.ts index 4c7099e4279e31..13729e40468ae2 100644 --- a/src/mono/wasm/runtime/loader/exit.ts +++ b/src/mono/wasm/runtime/loader/exit.ts @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, INTERNAL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; -import { mono_log_debug, consoleWebSocket, mono_log_error, mono_log_info_no_prefix, mono_log_warn } from "./logging"; +import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, INTERNAL, emscriptenModule, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { mono_log_debug, mono_log_error, mono_log_info_no_prefix, mono_log_warn, teardown_proxy_console } from "./logging"; export function is_exited() { return loaderHelpers.exitCode !== undefined; @@ -14,11 +14,39 @@ export function is_runtime_running() { export function assert_runtime_running() { mono_assert(runtimeHelpers.runtimeReady, "mono runtime didn't start yet"); - mono_assert(!loaderHelpers.assertAfterExit || !is_exited(), () => `mono runtime already exited with ${loaderHelpers.exitCode}`); + mono_assert(!loaderHelpers.assertAfterExit || !is_exited(), () => `mono runtime already exited with ${loaderHelpers.exitCode} ${loaderHelpers.exitReason}`); +} + +export function register_exit_handlers() { + if (!emscriptenModule.onAbort) { + emscriptenModule.onAbort = onAbort; + } + if (!emscriptenModule.onExit) { + emscriptenModule.onExit = onExit; + } +} + +export function unregister_exit_handlers() { + if (emscriptenModule.onAbort == onAbort) { + emscriptenModule.onAbort = undefined; + } + if (emscriptenModule.onExit == onExit) { + emscriptenModule.onExit = undefined; + } +} + +function onExit(code: number) { + mono_exit(code, loaderHelpers.exitReason); +} + +function onAbort(reason: any) { + mono_exit(1, loaderHelpers.exitReason || reason); } // 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 { + unregister_exit_handlers(); + // unify shape of the reason object const is_object = reason && typeof reason === "object"; exit_code = (is_object && typeof reason.status === "number") ? reason.status : exit_code; @@ -48,13 +76,23 @@ export function mono_exit(exit_code: number, reason?: any): void { if (!runtimeHelpers.runtimeReady) { mono_log_debug("abort_startup, reason: " + reason); abort_promises(reason); + } else { + if (runtimeHelpers.jiterpreter_dump_stats) { + runtimeHelpers.jiterpreter_dump_stats(false); + } + if (exit_code === 0 && loaderHelpers.config?.interopCleanupOnExit) { + runtimeHelpers.forceDisposeProxies(true, true); + } } + } + catch (err) { + mono_log_warn("mono_exit failed", err); + // don't propagate any failures + } + + try { logOnExit(exit_code, reason); appendElementOnExit(exit_code); - if (runtimeHelpers.jiterpreter_dump_stats) runtimeHelpers.jiterpreter_dump_stats(false); - if (exit_code === 0 && loaderHelpers.config?.interopCleanupOnExit) { - runtimeHelpers.forceDisposeProxies(true, true); - } } catch (err) { mono_log_warn("mono_exit failed", err); @@ -62,6 +100,11 @@ export function mono_exit(exit_code: number, reason?: any): void { } loaderHelpers.exitCode = exit_code; + loaderHelpers.exitReason = reason.message; + + if (!ENVIRONMENT_IS_WORKER && runtimeHelpers.runtimeReady) { + emscriptenModule.runtimeKeepalivePop(); + } } if (loaderHelpers.config && loaderHelpers.config.asyncFlushOnExit && exit_code === 0) { @@ -83,7 +126,8 @@ export function mono_exit(exit_code: number, reason?: any): void { } function set_exit_code_and_quit_now(exit_code: number, reason?: any): void { - if (is_runtime_running() && runtimeHelpers.mono_wasm_exit) { + if (runtimeHelpers.runtimeReady && runtimeHelpers.mono_wasm_exit) { + runtimeHelpers.runtimeReady = false; runtimeHelpers.mono_wasm_exit(exit_code); } // just in case mono_wasm_exit didn't exit or throw @@ -118,6 +162,7 @@ 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); @@ -167,23 +212,16 @@ function logOnExit(exit_code: number, reason: any) { mono_log(JSON.stringify(reason)); } } - if (loaderHelpers.config && loaderHelpers.config.logExitCode) { - if (consoleWebSocket) { - const stop_when_ws_buffer_empty = () => { - if (consoleWebSocket.bufferedAmount == 0) { - // tell xharness WasmTestMessagesProcessor we are done. - // note this sends last few bytes into the same WS - mono_log_info_no_prefix("WASM EXIT " + exit_code); - consoleWebSocket.onclose = null; - consoleWebSocket.close(1000, "exit_code:" + exit_code + ": " + reason); - } - else { - globalThis.setTimeout(stop_when_ws_buffer_empty, 100); - } - }; - stop_when_ws_buffer_empty(); - } else { - mono_log_info_no_prefix("WASM EXIT " + exit_code); + if (loaderHelpers.config) { + if (loaderHelpers.config.logExitCode) { + if (loaderHelpers.config.forwardConsoleLogsToWS) { + teardown_proxy_console("WASM EXIT " + exit_code); + } else { + mono_log_info_no_prefix("WASM EXIT " + exit_code); + } + } + else if (loaderHelpers.config.forwardConsoleLogsToWS) { + teardown_proxy_console(); } } } diff --git a/src/mono/wasm/runtime/loader/logging.ts b/src/mono/wasm/runtime/loader/logging.ts index 938938f229d34f..ed0a76feaf7153 100644 --- a/src/mono/wasm/runtime/loader/logging.ts +++ b/src/mono/wasm/runtime/loader/logging.ts @@ -4,7 +4,12 @@ /* eslint-disable no-console */ import { loaderHelpers } from "./globals"; +const methods = ["debug", "log", "trace", "warn", "info", "error"]; const prefix = "MONO_WASM: "; +let consoleWebSocket: WebSocket; +let theConsoleApi: any; +let originalConsoleMethods: any; +let threadId: string; export function mono_log_debug(msg: string, ...data: any) { if (loaderHelpers.diagnosticTracing) { @@ -31,88 +36,123 @@ export function mono_log_error(msg: string, ...data: any) { } console.error(prefix + msg, ...data); } -export let consoleWebSocket: WebSocket; -export function setup_proxy_console(id: string, console: Console, origin: string): void { - // this need to be copy, in order to keep reference to original methods - const originalConsole = { - log: console.log, - error: console.error - }; - const anyConsole = console as any; - - function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { - return function (...args: any[]) { - try { - let payload = args[0]; - if (payload === undefined) payload = "undefined"; - else if (payload === null) payload = "null"; - else if (typeof payload === "function") payload = payload.toString(); - else if (typeof payload !== "string") { - try { - payload = JSON.stringify(payload); - } catch (e) { - payload = payload.toString(); - } +function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { + return function (...args: any[]) { + try { + let payload = args[0]; + if (payload === undefined) payload = "undefined"; + else if (payload === null) payload = "null"; + else if (typeof payload === "function") payload = payload.toString(); + else if (typeof payload !== "string") { + try { + payload = JSON.stringify(payload); + } catch (e) { + payload = payload.toString(); } + } - if (typeof payload === "string") { - if (payload[0] == "[") { - const now = new Date().toISOString(); - if (id !== "main") { - payload = `[${id}][${now}] ${payload}`; - } else { - payload = `[${now}] ${payload}`; - } - } else if (id !== "main") { - payload = `[${id}] ${payload}`; + if (typeof payload === "string") { + if (payload[0] == "[") { + const now = new Date().toISOString(); + if (threadId !== "main") { + payload = `[${threadId}][${now}] ${payload}`; + } else { + payload = `[${now}] ${payload}`; } + } else if (threadId !== "main") { + payload = `[${threadId}] ${payload}`; } - - if (asJson) { - func(JSON.stringify({ - method: prefix, - payload: payload, - arguments: args.slice(1) - })); - } else { - func([prefix + payload, ...args.slice(1)]); - } - } catch (err) { - originalConsole.error(`proxyConsole failed: ${err}`); } - }; - } - const methods = ["debug", "trace", "warn", "info", "error"]; - for (const m of methods) { - if (typeof (anyConsole[m]) !== "function") { - anyConsole[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); + if (asJson) { + func(JSON.stringify({ + method: prefix, + payload: payload, + arguments: args.slice(1) + })); + } else { + func([prefix + payload, ...args.slice(1)]); + } + } catch (err) { + originalConsoleMethods.error(`proxyConsole failed: ${err}`); } - } + }; +} + +export function setup_proxy_console(id: string, console: Console, origin: string): void { + theConsoleApi = console as any; + threadId = id; + originalConsoleMethods = { + ...console + }; + + setupWS(); const consoleUrl = `${origin}/console`.replace("https://", "wss://").replace("http://", "ws://"); consoleWebSocket = new WebSocket(consoleUrl); + consoleWebSocket.addEventListener("error", logWSError); + consoleWebSocket.addEventListener("close", logWSClose); consoleWebSocket.addEventListener("open", () => { - originalConsole.log(`browser: [${id}] Console websocket connected.`); - }); - consoleWebSocket.addEventListener("error", (event) => { - originalConsole.error(`[${id}] websocket error: ${event}`, event); - }); - consoleWebSocket.addEventListener("close", (event) => { - originalConsole.error(`[${id}] websocket closed: ${event}`, event); + originalConsoleMethods.log(`browser: [${threadId}] Console websocket connected.`); + }, { + once: true }); +} - const send = (msg: string) => { - if (consoleWebSocket.readyState === WebSocket.OPEN) { - consoleWebSocket.send(msg); +export function teardown_proxy_console(message?: string) { + const stop_when_ws_buffer_empty = () => { + if (!consoleWebSocket) { + if (message && originalConsoleMethods) { + originalConsoleMethods.log(message); + } + } + else if (consoleWebSocket.bufferedAmount == 0) { + if (message) { + // tell xharness WasmTestMessagesProcessor we are done. + // note this sends last few bytes into the same WS + mono_log_info_no_prefix(message); + } + setupOriginal(); + + consoleWebSocket.removeEventListener("error", logWSError); + consoleWebSocket.removeEventListener("close", logWSClose); + consoleWebSocket.close(1000, message); + (consoleWebSocket as any) = undefined; } else { - originalConsole.log(msg); + globalThis.setTimeout(stop_when_ws_buffer_empty, 100); } }; + stop_when_ws_buffer_empty(); +} - for (const m of ["log", ...methods]) - anyConsole[m] = proxyConsoleMethod(`console.${m}`, send, true); +function send(msg: string) { + if (consoleWebSocket.readyState === WebSocket.OPEN) { + consoleWebSocket.send(msg); + } + else { + originalConsoleMethods.log(msg); + } +} + +function logWSError(event: Event) { + originalConsoleMethods.error(`[${threadId}] websocket error: ${event}`, event); +} + +function logWSClose(event: Event) { + originalConsoleMethods.error(`[${threadId}] websocket closed: ${event}`, event); +} + +function setupWS() { + for (const m of methods) { + theConsoleApi[m] = proxyConsoleMethod(`console.${m}`, send, true); + } +} + +function setupOriginal() { + for (const m of methods) { + theConsoleApi[m] = proxyConsoleMethod(`console.${m}`, originalConsoleMethods.log, false); + } } diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 688b4bb0112d64..e659058770258a 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -8,7 +8,7 @@ import type { MonoConfigInternal, EmscriptenModuleInternal, RuntimeModuleExports import { ENVIRONMENT_IS_WEB, emscriptenModule, exportedRuntimeAPI, globalObjectsRoot, monoConfig, mono_assert } from "./globals"; import { deep_merge_config, deep_merge_module, mono_wasm_load_config } from "./config"; -import { mono_exit } from "./exit"; +import { mono_exit, register_exit_handlers } from "./exit"; import { setup_proxy_console, mono_log_info, mono_log_debug } from "./logging"; import { mono_download_assets, prepareAssets, prepareAssetsWorker, resolve_single_asset_path, streamingCompileWasm } from "./assets"; import { detect_features_and_polyfill } from "./polyfills"; @@ -411,6 +411,8 @@ export async function createEmscripten(moduleFactory: DotnetModuleConfig | ((api mono_log_info(`starting in ${loaderHelpers.scriptDirectory}`); } + register_exit_handlers(); + return emscriptenModule.ENVIRONMENT_IS_PTHREAD ? createEmscriptenWorker() : createEmscriptenMain(); diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts index b08f392c3b3111..ba171251d09fe1 100644 --- a/src/mono/wasm/runtime/polyfills.ts +++ b/src/mono/wasm/runtime/polyfills.ts @@ -4,7 +4,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import type { EmscriptenReplacements } from "./types/internal"; import type { TypedArray } from "./types/emscripten"; -import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_PTHREAD, ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, INTERNAL, Module, loaderHelpers, runtimeHelpers } from "./globals"; +import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WORKER, INTERNAL, Module, loaderHelpers, runtimeHelpers } from "./globals"; import { replaceEmscriptenPThreadLibrary } from "./pthreads/shared/emscripten-replacements"; const dummyPerformance = { @@ -30,7 +30,6 @@ export function initializeReplacements(replacements: EmscriptenReplacements): vo replacements.fetch = loaderHelpers.fetch_like; // misc - replacements.noExitRuntime = ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_PTHREAD; replacements.ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_WORKER; // threads diff --git a/src/mono/wasm/runtime/run.ts b/src/mono/wasm/runtime/run.ts index 5d056c24e33d76..cc82cca657ac28 100644 --- a/src/mono/wasm/runtime/run.ts +++ b/src/mono/wasm/runtime/run.ts @@ -55,7 +55,13 @@ export async function mono_run_main(main_assembly_name: string, args?: string[]) await mono_wasm_wait_for_debugger(); } const method = find_entry_point(main_assembly_name); - return runtimeHelpers.javaScriptExports.call_entry_point(method, args); + + const res = await runtimeHelpers.javaScriptExports.call_entry_point(method, args); + + // one more timer loop before we return, so that any remaining queued calls could run + await new Promise(resolve => globalThis.setTimeout(resolve, 0)); + + return res; } export function find_entry_point(assembly: string) { diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 4b209c8f1fa3a1..d98791e1d13b00 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -5,7 +5,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import WasmEnableLegacyJsInterop from "consts:wasmEnableLegacyJsInterop"; import { DotnetModuleInternal, CharPtrNull } from "./types/internal"; -import { linkerDisableLegacyJsInterop, ENVIRONMENT_IS_PTHREAD, exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert, linkerWasmEnableSIMD, linkerWasmEnableEH, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WORKER } from "./globals"; +import { linkerDisableLegacyJsInterop, ENVIRONMENT_IS_PTHREAD, exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert, linkerWasmEnableSIMD, linkerWasmEnableEH, ENVIRONMENT_IS_WORKER } from "./globals"; import cwraps, { init_c_exports } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { toBase64StringImpl } from "./base64"; @@ -102,17 +102,6 @@ export function configureEmscriptenStartup(module: DotnetModuleInternal): void { runtimeHelpers.dotnetReady.promise_control.reject(err); }); module.ready = runtimeHelpers.dotnetReady.promise; - // execution order == [*] == - if (!module.onAbort) { - module.onAbort = (error) => { - loaderHelpers.mono_exit(1, error); - }; - } - if (!module.onExit) { - module.onExit = (code) => { - loaderHelpers.mono_exit(code, null); - }; - } } function instantiateWasm( @@ -236,6 +225,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { runtimeHelpers.mono_wasm_exit = cwraps.mono_wasm_exit; runtimeHelpers.abort = (reason: any) => { + loaderHelpers.exitReason = reason; if (!loaderHelpers.is_exited()) { cwraps.mono_wasm_abort(); } @@ -267,17 +257,28 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { return; } + if (!ENVIRONMENT_IS_WORKER) { + Module.runtimeKeepalivePush(); + } + runtimeHelpers.runtimeReady = true; + + if (runtimeHelpers.config.virtualWorkingDirectory) { + const FS = Module.FS; + const cwd = runtimeHelpers.config.virtualWorkingDirectory; + const wds = FS.stat(cwd); + if (!wds) { + Module.FS_createPath("/", cwd, true, true); + } + mono_assert(wds && FS.isDir(wds.mode), () => `FS.chdir: ${cwd} is not a directory`); + FS.chdir(cwd); + } + if (MonoWasmThreads && runtimeHelpers.config.startupMemoryCache) { await mono_wasm_init_threads(); } bindings_init(); jiterpreter_allocate_tables(Module); - runtimeHelpers.runtimeReady = true; - - if (ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER) { - Module.runtimeKeepalivePush(); - } if (MonoWasmThreads) { runtimeHelpers.javaScriptExports.install_synchronization_context(); @@ -540,17 +541,6 @@ async function mono_wasm_before_memory_snapshot() { mono_wasm_load_runtime("unused", runtimeHelpers.config.debugLevel); - if (runtimeHelpers.config.virtualWorkingDirectory) { - const FS = Module.FS; - const cwd = runtimeHelpers.config.virtualWorkingDirectory; - const wds = FS.stat(cwd); - if (!wds) { - Module.FS_createPath("/", cwd, true, true); - } - mono_assert(wds && FS.isDir(wds.mode), () => `FS.chdir: ${cwd} is not a directory`); - FS.chdir(cwd); - } - // we didn't have snapshot yet and the feature is enabled. Take snapshot now. if (runtimeHelpers.config.startupMemoryCache) { await storeMemorySnapshot(localHeapViewU8().buffer); diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 840edbabe308e3..53fb458d6a4323 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -109,6 +109,7 @@ export type LoaderHelpers = { assertAfterExit: boolean; exitCode: number | undefined; + exitReason: any; loadedFiles: string[], _loaded_files: { url: string, file: string }[]; @@ -303,7 +304,6 @@ export type EmscriptenReplacements = { updateMemoryViews: Function, pthreadReplacements: PThreadReplacements | undefined | null scriptDirectory: string; - noExitRuntime?: boolean; ENVIRONMENT_IS_WORKER: boolean; } export interface ExitStatusError { diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 9b623972e20ce0..18bf70a8dde864 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -321,6 +321,7 @@ async function dry_run(runArgs) { logExitCode: false, virtualWorkingDirectory: undefined, pthreadPoolSize: 0, + interopCleanupOnExit: false, // this just means to not continue startup after the snapshot is taken. // If there was previously a matching snapshot, it will be used. exitAfterSnapshot: true @@ -342,10 +343,14 @@ async function run() { console.log("Application arguments: " + runArgs.applicationArguments.join(' ')); if (ENVIRONMENT_IS_WEB && runArgs.memorySnapshot) { - const dryOk = await dry_run(runArgs); - if (!dryOk) { - mono_exit(1, "Failed during dry run"); - return; + if (globalThis.isSecureContext) { + const dryOk = await dry_run(runArgs); + if (!dryOk) { + mono_exit(1, "Failed during dry run"); + return; + } + } else { + console.log("Skipping dry run as the context is not secure and the snapshot would be not trusted."); } } diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 1afe827d1da038..6ed392341e6b66 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -278,7 +278,7 @@ <_EmccLinkFlags Include="-s ALLOW_MEMORY_GROWTH=1" /> <_EmccLinkFlags Include="-s ALLOW_TABLE_GROWTH=1" /> - <_EmccLinkFlags Include="-s NO_EXIT_RUNTIME=1" /> + <_EmccLinkFlags Include="-s NO_EXIT_RUNTIME=0" /> <_EmccLinkFlags Include="-s FORCE_FILESYSTEM=1" /> <_EmccLinkFlags Condition="'$(_EmccExportedLibraryFunction)' != ''" Include="-s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$(_EmccExportedLibraryFunction)" /> <_EmccLinkFlags Include="-s EXPORTED_RUNTIME_METHODS=$(_EmccExportedRuntimeMethods)" /> diff --git a/src/tests/Common/wasm-test-runner/index.html b/src/tests/Common/wasm-test-runner/index.html index aa53c88f3289d6..ff7d959f164d40 100644 --- a/src/tests/Common/wasm-test-runner/index.html +++ b/src/tests/Common/wasm-test-runner/index.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From a5e81992c7ccaf8a07ad4c9fa90a60241b9c0735 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 27 Oct 2023 19:31:45 +0200 Subject: [PATCH 2/5] nodeJS flush max 1000ms --- src/mono/wasm/runtime/loader/exit.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/exit.ts b/src/mono/wasm/runtime/loader/exit.ts index 13729e40468ae2..129e987805e94f 100644 --- a/src/mono/wasm/runtime/loader/exit.ts +++ b/src/mono/wasm/runtime/loader/exit.ts @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { delay } from "./assets"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, INTERNAL, emscriptenModule, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { mono_log_debug, mono_log_error, mono_log_info_no_prefix, mono_log_warn, teardown_proxy_console } from "./logging"; @@ -155,7 +156,8 @@ async function flush_node_streams() { }; const stderrFlushed = flushStream(process.stderr); const stdoutFlushed = flushStream(process.stdout); - await Promise.all([stdoutFlushed, stderrFlushed]); + const timeout = delay(1000); + await Promise.race([Promise.all([stdoutFlushed, stderrFlushed]), timeout]); } catch (err) { mono_log_error(`flushing std* streams failed: ${err}`); } From ed9fe7f0105785adcd10bab3c01a64d52dd4dbbc Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 30 Oct 2023 12:03:10 +0100 Subject: [PATCH 3/5] fix node flush --- src/mono/wasm/runtime/loader/exit.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/runtime/loader/exit.ts b/src/mono/wasm/runtime/loader/exit.ts index 129e987805e94f..e7a394c05a2320 100644 --- a/src/mono/wasm/runtime/loader/exit.ts +++ b/src/mono/wasm/runtime/loader/exit.ts @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { delay } from "./assets"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, INTERNAL, emscriptenModule, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { mono_log_debug, mono_log_error, mono_log_info_no_prefix, mono_log_warn, teardown_proxy_console } from "./logging"; @@ -150,14 +149,18 @@ async function flush_node_streams() { const process = await import(/* webpackIgnore: true */"process"); const flushStream = (stream: any) => { return new Promise((resolve, reject) => { - stream.on("error", (error: any) => reject(error)); - stream.write("", function () { resolve(); }); + stream.on("error", reject); + stream.end("", "UFT8", resolve); }); }; const stderrFlushed = flushStream(process.stderr); const stdoutFlushed = flushStream(process.stdout); - const timeout = delay(1000); + let timeoutId; + const timeout = new Promise(resolve => { + timeoutId = setTimeout(() => resolve("timeout"), 1000) + }); await Promise.race([Promise.all([stdoutFlushed, stderrFlushed]), timeout]); + clearTimeout(timeoutId); } catch (err) { mono_log_error(`flushing std* streams failed: ${err}`); } From 5de158e7536a62abf3a3f363a32d592fa0a1ed25 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 30 Oct 2023 15:51:54 +0100 Subject: [PATCH 4/5] argh --- src/mono/wasm/runtime/loader/exit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/exit.ts b/src/mono/wasm/runtime/loader/exit.ts index e7a394c05a2320..d293a354173961 100644 --- a/src/mono/wasm/runtime/loader/exit.ts +++ b/src/mono/wasm/runtime/loader/exit.ts @@ -157,7 +157,7 @@ async function flush_node_streams() { const stdoutFlushed = flushStream(process.stdout); let timeoutId; const timeout = new Promise(resolve => { - timeoutId = setTimeout(() => resolve("timeout"), 1000) + timeoutId = setTimeout(() => resolve("timeout"), 1000); }); await Promise.race([Promise.all([stdoutFlushed, stderrFlushed]), timeout]); clearTimeout(timeoutId); From a6b2b610881c190eb344effb3c2927fa43b0de81 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 31 Oct 2023 14:29:34 +0100 Subject: [PATCH 5/5] emscripten_force_exit --- src/mono/wasm/runtime/driver.c | 2 +- src/mono/wasm/runtime/loader/exit.ts | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 8b7979477bbd10..ba27e78f538a59 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -1188,7 +1188,7 @@ mono_wasm_exit (int exit_code) mono_jit_cleanup (root_domain); fflush (stdout); fflush (stderr); - exit (exit_code); + emscripten_force_exit (exit_code); } EMSCRIPTEN_KEEPALIVE int diff --git a/src/mono/wasm/runtime/loader/exit.ts b/src/mono/wasm/runtime/loader/exit.ts index d293a354173961..8093adcbabe062 100644 --- a/src/mono/wasm/runtime/loader/exit.ts +++ b/src/mono/wasm/runtime/loader/exit.ts @@ -128,7 +128,14 @@ export function mono_exit(exit_code: number, reason?: any): void { function set_exit_code_and_quit_now(exit_code: number, reason?: any): void { if (runtimeHelpers.runtimeReady && runtimeHelpers.mono_wasm_exit) { runtimeHelpers.runtimeReady = false; - runtimeHelpers.mono_wasm_exit(exit_code); + try { + runtimeHelpers.mono_wasm_exit(exit_code); + } + catch (err) { + if (runtimeHelpers.ExitStatus && !(err instanceof runtimeHelpers.ExitStatus)) { + mono_log_warn("mono_wasm_exit failed", err); + } + } } // just in case mono_wasm_exit didn't exit or throw if (exit_code !== 0 || !ENVIRONMENT_IS_WEB) { @@ -150,7 +157,7 @@ async function flush_node_streams() { const flushStream = (stream: any) => { return new Promise((resolve, reject) => { stream.on("error", reject); - stream.end("", "UFT8", resolve); + stream.end("", "utf8", resolve); }); }; const stderrFlushed = flushStream(process.stderr);