diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts index fe7ae5269f2f9..29d384f65795b 100644 --- a/src/mono/browser/runtime/cwraps.ts +++ b/src/mono/browser/runtime/cwraps.ts @@ -9,7 +9,7 @@ import type { MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments } from "./types/internal"; import type { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten"; -import { linkerEnableAotProfiler, linkerEnableBrowserProfiler, Module } from "./globals"; +import { Module, runtimeHelpers } from "./globals"; import { mono_log_error } from "./logging"; import { mono_assert } from "./globals"; @@ -60,8 +60,8 @@ const fn_signatures: SigLine[] = [ [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 - [() => !linkerEnableAotProfiler, "mono_wasm_profiler_init_aot", "void", ["string"]], - [() => !linkerEnableBrowserProfiler, "mono_wasm_profiler_init_aot", "void", ["string"]], + [() => !runtimeHelpers.emscriptenBuildOptions.enableAotProfiler, "mono_wasm_profiler_init_aot", "void", ["string"]], + [() => !runtimeHelpers.emscriptenBuildOptions.enableBrowserProfiler, "mono_wasm_profiler_init_aot", "void", ["string"]], [true, "mono_wasm_profiler_init_browser", "void", ["number"]], [false, "mono_wasm_exec_regression", "number", ["number", "string"]], [false, "mono_wasm_invoke_method_bound", "number", ["number", "number", "number"]], diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index 0d8f2833e3674..406deb7e4bf94 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -609,6 +609,9 @@ type RuntimeAPI = { productVersion: string; gitHash: string; buildConfiguration: string; + wasmEnableThreads: boolean; + wasmEnableSIMD: boolean; + wasmEnableExceptionHandling: boolean; }; } & APIType; type ModuleAPI = { diff --git a/src/mono/browser/runtime/driver.c b/src/mono/browser/runtime/driver.c index bb95a67097351..a7bd6f5966e0b 100644 --- a/src/mono/browser/runtime/driver.c +++ b/src/mono/browser/runtime/driver.c @@ -189,19 +189,6 @@ mono_wasm_load_runtime (const char *unused, int debug_level) mono_wasm_link_icu_shim (); #endif -#ifndef DISABLE_THREADS - monoeg_g_setenv ("MONO_SLEEP_ABORT_LIMIT", "5000", 0); -#endif - - // monoeg_g_setenv ("DOTNET_DebugWriteToStdErr", "1", 0); - -#ifdef DEBUG - // monoeg_g_setenv ("MONO_LOG_LEVEL", "debug", 0); - // monoeg_g_setenv ("MONO_LOG_MASK", "gc", 0); - // Setting this env var allows Diagnostic.Debug to write to stderr. In a browser environment this - // output will be sent to the console. Right now this is the only way to emit debug logging from - // corlib assemblies. -#endif // When the list of app context properties changes, please update RuntimeConfigReservedProperties for // target _WasmGenerateRuntimeConfig in BrowserWasmApp.targets file const char *appctx_keys[2]; @@ -372,7 +359,10 @@ mono_wasm_exec_regression (int verbose_level, char *image) EMSCRIPTEN_KEEPALIVE int mono_wasm_exit (int exit_code) { - mono_jit_cleanup (root_domain); + if (exit_code == 0) + { + mono_jit_cleanup (root_domain); + } fflush (stdout); fflush (stderr); emscripten_force_exit (exit_code); diff --git a/src/mono/browser/runtime/es6/dotnet.es6.lib.js b/src/mono/browser/runtime/es6/dotnet.es6.lib.js index 0a9236a11d3ba..cc1944eeed984 100644 --- a/src/mono/browser/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/browser/runtime/es6/dotnet.es6.lib.js @@ -15,7 +15,7 @@ const RUN_AOT_COMPILATION = process.env.RUN_AOT_COMPILATION === "1"; var methodIndexByName = undefined; var gitHash = undefined; -function setup(linkerSetup) { +function setup(emscriptenBuildOptions) { // USE_PTHREADS is emscripten's define symbol, which is passed to acorn optimizer, so we could use it here #if USE_PTHREADS const modulePThread = PThread; @@ -43,8 +43,7 @@ function setup(linkerSetup) { updateMemoryViews, getMemory: () => { return wasmMemory; }, getWasmIndirectFunctionTable: () => { return wasmTable; }, - ...linkerSetup - }); + }, emscriptenBuildOptions); #if USE_PTHREADS if (ENVIRONMENT_IS_PTHREAD) { @@ -83,11 +82,12 @@ function injectDependencies() { #endif DotnetSupportLib["$DOTNET__postset"] = `DOTNET.setup({ ` + - `linkerWasmEnableSIMD: ${WASM_ENABLE_SIMD ? "true" : "false"},` + - `linkerWasmEnableEH: ${WASM_ENABLE_EH ? "true" : "false"},` + - `linkerEnableAotProfiler: ${ENABLE_AOT_PROFILER ? "true" : "false"}, ` + - `linkerEnableBrowserProfiler: ${ENABLE_BROWSER_PROFILER ? "true" : "false"}, ` + - `linkerRunAOTCompilation: ${RUN_AOT_COMPILATION ? "true" : "false"}, ` + + `wasmEnableSIMD: ${WASM_ENABLE_SIMD ? "true" : "false"},` + + `wasmEnableEH: ${WASM_ENABLE_EH ? "true" : "false"},` + + `enableAotProfiler: ${ENABLE_AOT_PROFILER ? "true" : "false"}, ` + + `enableBrowserProfiler: ${ENABLE_BROWSER_PROFILER ? "true" : "false"}, ` + + `runAOTCompilation: ${RUN_AOT_COMPILATION ? "true" : "false"}, ` + + `wasmEnableThreads: ${USE_PTHREADS ? "true" : "false"}, ` + `gitHash: "${gitHash}", ` + `});`; @@ -96,4 +96,4 @@ function injectDependencies() { } -// var methodIndexByName wil be appended below by the MSBuild in browser.proj +// var methodIndexByName wil be appended below by the MSBuild in browser.proj via exports-linker.ts diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index 58ddb435f41ed..cb286789879b6 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -12,7 +12,7 @@ import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue } from "./jiterpreter-jit-call"; import { mono_wasm_resolve_or_reject_promise } from "./marshal-to-js"; import { mono_wasm_eventloop_has_unsettled_interop_promises } from "./pthreads/shared/eventloop"; -import { mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_unregistered, mono_wasm_pthread_on_pthread_registered } from "./pthreads/worker"; +import { mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_unregistered, mono_wasm_pthread_on_pthread_registered, mono_wasm_pthread_set_name } from "./pthreads/worker"; import { mono_wasm_schedule_timer, schedule_background_exec } from "./scheduling"; import { mono_wasm_asm_loaded } from "./startup"; import { mono_wasm_diagnostic_server_on_server_thread_created } from "./diagnostics/server_pthread"; @@ -37,6 +37,8 @@ export const mono_wasm_threads_imports = !WasmEnableThreads ? [] : [ mono_wasm_pthread_on_pthread_registered, mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_unregistered, + mono_wasm_pthread_set_name, + // threads.c mono_wasm_eventloop_has_unsettled_interop_promises, // diagnostics_server.c diff --git a/src/mono/browser/runtime/exports.ts b/src/mono/browser/runtime/exports.ts index 1cea8798bb4b4..2f3aa96a0ec6b 100644 --- a/src/mono/browser/runtime/exports.ts +++ b/src/mono/browser/runtime/exports.ts @@ -3,6 +3,10 @@ import ProductVersion from "consts:productVersion"; import BuildConfiguration from "consts:configuration"; +import WasmEnableThreads from "consts:wasmEnableThreads"; +import WasmEnableSIMD from "consts:wasmEnableSIMD"; +import WasmEnableExceptionHandling from "consts:wasmEnableExceptionHandling"; + import type { RuntimeAPI } from "./types"; import { Module, exportedRuntimeAPI, loaderHelpers, passEmscriptenInternals, runtimeHelpers, setRuntimeGlobals, } from "./globals"; @@ -18,6 +22,7 @@ import { mono_wasm_stringify_as_error_with_stack } from "./logging"; import { instantiate_asset, instantiate_symbols_asset, instantiate_segmentation_rules_asset } from "./assets"; import { jiterpreter_dump_stats } from "./jiterpreter"; import { forceDisposeProxies } from "./gc-handles"; +import { dumpThreads } from "./pthreads/browser"; export let runtimeList: RuntimeList; @@ -35,6 +40,11 @@ function initializeExports(globalObjects: GlobalObjects): RuntimeAPI { forceDisposeProxies, instantiate_segmentation_rules_asset, }); + if (WasmEnableThreads) { + Object.assign(runtimeHelpers, { + dumpThreads, + }); + } const API = export_api(); Object.assign(exportedRuntimeAPI, { @@ -43,7 +53,10 @@ function initializeExports(globalObjects: GlobalObjects): RuntimeAPI { runtimeBuildInfo: { productVersion: ProductVersion, gitHash: runtimeHelpers.gitHash, - buildConfiguration: BuildConfiguration + buildConfiguration: BuildConfiguration, + wasmEnableThreads: WasmEnableThreads, + wasmEnableSIMD: WasmEnableSIMD, + wasmEnableExceptionHandling: WasmEnableExceptionHandling, }, ...API, }); diff --git a/src/mono/browser/runtime/globals.ts b/src/mono/browser/runtime/globals.ts index bf7b5a01a997b..be2ae8f86bee9 100644 --- a/src/mono/browser/runtime/globals.ts +++ b/src/mono/browser/runtime/globals.ts @@ -9,7 +9,7 @@ import gitHash from "consts:gitHash"; import { RuntimeAPI } from "./types/index"; -import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController } from "./types/internal"; +import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController, EmscriptenBuildOptions } from "./types/internal"; import { mono_log_error } from "./logging"; // these are our public API (except internal) @@ -30,23 +30,14 @@ export let exportedRuntimeAPI: RuntimeAPI = null as any; export let runtimeHelpers: RuntimeHelpers = null as any; export let loaderHelpers: LoaderHelpers = null as any; -export let linkerWasmEnableSIMD = true; -export let linkerWasmEnableEH = true; -export let linkerEnableAotProfiler = false; -export let linkerEnableBrowserProfiler = false; -export let linkerRunAOTCompilation = false; export let _runtimeModuleLoaded = false; // please keep it in place also as rollup guard -export function passEmscriptenInternals(internals: EmscriptenInternals): void { +export function passEmscriptenInternals(internals: EmscriptenInternals, emscriptenBuildOptions: EmscriptenBuildOptions): void { + runtimeHelpers.emscriptenBuildOptions = emscriptenBuildOptions; + ENVIRONMENT_IS_PTHREAD = internals.isPThread; - linkerWasmEnableSIMD = internals.linkerWasmEnableSIMD; - linkerWasmEnableEH = internals.linkerWasmEnableEH; - linkerEnableAotProfiler = internals.linkerEnableAotProfiler; - linkerEnableBrowserProfiler = internals.linkerEnableBrowserProfiler; - linkerRunAOTCompilation = internals.linkerRunAOTCompilation; runtimeHelpers.quit = internals.quit_; runtimeHelpers.ExitStatus = internals.ExitStatus; - runtimeHelpers.moduleGitHash = internals.gitHash; runtimeHelpers.getMemory = internals.getMemory; runtimeHelpers.getWasmIndirectFunctionTable = internals.getWasmIndirectFunctionTable; runtimeHelpers.updateMemoryViews = internals.updateMemoryViews; diff --git a/src/mono/browser/runtime/jiterpreter-support.ts b/src/mono/browser/runtime/jiterpreter-support.ts index cacd4d306695a..dcbba5928a5bc 100644 --- a/src/mono/browser/runtime/jiterpreter-support.ts +++ b/src/mono/browser/runtime/jiterpreter-support.ts @@ -3,7 +3,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { NativePointer, ManagedPointer, VoidPtr } from "./types/emscripten"; -import { Module, mono_assert, runtimeHelpers, linkerRunAOTCompilation } from "./globals"; +import { Module, mono_assert, runtimeHelpers } from "./globals"; import { WasmOpcode, WasmSimdOpcode, WasmValtype } from "./jiterpreter-opcodes"; import { MintOpcode } from "./mintops"; import cwraps from "./cwraps"; @@ -2017,8 +2017,8 @@ export function jiterpreter_allocate_tables() { // then create special placeholder functions that examine the rmethod to determine which kind // of method is being called. const traceTableSize = options.tableSize, - jitCallTableSize = linkerRunAOTCompilation ? options.tableSize : 1, - interpEntryTableSize = linkerRunAOTCompilation ? options.aotTableSize : 1, + jitCallTableSize = runtimeHelpers.emscriptenBuildOptions.runAOTCompilation ? options.tableSize : 1, + interpEntryTableSize = runtimeHelpers.emscriptenBuildOptions.runAOTCompilation ? options.aotTableSize : 1, numInterpEntryTables = JiterpreterTable.LAST - JiterpreterTable.InterpEntryStatic0 + 1, totalSize = traceTableSize + jitCallTableSize + (numInterpEntryTables * interpEntryTableSize) + 1, wasmTable = getWasmFunctionTable(); diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 68f62e2261ddd..9364bf706801f 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -193,6 +193,11 @@ export function normalizeConfig() { config.pthreadPoolSize = 7; } + // this is how long the Mono GC will try to wait for all threads to be suspended before it gives up and aborts the process + if (WasmEnableThreads && config.environmentVariables["MONO_SLEEP_ABORT_LIMIT"] === undefined) { + config.environmentVariables["MONO_SLEEP_ABORT_LIMIT"] = "5000"; + } + // Default values (when WasmDebugLevel is not set) // - Build (debug) => debugBuild=true & debugLevel=-1 => -1 // - Build (release) => debugBuild=true & debugLevel=0 => 0 @@ -200,9 +205,10 @@ export function normalizeConfig() { // - Publish (release) => debugBuild=false & debugLevel=0 => 0 config.debugLevel = hasDebuggingEnabled(config) ? config.debugLevel : 0; - if (config.diagnosticTracing === undefined && BuildConfiguration === "Debug") { + if (BuildConfiguration === "Debug" && config.diagnosticTracing === undefined) { config.diagnosticTracing = true; } + if (config.applicationCulture) { // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. config.environmentVariables!["LANG"] = `${config.applicationCulture}.UTF-8`; diff --git a/src/mono/browser/runtime/loader/exit.ts b/src/mono/browser/runtime/loader/exit.ts index f29bd54d43c10..2078a9f0533ec 100644 --- a/src/mono/browser/runtime/loader/exit.ts +++ b/src/mono/browser/runtime/loader/exit.ts @@ -108,6 +108,9 @@ export function mono_exit(exit_code: number, reason?: any): void { if (exit_code === 0 && loaderHelpers.config?.interopCleanupOnExit) { runtimeHelpers.forceDisposeProxies(true, true); } + if (WasmEnableThreads && exit_code !== 0 && loaderHelpers.config?.dumpThreadsOnNonZeroExit) { + runtimeHelpers.dumpThreads(); + } } } catch (err) { diff --git a/src/mono/browser/runtime/loader/globals.ts b/src/mono/browser/runtime/loader/globals.ts index 8e65efcf4d5b3..ff84a5ddb3581 100644 --- a/src/mono/browser/runtime/loader/globals.ts +++ b/src/mono/browser/runtime/loader/globals.ts @@ -13,7 +13,7 @@ import type { MonoConfig, RuntimeAPI } from "../types"; import { assert_runtime_running, installUnhandledErrorHandler, is_exited, is_runtime_running, mono_exit } from "./exit"; import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; import { mono_download_assets, resolve_single_asset_path, retrieve_asset_download } from "./assets"; -import { mono_log_error, mono_set_thread_name, setup_proxy_console } from "./logging"; +import { mono_log_error, set_thread_prefix, setup_proxy_console } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { deep_merge_config, hasDebuggingEnabled } from "./config"; import { logDownloadStatsToConsole, purgeUnusedCacheEntriesAsync } from "./assetsCache"; @@ -114,7 +114,7 @@ export function setLoaderGlobals( mono_download_assets, resolve_single_asset_path, setup_proxy_console, - mono_set_thread_name, + set_thread_prefix, logDownloadStatsToConsole, purgeUnusedCacheEntriesAsync, installUnhandledErrorHandler, diff --git a/src/mono/browser/runtime/loader/logging.ts b/src/mono/browser/runtime/loader/logging.ts index d61e16f5d11c9..723e55201fcc5 100644 --- a/src/mono/browser/runtime/loader/logging.ts +++ b/src/mono/browser/runtime/loader/logging.ts @@ -14,8 +14,8 @@ let theConsoleApi: any; let originalConsoleMethods: any; let threadNamePrefix: string; -export function mono_set_thread_name(threadName: string) { - threadNamePrefix = threadName; +export function set_thread_prefix(threadPrefix: string) { + threadNamePrefix = threadPrefix; } export function mono_log_debug(msg: string, ...data: any[]) { diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index 9a89bac6931af..0466c75a3e0b5 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -125,6 +125,19 @@ export class HostBuilder implements DotnetHostBuilder { } } + // internal + withDumpThreadsOnNonZeroExit(): DotnetHostBuilder { + try { + deep_merge_config(monoConfig, { + dumpThreadsOnNonZeroExit: true + }); + return this; + } catch (err) { + mono_exit(1, err); + throw err; + } + } + // internal withAssertAfterExit(): DotnetHostBuilder { try { diff --git a/src/mono/browser/runtime/logging.ts b/src/mono/browser/runtime/logging.ts index ff52269a2726f..4e9229796138c 100644 --- a/src/mono/browser/runtime/logging.ts +++ b/src/mono/browser/runtime/logging.ts @@ -8,8 +8,8 @@ import { CharPtr, VoidPtr } from "./types/emscripten"; let prefix = "MONO_WASM: "; -export function mono_set_thread_name(threadName: string) { - prefix = `[${threadName}] MONO_WASM: `; +export function set_thread_prefix(threadPrefix: string) { + prefix = `[${threadPrefix}] MONO_WASM: `; } export function mono_log_debug(msg: string, ...data: any) { diff --git a/src/mono/browser/runtime/profiler.ts b/src/mono/browser/runtime/profiler.ts index afc98c51b873b..0567d3a386fe1 100644 --- a/src/mono/browser/runtime/profiler.ts +++ b/src/mono/browser/runtime/profiler.ts @@ -1,7 +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 { ENVIRONMENT_IS_WEB, linkerEnableAotProfiler, linkerEnableBrowserProfiler, mono_assert, runtimeHelpers } from "./globals"; +import { ENVIRONMENT_IS_WEB, mono_assert, runtimeHelpers } from "./globals"; import { MonoMethod, AOTProfilerOptions, BrowserProfilerOptions } from "./types/internal"; import { profiler_c_functions as cwraps } from "./cwraps"; import { utf8ToString } from "./strings"; @@ -15,7 +15,7 @@ import { utf8ToString } from "./strings"; // DumpAotProfileData stores the data into INTERNAL.aotProfileData. // export function mono_wasm_init_aot_profiler(options: AOTProfilerOptions): void { - mono_assert(linkerEnableAotProfiler, "AOT profiler is not enabled, please use aot; in your project file."); + mono_assert(runtimeHelpers.emscriptenBuildOptions.enableAotProfiler, "AOT profiler is not enabled, please use aot; in your project file."); if (options == null) options = {}; if (!("writeAt" in options)) @@ -27,7 +27,7 @@ export function mono_wasm_init_aot_profiler(options: AOTProfilerOptions): void { } export function mono_wasm_init_browser_profiler(options: BrowserProfilerOptions): void { - mono_assert(linkerEnableBrowserProfiler, "Browser profiler is not enabled, please use browser; in your project file."); + mono_assert(runtimeHelpers.emscriptenBuildOptions.enableBrowserProfiler, "Browser profiler is not enabled, please use browser; in your project file."); if (options == null) options = {}; const arg = "browser:"; diff --git a/src/mono/browser/runtime/pthreads/browser/index.ts b/src/mono/browser/runtime/pthreads/browser/index.ts index f459508a93ef0..e1ea4961a8463 100644 --- a/src/mono/browser/runtime/pthreads/browser/index.ts +++ b/src/mono/browser/runtime/pthreads/browser/index.ts @@ -3,11 +3,13 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { MonoWorkerToMainMessage, pthreadPtr } from "../shared/types"; +import { MonoWorkerToMainMessage, PThreadInfo, pthreadPtr } from "../shared/types"; import { MonoThreadMessage } from "../shared"; import { PThreadWorker, allocateUnusedWorker, getRunningWorkers, getUnusedWorkerPool, getWorker, loadWasmModuleToWorker } from "../shared/emscripten-internals"; import { createPromiseController, mono_assert, runtimeHelpers } from "../../globals"; import { MainToWorkerMessageType, PromiseAndController, PromiseController, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal"; +import { mono_log_info } from "../../logging"; +import { monoThreadInfo } from "../worker"; const threadPromises: Map[]> = new Map(); @@ -101,6 +103,7 @@ function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): case WorkerToMainMessageType.monoAttached: case WorkerToMainMessageType.enabledInterop: case WorkerToMainMessageType.monoUnRegistered: + case WorkerToMainMessageType.updateInfo: worker.info = Object.assign(worker.info!, message.info, {}); break; default: @@ -168,4 +171,36 @@ export function cancelThreads() { worker.postMessage({ cmd: "cancel" }); } } -} \ No newline at end of file +} + +export function dumpThreads(): void { + if (!WasmEnableThreads) return; + mono_log_info("Dumping web worker info as seen by UI thread, it could be stale: "); + const emptyInfo = { + pthreadId: 0, + threadPrefix: " - ", + threadName: "????", + isRunning: false, + isAttached: false, + isExternalEventLoop: false, + reuseCount: 0, + }; + const threadInfos: PThreadInfo[] = [ + Object.assign({}, emptyInfo, monoThreadInfo), // UI thread + ]; + for (const worker of getRunningWorkers()) { + threadInfos.push(Object.assign({}, emptyInfo, worker.info)); + } + for (const worker of getUnusedWorkerPool()) { + threadInfos.push(Object.assign({}, emptyInfo, worker.info)); + } + threadInfos.forEach((info, i) => { + const idx = (i + "").padStart(2, "0"); + const isRunning = (info.isRunning + "").padStart(5, " "); + const isAttached = (info.isAttached + "").padStart(5, " "); + const isEventLoop = (info.isExternalEventLoop + "").padStart(5, " "); + const reuseCount = (info.reuseCount + "").padStart(3, " "); + // eslint-disable-next-line no-console + console.info(`${idx} | ${info.threadPrefix}: isRunning:${isRunning} isAttached:${isAttached} isEventLoop:${isEventLoop} reuseCount:${reuseCount} - ${info.threadName}`); + }); +} diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts index c68de0a52a987..f0bbf80d6636b 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts @@ -4,9 +4,9 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; -import { onWorkerLoadInitiated, resolveThreadPromises } from "../browser"; +import { dumpThreads, onWorkerLoadInitiated, resolveThreadPromises } from "../browser"; import { mono_wasm_pthread_on_pthread_created } from "../worker"; -import { PThreadLibrary, PThreadWorker, getModulePThread, getRunningWorkers, getUnusedWorkerPool } from "./emscripten-internals"; +import { PThreadLibrary, PThreadWorker, getModulePThread, getUnusedWorkerPool } from "./emscripten-internals"; import { loaderHelpers, mono_assert } from "../../globals"; import { mono_log_warn } from "../../logging"; @@ -119,26 +119,10 @@ function allocateUnusedWorker(): PThreadWorker { pthreadId: 0, reuseCount: 0, updateCount: 0, - threadName: "", + threadPrefix: " - ", + threadName: "emscripten-pool", }; return worker; } -export function dumpThreads(): void { - if (!WasmEnableThreads) return; - // eslint-disable-next-line no-console - console.log("Running workers:"); - getRunningWorkers().forEach((worker) => { - // eslint-disable-next-line no-console - console.log(`${worker.info.threadName}: isRunning:${worker.info.isRunning} isAttached:${worker.info.isAttached} isExternalEventLoop:${worker.info.isExternalEventLoop} ${JSON.stringify(worker.info)}`); - }); - - // eslint-disable-next-line no-console - console.log("Unused workers:"); - getUnusedWorkerPool().forEach((worker) => { - // eslint-disable-next-line no-console - console.log(`${worker.info.threadName}: isRunning:${worker.info.isRunning} isAttached:${worker.info.isAttached} isExternalEventLoop:${worker.info.isExternalEventLoop} ${JSON.stringify(worker.info)}`); - }); - -} diff --git a/src/mono/browser/runtime/pthreads/shared/index.ts b/src/mono/browser/runtime/pthreads/shared/index.ts index 758f1e62b1ba0..d91efc5775173 100644 --- a/src/mono/browser/runtime/pthreads/shared/index.ts +++ b/src/mono/browser/runtime/pthreads/shared/index.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; import { ENVIRONMENT_IS_PTHREAD, Module, loaderHelpers, mono_assert, runtimeHelpers } from "../../globals"; -import { mono_log_debug, mono_set_thread_name } from "../../logging"; +import { mono_log_debug, set_thread_prefix } from "../../logging"; import { bindings_init } from "../../startup"; import { forceDisposeProxies } from "../../gc-handles"; import { GCHandle, GCHandleNull, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal"; @@ -63,16 +63,28 @@ export function mono_wasm_uninstall_js_worker_interop(): void { // this is just for Debug build of the runtime, making it easier to debug worker threads export function update_thread_info(): void { - loaderHelpers.mono_set_thread_name(monoThreadInfo.threadName!); + const threadType = monoThreadInfo.isUI ? "main" + : !monoThreadInfo.isAttached ? "emsc" + : monoThreadInfo.isTimer ? "timr" + : monoThreadInfo.isLongRunning ? "long" + : monoThreadInfo.isThreadPoolGate ? "gate" + : monoThreadInfo.isDebugger ? "dbgr" + : monoThreadInfo.isThreadPoolWorker ? "pool" + : monoThreadInfo.isExternalEventLoop ? "jsww" + : monoThreadInfo.isBackground ? "back" + : "norm"; + monoThreadInfo.threadPrefix = `0x${monoThreadInfo.pthreadId.toString(16).padStart(8, "0")}-${threadType}`; + + loaderHelpers.set_thread_prefix(monoThreadInfo.threadPrefix!); if (!loaderHelpers.config.forwardConsoleLogsToWS) { - mono_set_thread_name(monoThreadInfo.threadName!); + set_thread_prefix(monoThreadInfo.threadPrefix!); } (globalThis as any).monoThreadInfo = monoThreadInfo; if (WasmEnableThreads && BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) { monoThreadInfo.updateCount++; try { - (globalThis as any).monoThreadInfoFn = new Function(`//# sourceURL=https://${monoThreadInfo.updateCount}WorkerInfo${monoThreadInfo.isAttached ? monoThreadInfo.threadName : ""}/\r\nconsole.log("${JSON.stringify(monoThreadInfo)}");`); + (globalThis as any).monoThreadInfoFn = new Function(`//# sourceURL=https://${monoThreadInfo.updateCount}WorkerInfo${monoThreadInfo.isAttached ? monoThreadInfo.threadPrefix : ""}/\r\nconsole.log("${JSON.stringify(monoThreadInfo)}");`); } catch (ex) { runtimeHelpers.cspPolicy = true; diff --git a/src/mono/browser/runtime/pthreads/shared/types.ts b/src/mono/browser/runtime/pthreads/shared/types.ts index ab7e3b1a238be..11716137a87e5 100644 --- a/src/mono/browser/runtime/pthreads/shared/types.ts +++ b/src/mono/browser/runtime/pthreads/shared/types.ts @@ -12,18 +12,18 @@ export interface PThreadInfo { reuseCount: number, updateCount: number, - name?: string, threadName: string, + threadPrefix: string, isLoaded?: boolean, isRegistered?: boolean, isRunning?: boolean, isAttached?: boolean, isExternalEventLoop?: boolean, - isBrowserThread?: boolean; + isUI?: boolean; isBackground?: boolean, isDebugger?: boolean, - isThreadPool?: boolean, + isThreadPoolWorker?: boolean, isTimer?: boolean, isLongRunning?: boolean, isThreadPoolGate?: boolean, diff --git a/src/mono/browser/runtime/pthreads/worker/index.ts b/src/mono/browser/runtime/pthreads/worker/index.ts index c2af89871d676..0ea6e82de3d98 100644 --- a/src/mono/browser/runtime/pthreads/worker/index.ts +++ b/src/mono/browser/runtime/pthreads/worker/index.ts @@ -61,7 +61,8 @@ export const monoThreadInfo: PThreadInfo = { pthreadId: 0, reuseCount: 0, updateCount: 0, - threadName: "", + threadPrefix: " - ", + threadName: "emscripten-loaded", }; /// This is the "public internal" API for runtime subsystems that wish to be notified about @@ -96,7 +97,7 @@ export function mono_wasm_pthread_on_pthread_created(): void { monoThreadInfo.pthreadId = pthread_id; monoThreadInfo.reuseCount++; monoThreadInfo.updateCount++; - monoThreadInfo.threadName = `0x${pthread_id.toString(16).padStart(8, "0")}`; + monoThreadInfo.threadName = "pthread-assigned"; update_thread_info(); // don't do this callback for the main thread @@ -153,26 +154,18 @@ export function mono_wasm_pthread_on_pthread_attached(pthread_id: number, thread try { mono_assert(monoThreadInfo !== null && monoThreadInfo.pthreadId == pthread_id, "expected monoThreadInfo to be set already when attaching"); - const name = monoThreadInfo.name = utf8ToString(thread_name); + const name = monoThreadInfo.threadName = utf8ToString(thread_name); monoThreadInfo.isAttached = true; - monoThreadInfo.isThreadPool = threadpool_thread !== 0; + monoThreadInfo.isThreadPoolWorker = threadpool_thread !== 0; monoThreadInfo.isExternalEventLoop = external_eventloop !== 0; monoThreadInfo.isBackground = background_thread !== 0; monoThreadInfo.isDebugger = debugger_thread !== 0; // FIXME: this is a hack to get constant length thread names + monoThreadInfo.threadName = name; monoThreadInfo.isTimer = name == ".NET Timer"; monoThreadInfo.isLongRunning = name == ".NET Long Running Task"; monoThreadInfo.isThreadPoolGate = name == ".NET TP Gate"; - const threadType = monoThreadInfo.isTimer ? "timr" - : monoThreadInfo.isLongRunning ? "long" - : monoThreadInfo.isThreadPoolGate ? "gate" - : monoThreadInfo.isDebugger ? "dbgr" - : monoThreadInfo.isThreadPool ? "pool" - : monoThreadInfo.isExternalEventLoop ? "jsww" - : monoThreadInfo.isBackground ? "back" - : "norm"; - monoThreadInfo.threadName = `0x${pthread_id.toString(16).padStart(8, "0")}-${threadType}`; update_thread_info(); currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadAttached, pthread_self)); postMessageToMain({ @@ -187,6 +180,17 @@ export function mono_wasm_pthread_on_pthread_attached(pthread_id: number, thread } } +export function mono_wasm_pthread_set_name(name: CharPtr): void { + if (!WasmEnableThreads) return; + if (!ENVIRONMENT_IS_PTHREAD) return; + monoThreadInfo.threadName = utf8ToString(name); + update_thread_info(); + postMessageToMain({ + monoCmd: WorkerToMainMessageType.updateInfo, + info: monoThreadInfo, + }); +} + /// Called in the worker thread (not main thread) from mono when a pthread becomes detached from the mono runtime. export function mono_wasm_pthread_on_pthread_unregistered(pthread_id: number): void { if (!WasmEnableThreads) return; @@ -194,7 +198,6 @@ export function mono_wasm_pthread_on_pthread_unregistered(pthread_id: number): v mono_assert(pthread_id === monoThreadInfo.pthreadId, "expected pthread_id to match when un-registering"); postRunWorker(); monoThreadInfo.isAttached = false; - monoThreadInfo.threadName = monoThreadInfo.threadName + "=>detached"; update_thread_info(); postMessageToMain({ monoCmd: WorkerToMainMessageType.monoUnRegistered, diff --git a/src/mono/browser/runtime/snapshot.ts b/src/mono/browser/runtime/snapshot.ts index 76aac0eecfaa2..e21c13ce2a1b9 100644 --- a/src/mono/browser/runtime/snapshot.ts +++ b/src/mono/browser/runtime/snapshot.ts @@ -191,6 +191,7 @@ export async function getCacheKey(prefix: string): Promise { delete inputs.appendElementOnExit; delete inputs.assertAfterExit; delete inputs.interopCleanupOnExit; + delete inputs.dumpThreadsOnNonZeroExit; delete inputs.logExitCode; delete inputs.pthreadPoolSize; delete inputs.asyncFlushOnExit; diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index d7db9bc9a5567..892131d818a26 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -4,7 +4,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { DotnetModuleInternal, CharPtrNull } from "./types/internal"; -import { ENVIRONMENT_IS_NODE, exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert, linkerWasmEnableSIMD, linkerWasmEnableEH, ENVIRONMENT_IS_WORKER } from "./globals"; +import { ENVIRONMENT_IS_NODE, exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert, ENVIRONMENT_IS_WORKER } from "./globals"; import cwraps, { init_c_exports, threads_c_functions as tcwraps } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { toBase64StringImpl } from "./base64"; @@ -26,7 +26,7 @@ import { mono_log_debug, mono_log_error, mono_log_warn } from "./logging"; // threads import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from "./pthreads/browser"; -import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents } from "./pthreads/worker"; +import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents, monoThreadInfo } from "./pthreads/worker"; import { mono_wasm_main_thread_ptr, mono_wasm_pthread_ptr } from "./pthreads/shared"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; import { localHeapViewU8 } from "./memory"; @@ -383,8 +383,12 @@ export function postRunWorker() { async function mono_wasm_init_threads() { if (!WasmEnableThreads) return; - const threadName = `0x${mono_wasm_main_thread_ptr().toString(16)}-main`; - loaderHelpers.mono_set_thread_name(threadName); + const threadPrefix = `0x${mono_wasm_main_thread_ptr().toString(16)}-main`; + monoThreadInfo.threadPrefix = threadPrefix; + monoThreadInfo.threadName = "UI Thread"; + monoThreadInfo.isUI = true; + monoThreadInfo.isAttached = true; + loaderHelpers.set_thread_prefix(threadPrefix); await instantiateWasmPThreadWorkerPool(); await mono_wasm_init_diagnostics(); } @@ -396,10 +400,13 @@ function mono_wasm_pre_init_essential(isWorker: boolean): void { mono_log_debug("mono_wasm_pre_init_essential"); if (loaderHelpers.gitHash !== runtimeHelpers.gitHash) { - mono_log_warn("The version of dotnet.runtime.js is different from the version of dotnet.js!"); + mono_log_warn(`The version of dotnet.runtime.js ${runtimeHelpers.gitHash} is different from the version of dotnet.js ${loaderHelpers.gitHash}!`); } - if (loaderHelpers.gitHash !== runtimeHelpers.moduleGitHash) { - mono_log_warn("The version of dotnet.native.js is different from the version of dotnet.js!"); + if (loaderHelpers.gitHash !== runtimeHelpers.emscriptenBuildOptions.gitHash) { + mono_log_warn(`The version of dotnet.native.js ${runtimeHelpers.emscriptenBuildOptions.gitHash} is different from the version of dotnet.js ${loaderHelpers.gitHash}!`); + } + if (WasmEnableThreads !== runtimeHelpers.emscriptenBuildOptions.wasmEnableThreads) { + mono_log_warn(`The threads of dotnet.native.js ${runtimeHelpers.emscriptenBuildOptions.wasmEnableThreads} is different from the version of dotnet.runtime.js ${WasmEnableThreads}!`); } init_c_exports(); @@ -513,10 +520,10 @@ async function instantiate_wasm_module( async function ensureUsedWasmFeatures() { runtimeHelpers.featureWasmSimd = await loaderHelpers.simd(); runtimeHelpers.featureWasmEh = await loaderHelpers.exceptions(); - if (linkerWasmEnableSIMD) { + if (runtimeHelpers.emscriptenBuildOptions.wasmEnableSIMD) { mono_assert(runtimeHelpers.featureWasmSimd, "This browser/engine doesn't support WASM SIMD. Please use a modern version. See also https://aka.ms/dotnet-wasm-features"); } - if (linkerWasmEnableEH) { + if (runtimeHelpers.emscriptenBuildOptions.wasmEnableEH) { mono_assert(runtimeHelpers.featureWasmEh, "This browser/engine doesn't support WASM exception handling. Please use a modern version. See also https://aka.ms/dotnet-wasm-features"); } } diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index e6f7152b9bff9..5c4c6511763e9 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -579,6 +579,9 @@ export type RuntimeAPI = { productVersion: string, gitHash: string, buildConfiguration: string, + wasmEnableThreads: boolean, + wasmEnableSIMD: boolean, + wasmEnableExceptionHandling: boolean, } } & APIType diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 08f34bf68a9a0..e0cfaf8fd2a2b 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -78,6 +78,7 @@ export type MonoConfigInternal = MonoConfig & { appendElementOnExit?: boolean assertAfterExit?: boolean // default true for shell/nodeJS interopCleanupOnExit?: boolean + dumpThreadsOnNonZeroExit?: boolean logExitCode?: boolean forwardConsoleLogsToWS?: boolean, asyncFlushOnExit?: boolean @@ -150,7 +151,7 @@ export type LoaderHelpers = { mono_download_assets: () => Promise, resolve_single_asset_path: (behavior: SingleAssetBehaviors) => AssetEntryInternal, setup_proxy_console: (id: string, console: Console, origin: string) => void - mono_set_thread_name: (tid: string) => void + set_thread_prefix: (prefix: string) => void fetch_like: (url: string, init?: RequestInit) => Promise; locateFile: (path: string, prefix?: string) => string, out(message: string): void; @@ -175,8 +176,8 @@ export type LoaderHelpers = { simd: () => Promise, } export type RuntimeHelpers = { + emscriptenBuildOptions: EmscriptenBuildOptions, gitHash: string, - moduleGitHash: string, config: MonoConfigInternal; diagnosticTracing: boolean; @@ -227,6 +228,7 @@ export type RuntimeHelpers = { instantiate_segmentation_rules_asset: (pendingAsset: AssetEntryInternal) => Promise, jiterpreter_dump_stats?: (x: boolean) => string, forceDisposeProxies: (disposeMethods: boolean, verbose: boolean) => void, + dumpThreads: () => void, } export type AOTProfilerOptions = { @@ -247,13 +249,18 @@ export function is_nullish(value: T | null | undefined): value is null | unde return (value === undefined) || (value === null); } +// these are values from the last re-link with emcc/workload +export type EmscriptenBuildOptions = { + wasmEnableSIMD: boolean, + wasmEnableEH: boolean, + enableAotProfiler: boolean, + enableBrowserProfiler: boolean, + runAOTCompilation: boolean, + wasmEnableThreads: boolean, + gitHash: string, +}; export type EmscriptenInternals = { isPThread: boolean, - linkerWasmEnableSIMD: boolean, - linkerWasmEnableEH: boolean, - linkerEnableAotProfiler: boolean, - linkerEnableBrowserProfiler: boolean, - linkerRunAOTCompilation: boolean, quit_: Function, ExitStatus: ExitStatusError, gitHash: string, @@ -466,7 +473,7 @@ export interface PromiseAndController { promise_control: PromiseController; } -export type passEmscriptenInternalsType = (internals: EmscriptenInternals) => void; +export type passEmscriptenInternalsType = (internals: EmscriptenInternals, emscriptenBuildOptions: EmscriptenBuildOptions) => void; export type setGlobalObjectsType = (globalObjects: GlobalObjects) => void; export type initializeExportsType = (globalObjects: GlobalObjects) => RuntimeAPI; export type initializeReplacementsType = (replacements: EmscriptenReplacements) => void; @@ -500,6 +507,7 @@ export const monoMessageSymbol = "__mono_message__"; export const enum WorkerToMainMessageType { monoRegistered = "monoRegistered", monoAttached = "monoAttached", + updateInfo = "updateInfo", enabledInterop = "notify_enabled_interop", monoUnRegistered = "monoUnRegistered", pthreadCreated = "pthreadCreated", diff --git a/src/mono/browser/test-main.js b/src/mono/browser/test-main.js index 2c4c04106e22f..16a00729e1de3 100644 --- a/src/mono/browser/test-main.js +++ b/src/mono/browser/test-main.js @@ -254,6 +254,7 @@ function configureRuntime(dotnet, runArgs) { .withElementOnExit() .withInteropCleanupOnExit() .withAssertAfterExit() + .withDumpThreadsOnNonZeroExit() .withConfig({ loadAllSatelliteResources: true }); @@ -272,6 +273,12 @@ function configureRuntime(dotnet, runArgs) { }) } } + + // dotnet.withEnvironmentVariable("MONO_LOG_LEVEL", "debug") + // dotnet.withEnvironmentVariable("MONO_LOG_MASK", "gc") + // dotnet.withEnvironmentVariable("MONO_GC_DEBUG", "9") + // dotnet.withEnvironmentVariable("DOTNET_DebugWriteToStdErr", "1") + if (ENVIRONMENT_IS_WEB) { if (runArgs.memorySnapshot) dotnet.withStartupMemoryCache(true); @@ -306,6 +313,7 @@ async function dry_run(runArgs) { virtualWorkingDirectory: undefined, pthreadPoolSize: 0, interopCleanupOnExit: false, + dumpThreadsOnNonZeroExit: 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 diff --git a/src/mono/mono/utils/mono-threads-wasm.c b/src/mono/mono/utils/mono-threads-wasm.c index d880b47d0513c..ea8faf904c256 100644 --- a/src/mono/mono/utils/mono-threads-wasm.c +++ b/src/mono/mono/utils/mono-threads-wasm.c @@ -164,15 +164,20 @@ mono_native_thread_os_id_get (void) MONO_API gboolean mono_native_thread_create (MonoNativeThreadId *tid, gpointer func, gpointer arg) { +#ifdef __EMSCRIPTEN_PTHREADS__ + return pthread_create (tid, NULL, (void *(*)(void *)) func, arg) == 0; +#else g_error ("WASM doesn't support threading"); +#endif } -static const char *thread_name; - void mono_native_thread_set_name (MonoNativeThreadId tid, const char *name) { - thread_name = g_strdup (name); +#ifndef DISABLE_THREADS + // note there is also emscripten_set_thread_name, but it only changes the name for emscripten profiler + mono_wasm_pthread_set_name (name); +#endif } gboolean diff --git a/src/mono/mono/utils/mono-threads-wasm.h b/src/mono/mono/utils/mono-threads-wasm.h index bccb81f46fd83..1c4934c2f9b43 100644 --- a/src/mono/mono/utils/mono-threads-wasm.h +++ b/src/mono/mono/utils/mono-threads-wasm.h @@ -87,6 +87,9 @@ mono_threads_wasm_on_thread_attached (pthread_t tid, const char* thread_name, gb void mono_threads_wasm_on_thread_unregistered (void); +void +mono_wasm_pthread_set_name (const char* thread_name); + #endif /* HOST_WASM*/ #endif /* __MONO_THREADS_WASM_H__ */