diff --git a/src/mono/browser/runtime/assets.ts b/src/mono/browser/runtime/assets.ts index bd88949b6234c..40dc39349ff56 100644 --- a/src/mono/browser/runtime/assets.ts +++ b/src/mono/browser/runtime/assets.ts @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import type { AssetEntryInternal } from "./types/internal"; + import cwraps from "./cwraps"; import { mono_wasm_load_icu_data } from "./icu"; import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { mono_log_info, mono_log_debug, parseSymbolMapFile } from "./logging"; import { mono_wasm_load_bytes_into_heap } from "./memory"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; -import { AssetEntryInternal } from "./types/internal"; import { AssetEntry } from "./types"; import { VoidPtr } from "./types/emscripten"; import { setSegmentationRulesFromJson } from "./hybrid-globalization/grapheme-segmenter"; diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts index 7dc7c207697ec..dbb0babce8231 100644 --- a/src/mono/browser/runtime/cwraps.ts +++ b/src/mono/browser/runtime/cwraps.ts @@ -6,13 +6,12 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import type { MonoAssembly, MonoClass, MonoMethod, MonoObject, - MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments + MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments, PThreadPtr } from "./types/internal"; import type { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten"; import { Module, runtimeHelpers } from "./globals"; import { mono_log_error } from "./logging"; import { mono_assert } from "./globals"; -import { PThreadPtr } from "./pthreads/shared/types"; type SigLine = [lazyOrSkip: boolean | (() => boolean), name: string, returnType: string | null, argTypes?: string[], opts?: any]; diff --git a/src/mono/browser/runtime/debug.ts b/src/mono/browser/runtime/debug.ts index 1c919426bc3a8..74c0128f2e4e3 100644 --- a/src/mono/browser/runtime/debug.ts +++ b/src/mono/browser/runtime/debug.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import BuildConfiguration from "consts:configuration"; + import { INTERNAL, Module, loaderHelpers, runtimeHelpers } from "./globals"; import { toBase64StringImpl } from "./base64"; import cwraps from "./cwraps"; diff --git a/src/mono/browser/runtime/diagnostics/browser/controller.ts b/src/mono/browser/runtime/diagnostics/browser/controller.ts index 7bcdf62b86850..c03b2e677e131 100644 --- a/src/mono/browser/runtime/diagnostics/browser/controller.ts +++ b/src/mono/browser/runtime/diagnostics/browser/controller.ts @@ -7,10 +7,10 @@ import { threads_c_functions as cwraps } from "../../cwraps"; import { INTERNAL, mono_assert } from "../../globals"; import { mono_log_info, mono_log_debug, mono_log_warn } from "../../logging"; import { withStackAlloc, getI32 } from "../../memory"; -import { Thread, waitForThread } from "../../pthreads/browser"; +import { waitForThread } from "../../pthreads/browser"; import { isDiagnosticMessage, makeDiagnosticServerControlCommand } from "../shared/controller-commands"; import monoDiagnosticsMock from "consts:monoDiagnosticsMock"; -import { PThreadPtr } from "../../pthreads/shared/types"; +import { PThreadPtr, Thread } from "../../types/internal"; /// An object that can be used to control the diagnostic server. export interface ServerController { diff --git a/src/mono/browser/runtime/diagnostics/index.ts b/src/mono/browser/runtime/diagnostics/index.ts index 474fd8a824fd5..a9d6f9414267a 100644 --- a/src/mono/browser/runtime/diagnostics/index.ts +++ b/src/mono/browser/runtime/diagnostics/index.ts @@ -37,12 +37,9 @@ let diagnosticsServerEnabled = false; let diagnosticsInitialized = false; export async function mono_wasm_init_diagnostics(): Promise { - if (diagnosticsInitialized) - return; - if (!WasmEnableThreads) { - mono_log_warn("ignoring diagnostics options because this runtime does not support diagnostics"); - return; - } + if (!WasmEnableThreads) return; + if (diagnosticsInitialized) return; + const options = diagnostic_options_from_environment(); if (!options) return; diff --git a/src/mono/browser/runtime/diagnostics/shared/controller-commands.ts b/src/mono/browser/runtime/diagnostics/shared/controller-commands.ts index 5e08f56c627ea..b1d9fb0201670 100644 --- a/src/mono/browser/runtime/diagnostics/shared/controller-commands.ts +++ b/src/mono/browser/runtime/diagnostics/shared/controller-commands.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 type { MonoThreadMessage } from "../../pthreads/shared"; import { isMonoThreadMessage } from "../../pthreads/shared"; +import type { MonoThreadMessage } from "../../types/internal"; // Messages from the main thread to the diagnostic server thread export interface DiagnosticMessage extends MonoThreadMessage { diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index e60265f04a281..b76ba531d5efd 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -190,6 +190,10 @@ type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number; + /** + * initial number of unused workers keep in the emscripten pthread pool after startup + */ + pthreadPoolReady?: number; /** * If true, a list of the methods optimized by the interpreter will be saved and used for faster startup * on future runs of the application diff --git a/src/mono/browser/runtime/exports-internal.ts b/src/mono/browser/runtime/exports-internal.ts index 5fe5773f97577..dc03bcf80a9eb 100644 --- a/src/mono/browser/runtime/exports-internal.ts +++ b/src/mono/browser/runtime/exports-internal.ts @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import WasmEnableThreads from "consts:wasmEnableThreads"; + +import { MonoObjectNull, type MonoObject } from "./types/internal"; import cwraps, { profiler_c_functions } from "./cwraps"; import { mono_wasm_send_dbg_command_with_parms, mono_wasm_send_dbg_command, mono_wasm_get_dbg_command_info, mono_wasm_get_details, mono_wasm_release_object, mono_wasm_call_function_on, mono_wasm_debugger_resume, mono_wasm_detach_debugger, mono_wasm_raise_debug_event, mono_wasm_change_debugger_log_level, mono_wasm_debugger_attached } from "./debug"; import { http_wasm_supports_streaming_request, http_wasm_supports_streaming_response, http_wasm_create_controller, http_wasm_abort_request, http_wasm_abort_response, http_wasm_transform_stream_write, http_wasm_transform_stream_close, http_wasm_fetch, http_wasm_fetch_stream, http_wasm_fetch_bytes, http_wasm_get_response_header_names, http_wasm_get_response_header_values, http_wasm_get_response_bytes, http_wasm_get_response_length, http_wasm_get_streamed_response_bytes, http_wasm_get_response_type, http_wasm_get_response_status } from "./http"; @@ -17,16 +20,17 @@ import { loadLazyAssembly } from "./lazyLoading"; import { loadSatelliteAssemblies } from "./satelliteAssemblies"; import { forceDisposeProxies } from "./gc-handles"; import { mono_wasm_get_func_id_to_name_mappings } from "./logging"; -import { MonoObject, MonoObjectNull } from "./types/internal"; import { monoStringToStringUnsafe } from "./strings"; -import { thread_available } from "./pthreads/browser"; import { mono_wasm_bind_cs_function } from "./invoke-cs"; +import { dumpThreads, thread_available } from "./pthreads/browser"; + export function export_internal(): any { return { // tests mono_wasm_exit: (exit_code: number) => { Module.err("early exit " + exit_code); }, forceDisposeProxies, + dumpThreads: WasmEnableThreads ? dumpThreads : undefined, // with mono_wasm_debugger_log and mono_wasm_trace_logger logging: undefined, @@ -57,7 +61,7 @@ export function export_internal(): any { get_global_this, get_dotnet_instance: () => exportedRuntimeAPI, dynamic_import, - thread_available, + thread_available: WasmEnableThreads ? thread_available : undefined, mono_wasm_bind_cs_function, // BrowserWebSocket diff --git a/src/mono/browser/runtime/exports.ts b/src/mono/browser/runtime/exports.ts index 2f3aa96a0ec6b..b2515b3220c40 100644 --- a/src/mono/browser/runtime/exports.ts +++ b/src/mono/browser/runtime/exports.ts @@ -70,6 +70,10 @@ function initializeExports(globalObjects: GlobalObjects): RuntimeAPI { runtimeList = globalThisAny.getDotnetRuntime.__list; } + if (BuildConfiguration === "Debug") { + globalThisAny.INTERNAL = globals.internal; + } + return exportedRuntimeAPI; } diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts index dfe894f569826..5be22e37a42c1 100644 --- a/src/mono/browser/runtime/interp-pgo.ts +++ b/src/mono/browser/runtime/interp-pgo.ts @@ -198,6 +198,7 @@ export async function getCacheKey(prefix: string): Promise { delete inputs.dumpThreadsOnNonZeroExit; delete inputs.logExitCode; delete inputs.pthreadPoolSize; + delete inputs.pthreadPoolReady; delete inputs.asyncFlushOnExit; delete inputs.remoteSources; delete inputs.ignorePdbLoadErrors; diff --git a/src/mono/browser/runtime/loader/assets.ts b/src/mono/browser/runtime/loader/assets.ts index 624244a63ff9c..5cfc457c4d108 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -3,7 +3,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import type { AssetEntryInternal, PromiseAndController } from "../types/internal"; +import { PThreadPtrNull, type AssetEntryInternal, type PThreadWorker, type PromiseAndController } from "../types/internal"; import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, SingleAssetBehaviors as SingleAssetBehaviors, WebAssemblyBootResourceType } from "../types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { createPromiseController } from "./promise-controller"; @@ -733,4 +733,24 @@ export async function streamingCompileWasm() { catch (err) { loaderHelpers.wasmCompilePromise.promise_control.reject(err); } +} + +export function preloadWorkers() { + if (!WasmEnableThreads) return; + const jsModuleWorker = resolve_single_asset_path("js-module-threads"); + for (let i = 0; i < loaderHelpers.config.pthreadPoolSize!; i++) { + const workerNumber = loaderHelpers.workerNextNumber++; + const worker: Partial = new Worker(jsModuleWorker.resolvedUrl!, { + name: "dotnet-worker-" + workerNumber.toString().padStart(3, "0"), + }); + worker.info = { + workerNumber, + pthreadId: PThreadPtrNull, + reuseCount: 0, + updateCount: 0, + threadPrefix: " - ", + threadName: "emscripten-pool", + } as any; + loaderHelpers.loadingWorkers.push(worker as any); + } } \ No newline at end of file diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 6f64d327fe7ad..f7e5c45e5342a 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -187,10 +187,13 @@ export function normalizeConfig() { config.cachedResourcesPurgeDelay = 10000; } + // ActiveIssue https://github.com/dotnet/runtime/issues/75602 if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolSize)) { - // ActiveIssue https://github.com/dotnet/runtime/issues/75602 config.pthreadPoolSize = 7; } + if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolReady)) { + config.pthreadPoolReady = 3; + } // 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) { diff --git a/src/mono/browser/runtime/loader/globals.ts b/src/mono/browser/runtime/loader/globals.ts index c9d8ffd8e8c9d..7e9708845e65b 100644 --- a/src/mono/browser/runtime/loader/globals.ts +++ b/src/mono/browser/runtime/loader/globals.ts @@ -92,6 +92,8 @@ export function setLoaderGlobals( loadedFiles: [], loadedAssemblies: [], libraryInitializers: [], + loadingWorkers: [], + workerNextNumber: 1, actual_downloaded_assets_count: 0, actual_instantiated_assets_count: 0, expected_downloaded_assets_count: 0, diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index 1f85b4e241200..730a692b4ebf5 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -10,7 +10,7 @@ import { ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, emscriptenModule, exportedRu import { deep_merge_config, deep_merge_module, mono_wasm_load_config } from "./config"; import { installUnhandledErrorHandler, mono_exit, registerEmscriptenExitHandlers } 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 { mono_download_assets, preloadWorkers, prepareAssets, prepareAssetsWorker, resolve_single_asset_path, streamingCompileWasm } from "./assets"; import { detect_features_and_polyfill } from "./polyfills"; import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; @@ -487,6 +487,7 @@ async function createEmscriptenMain(): Promise { setTimeout(async () => { try { init_globalization(); + preloadWorkers(); await mono_download_assets(); } catch (err) { diff --git a/src/mono/browser/runtime/loader/worker.ts b/src/mono/browser/runtime/loader/worker.ts index 81a9cecad6a74..baeeaf0165f67 100644 --- a/src/mono/browser/runtime/loader/worker.ts +++ b/src/mono/browser/runtime/loader/worker.ts @@ -1,10 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { MonoConfigInternal, WorkerToMainMessageType, monoMessageSymbol } from "../types/internal"; +import { MonoConfigInternal, PThreadInfo, WorkerToMainMessageType, monoMessageSymbol } from "../types/internal"; import { MonoConfig } from "../types"; import { deep_merge_config, normalizeConfig } from "./config"; -import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals"; +import { ENVIRONMENT_IS_WEB, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_debug } from "./logging"; export function setupPreloadChannelToMainThread() { @@ -13,7 +13,8 @@ export function setupPreloadChannelToMainThread() { const mainPort = channel.port2; workerPort.addEventListener("message", (event) => { const config = JSON.parse(event.data.config) as MonoConfig; - onMonoConfigReceived(config); + const monoThreadInfo = JSON.parse(event.data.monoThreadInfo) as PThreadInfo; + onMonoConfigReceived(config, monoThreadInfo); workerPort.close(); mainPort.close(); }, { once: true }); @@ -30,13 +31,13 @@ export function setupPreloadChannelToMainThread() { let workerMonoConfigReceived = false; // called when the main thread sends us the mono config -function onMonoConfigReceived(config: MonoConfigInternal): void { +function onMonoConfigReceived(config: MonoConfigInternal, monoThreadInfo: PThreadInfo): void { if (workerMonoConfigReceived) { mono_log_debug("mono config already received"); return; } - deep_merge_config(loaderHelpers.config, config); + runtimeHelpers.monoThreadInfo = monoThreadInfo; normalizeConfig(); mono_log_debug("mono config received"); workerMonoConfigReceived = true; diff --git a/src/mono/browser/runtime/polyfills.ts b/src/mono/browser/runtime/polyfills.ts index 7aae92566b34a..5f18ba3fd7974 100644 --- a/src/mono/browser/runtime/polyfills.ts +++ b/src/mono/browser/runtime/polyfills.ts @@ -5,7 +5,8 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import type { EmscriptenReplacements } from "./types/internal"; import type { TypedArray } from "./types/emscripten"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WORKER, INTERNAL, Module, loaderHelpers, runtimeHelpers } from "./globals"; -import { replaceEmscriptenPThreadLibrary } from "./pthreads/shared/emscripten-replacements"; +import { replaceEmscriptenPThreadUI } from "./pthreads/browser/replacements"; +import { replaceEmscriptenPThreadWorker } from "./pthreads/worker/replacements"; const dummyPerformance = { now: function () { @@ -34,7 +35,11 @@ export function initializeReplacements(replacements: EmscriptenReplacements): vo // threads if (WasmEnableThreads && replacements.modulePThread) { - replaceEmscriptenPThreadLibrary(replacements.modulePThread); + if (ENVIRONMENT_IS_WORKER) { + replaceEmscriptenPThreadWorker(replacements.modulePThread); + } else { + replaceEmscriptenPThreadUI(replacements.modulePThread); + } } } diff --git a/src/mono/browser/runtime/pthreads/browser/index.ts b/src/mono/browser/runtime/pthreads/browser/index.ts index 84c58b694d13a..cf384e77cb3c3 100644 --- a/src/mono/browser/runtime/pthreads/browser/index.ts +++ b/src/mono/browser/runtime/pthreads/browser/index.ts @@ -3,23 +3,17 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { MonoWorkerToMainMessage, PThreadInfo, PThreadPtr, PThreadPtrNull } from "../shared/types"; -import { MonoThreadMessage, mono_wasm_pthread_ptr, update_thread_info } 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 { MonoWorkerToMainMessage } from "../shared/types"; +import { mono_wasm_pthread_ptr, update_thread_info } from "../shared"; +import { ENVIRONMENT_IS_WORKER, createPromiseController, loaderHelpers, mono_assert, runtimeHelpers } from "../../globals"; +import { MainToWorkerMessageType, MonoThreadMessage, PThreadInfo, PThreadPtr, PThreadPtrNull, PThreadWorker, PromiseAndController, PromiseController, Thread, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal"; +import { mono_log_error, mono_log_info } from "../../logging"; import { monoThreadInfo } from "../worker"; -import { mono_wasm_init_diagnostics } from "../../diagnostics"; +import { getRunningWorkers, getUnusedWorkerPool, getWorker, loadWasmModuleToWorker } from "./replacements"; +import { threads_c_functions as cwraps } from "../../cwraps"; const threadPromises: Map[]> = new Map(); -export interface Thread { - readonly pthreadPtr: PThreadPtr; - readonly port: MessagePort; - postMessageToWorker(message: T): void; -} - class ThreadImpl implements Thread { constructor(readonly pthreadPtr: PThreadPtr, readonly worker: Worker, readonly port: MessagePort) { } postMessageToWorker(message: T): void { @@ -30,6 +24,7 @@ class ThreadImpl implements Thread { /// wait until the thread with the given id has set up a message port to the runtime export function waitForThread(pthreadPtr: PThreadPtr): Promise { if (!WasmEnableThreads) return null as any; + mono_assert(!ENVIRONMENT_IS_WORKER, "waitForThread should only be called from the UI thread"); const worker = getWorker(pthreadPtr); if (worker?.thread) { return Promise.resolve(worker?.thread); @@ -81,8 +76,7 @@ function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): let port: MessagePort; let thread: Thread; pthreadId = message.info?.pthreadId ?? 0; - - worker.info = Object.assign(worker.info, message.info, {}); + worker.info = Object.assign({}, worker.info, message.info); switch (message.monoCmd) { case WorkerToMainMessageType.preload: // this one shot port from setupPreloadChannelToMainThread @@ -90,7 +84,8 @@ function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): port.postMessage({ type: "pthread", cmd: MainToWorkerMessageType.applyConfig, - config: JSON.stringify(runtimeHelpers.config) + config: JSON.stringify(runtimeHelpers.config), + monoThreadInfo: JSON.stringify(worker.info), }); port.close(); break; @@ -140,35 +135,26 @@ export function thread_available(): Promise { return pendingWorkerLoad.promise; } +export function populateEmscriptenPool(): void { + if (!WasmEnableThreads) return; + const unused = getUnusedWorkerPool(); + for (const worker of loaderHelpers.loadingWorkers) { + unused.push(worker); + } + loaderHelpers.loadingWorkers.length = 0; // GC +} + export async function mono_wasm_init_threads() { if (!WasmEnableThreads) return; + + // setup the UI thread monoThreadInfo.pthreadId = mono_wasm_pthread_ptr(); monoThreadInfo.threadName = "UI Thread"; monoThreadInfo.isUI = true; monoThreadInfo.isRunning = true; update_thread_info(); - await instantiateWasmPThreadWorkerPool(); - await mono_wasm_init_diagnostics(); -} -/// We call on the main thread this during startup to pre-allocate a pool of pthread workers. -/// At this point asset resolution needs to be working (ie we loaded MonoConfig). -/// This is used instead of the Emscripten PThread.initMainThread because we call it later. -export function preAllocatePThreadWorkerPool(pthreadPoolSize: number): void { - if (!WasmEnableThreads) return; - for (let i = 0; i < pthreadPoolSize; i++) { - allocateUnusedWorker(); - } -} - -/// We call this on the main thread during startup once we fetched WasmModule. -/// This sends a message to each pre-allocated worker to load the WasmModule and dotnet.js and to set up -/// message handling. -/// This is used instead of the Emscripten "receiveInstance" in "createWasm" because that code is -/// conditioned on a non-zero PTHREAD_POOL_SIZE (but we set it to 0 to avoid early worker allocation). -export async function instantiateWasmPThreadWorkerPool(): Promise { - if (!WasmEnableThreads) return null as any; - // this is largely copied from emscripten's "receiveInstance" in "createWasm" in "src/preamble.js" + // wait until all workers in the pool are loaded - ready to be used as pthread synchronously const workers = getUnusedWorkerPool(); if (workers.length > 0) { const promises = workers.map(loadWasmModuleToWorker); @@ -178,6 +164,7 @@ export async function instantiateWasmPThreadWorkerPool(): Promise { // when we create threads with browser event loop, it's not able to be joined by mono's thread join during shutdown and blocks process exit export function cancelThreads() { + if (!WasmEnableThreads) return; const workers: PThreadWorker[] = getRunningWorkers(); for (const worker of workers) { if (worker.info.isExternalEventLoop) { @@ -189,14 +176,16 @@ export function cancelThreads() { 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, + const emptyInfo: PThreadInfo = { + workerNumber: -1, + pthreadId: PThreadPtrNull, threadPrefix: " - ", threadName: "????", isRunning: false, isAttached: false, isExternalEventLoop: false, reuseCount: 0, + updateCount: 0, }; const threadInfos: PThreadInfo[] = [ Object.assign({}, emptyInfo, monoThreadInfo), // UI thread @@ -207,8 +196,8 @@ export function dumpThreads(): void { for (const worker of getUnusedWorkerPool()) { threadInfos.push(Object.assign({}, emptyInfo, worker.info)); } - threadInfos.forEach((info, i) => { - const idx = (i + "").padStart(2, "0"); + threadInfos.forEach((info) => { + const idx = info.workerNumber.toString().padStart(3, "0"); const isRunning = (info.isRunning + "").padStart(5, " "); const isAttached = (info.isAttached + "").padStart(5, " "); const isEventLoop = (info.isExternalEventLoop + "").padStart(5, " "); @@ -217,3 +206,16 @@ export function dumpThreads(): void { console.info(`${idx} | ${info.threadPrefix}: isRunning:${isRunning} isAttached:${isAttached} isEventLoop:${isEventLoop} reuseCount:${reuseCount} - ${info.threadName}`); }); } + +export function init_finalizer_thread() { + // we don't need it immediately, so we can wait a bit, to keep CPU working on normal startup + setTimeout(() => { + try { + cwraps.mono_wasm_init_finalizer_thread(); + } + catch (err) { + mono_log_error("init_finalizer_thread() failed", err); + loaderHelpers.mono_exit(1, err); + } + }, 200); +} \ No newline at end of file diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/browser/runtime/pthreads/browser/replacements.ts similarity index 70% rename from src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts rename to src/mono/browser/runtime/pthreads/browser/replacements.ts index 0acb8b6615c69..d4eab5b05bb06 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts +++ b/src/mono/browser/runtime/pthreads/browser/replacements.ts @@ -4,31 +4,16 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; -import { dumpThreads, onWorkerLoadInitiated, resolveThreadPromises } from "../browser"; -import { mono_wasm_pthread_on_pthread_created, onRunMessage } from "../worker"; -import { PThreadLibrary, PThreadWorker, getModulePThread, getUnusedWorkerPool } from "./emscripten-internals"; +import { dumpThreads, onWorkerLoadInitiated, resolveThreadPromises } from "."; import { Module, loaderHelpers, mono_assert } from "../../globals"; import { mono_log_warn } from "../../logging"; -import { PThreadPtr, PThreadPtrNull } from "./types"; +import { PThreadLibrary, PThreadPtr, PThreadPtrNull, PThreadWorker } from "../../types/internal"; -/** @module emscripten-replacements Replacements for individual functions in the emscripten PThreads library. - * These have a hard dependency on the version of Emscripten that we are using and may need to be kept in sync with - * {@linkcode file://./../../../emsdk/upstream/emscripten/src/library_pthread.js} - */ - -export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): void { +export function replaceEmscriptenPThreadUI(modulePThread: PThreadLibrary): void { if (!WasmEnableThreads) return; const originalLoadWasmModuleToWorker = modulePThread.loadWasmModuleToWorker; - const originalThreadInitTLS = modulePThread.threadInitTLS; const originalReturnWorkerToPool = modulePThread.returnWorkerToPool; - const original_emscripten_thread_init = (Module as any)["__emscripten_thread_init"]; - - - (Module as any)["__emscripten_thread_init"] = (pthread_ptr: PThreadPtr, isMainBrowserThread: number, isMainRuntimeThread: number, canBlock: number) => { - onRunMessage(pthread_ptr); - original_emscripten_thread_init(pthread_ptr, isMainBrowserThread, isMainRuntimeThread, canBlock); - }; modulePThread.loadWasmModuleToWorker = (worker: PThreadWorker): Promise => { const afterLoaded = originalLoadWasmModuleToWorker(worker); @@ -43,10 +28,6 @@ export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): } return afterLoaded; }; - modulePThread.threadInitTLS = (): void => { - originalThreadInitTLS(); - mono_wasm_pthread_on_pthread_created(); - }; modulePThread.allocateUnusedWorker = allocateUnusedWorker; modulePThread.getNewWorker = () => getNewWorker(modulePThread); modulePThread.returnWorkerToPool = (worker: PThreadWorker) => { @@ -88,7 +69,7 @@ function getNewWorker(modulePThread: PThreadLibrary): PThreadWorker { if (!WasmEnableThreads) return null as any; if (modulePThread.unusedWorkers.length == 0) { - mono_log_warn(`Failed to find unused WebWorker, this may deadlock. Please increase the pthreadPoolSize. Running threads ${modulePThread.runningWorkers.length}. Loading workers: ${modulePThread.unusedWorkers.length}`); + mono_log_warn(`Failed to find unused WebWorker, this may deadlock. Please increase the pthreadPoolReady. Running threads ${modulePThread.runningWorkers.length}. Loading workers: ${modulePThread.unusedWorkers.length}`); const worker = allocateUnusedWorker(); modulePThread.loadWasmModuleToWorker(worker); availableThreadCount--; @@ -96,7 +77,7 @@ function getNewWorker(modulePThread: PThreadLibrary): PThreadWorker { } // keep them pre-allocated all the time, not just during startup - if (loaderHelpers.config.pthreadPoolSize && modulePThread.unusedWorkers.length <= loaderHelpers.config.pthreadPoolSize) { + if (modulePThread.unusedWorkers.length <= loaderHelpers.config.pthreadPoolReady!) { const worker = allocateUnusedWorker(); modulePThread.loadWasmModuleToWorker(worker); } @@ -109,7 +90,7 @@ function getNewWorker(modulePThread: PThreadLibrary): PThreadWorker { return worker; } } - mono_log_warn(`Failed to find loaded WebWorker, this may deadlock. Please increase the pthreadPoolSize. Running threads ${modulePThread.runningWorkers.length}. Loading workers: ${modulePThread.unusedWorkers.length}`); + mono_log_warn(`Failed to find loaded WebWorker, this may deadlock. Please increase the pthreadPoolReady. Running threads ${modulePThread.runningWorkers.length}. Loading workers: ${modulePThread.unusedWorkers.length}`); availableThreadCount--; // negative value return modulePThread.unusedWorkers.pop()!; } @@ -121,10 +102,14 @@ function allocateUnusedWorker(): PThreadWorker { const asset = loaderHelpers.resolve_single_asset_path("js-module-threads"); const uri = asset.resolvedUrl; mono_assert(uri !== undefined, "could not resolve the uri for the js-module-threads asset"); - const worker = new Worker(uri) as PThreadWorker; + const workerNumber = loaderHelpers.workerNextNumber++; + const worker = new Worker(uri, { + name: "dotnet-worker-" + workerNumber.toString().padStart(3, "0"), + }) as PThreadWorker; getUnusedWorkerPool().push(worker); worker.loaded = false; worker.info = { + workerNumber, pthreadId: PThreadPtrNull, reuseCount: 0, updateCount: 0, @@ -134,4 +119,22 @@ function allocateUnusedWorker(): PThreadWorker { return worker; } +export function getWorker(pthreadPtr: PThreadPtr): PThreadWorker | undefined { + return getModulePThread().pthreads[pthreadPtr as any]; +} +export function getUnusedWorkerPool(): PThreadWorker[] { + return getModulePThread().unusedWorkers; +} + +export function getRunningWorkers(): PThreadWorker[] { + return getModulePThread().runningWorkers; +} + +export function loadWasmModuleToWorker(worker: PThreadWorker): Promise { + return getModulePThread().loadWasmModuleToWorker(worker); +} + +export function getModulePThread(): PThreadLibrary { + return (Module).PThread as PThreadLibrary; +} diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts deleted file mode 100644 index 0529744570efd..0000000000000 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { Module } from "../../globals"; -import { Thread } from "../browser"; -import { PThreadInfo, PThreadPtr } from "./types"; - -/** @module emscripten-internals accessors to the functions in the emscripten PThreads library, including - * the low-level representations of {@linkcode PThreadPtr} thread info structs, etc. - * Additionally, note that some of these functions are replaced by {@linkcode file://./emscripten-replacements.ts}. - * These have a hard dependency on the version of Emscripten that we are using and may need to be kept in sync with - * {@linkcode file://./../../../emsdk/upstream/emscripten/src/library_pthread.js} - */ - -// This is what we know about the Emscripten PThread library -export interface PThreadLibrary { - unusedWorkers: PThreadWorker[]; - runningWorkers: PThreadWorker[]; - pthreads: PThreadInfoMap; - allocateUnusedWorker: () => void; - loadWasmModuleToWorker: (worker: PThreadWorker) => Promise; - threadInitTLS: () => void, - getNewWorker: () => PThreadWorker, - returnWorkerToPool: (worker: PThreadWorker) => void, -} - - -/// N.B. emscripten deletes the `pthread` property from the worker when it is not actively running a pthread -export interface PThreadWorker extends Worker { - pthread_ptr: PThreadPtr; - loaded: boolean; - // this info is updated via async messages from the worker, it could be stale - info: PThreadInfo; - thread?: Thread; -} - -interface PThreadInfoMap { - [key: number]: PThreadWorker; -} - - -export function getWorker(pthreadPtr: PThreadPtr): PThreadWorker | undefined { - return getModulePThread().pthreads[pthreadPtr as any]; -} - -export function allocateUnusedWorker(): void { - /// See library_pthread.js in Emscripten. - /// This function allocates a new worker and adds it to the pool of workers. - /// It's called when the pool of workers is empty and a new thread is created. - getModulePThread().allocateUnusedWorker(); -} -export function getUnusedWorkerPool(): PThreadWorker[] { - return getModulePThread().unusedWorkers; -} -export function getRunningWorkers(): PThreadWorker[] { - return getModulePThread().runningWorkers; -} - -export function loadWasmModuleToWorker(worker: PThreadWorker): Promise { - return getModulePThread().loadWasmModuleToWorker(worker); -} - -export function getModulePThread(): PThreadLibrary { - return (Module).PThread as PThreadLibrary; -} diff --git a/src/mono/browser/runtime/pthreads/shared/index.ts b/src/mono/browser/runtime/pthreads/shared/index.ts index aadab84fa1206..b2d48a112c00a 100644 --- a/src/mono/browser/runtime/pthreads/shared/index.ts +++ b/src/mono/browser/runtime/pthreads/shared/index.ts @@ -8,20 +8,12 @@ import { ENVIRONMENT_IS_PTHREAD, Module, loaderHelpers, mono_assert, runtimeHelp import { set_thread_prefix } from "../../logging"; import { bindings_init } from "../../startup"; import { forceDisposeProxies } from "../../gc-handles"; -import { GCHandle, GCHandleNull, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal"; -import { MonoWorkerToMainMessage, PThreadPtr, PThreadPtrNull } from "./types"; +import { GCHandle, GCHandleNull, MonoThreadMessage, PThreadPtr, PThreadPtrNull, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal"; +import { MonoWorkerToMainMessage } from "./types"; import { monoThreadInfo } from "../worker"; /// Messages sent on the dedicated mono channel between a pthread and the browser thread -// We use a namespacing scheme to avoid collisions: type/command should be unique. -export interface MonoThreadMessage { - // Type of message. Generally a subsystem like "diagnostic_server", or "event_pipe", "debugger", etc. - type: string; - // A particular kind of message. For example, "started", "stopped", "stopped_with_error", etc. - cmd: string; -} - export function isMonoThreadMessage(x: unknown): x is MonoThreadMessage { if (typeof (x) !== "object" || x === null) { return false; @@ -65,6 +57,7 @@ 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 { + if (!WasmEnableThreads) return; const threadType = !monoThreadInfo.isRegistered ? "emsc" : monoThreadInfo.isUI ? "-UI-" : monoThreadInfo.isTimer ? "timr" diff --git a/src/mono/browser/runtime/pthreads/shared/types.ts b/src/mono/browser/runtime/pthreads/shared/types.ts index 1ea8e0c65e579..ce171079172b2 100644 --- a/src/mono/browser/runtime/pthreads/shared/types.ts +++ b/src/mono/browser/runtime/pthreads/shared/types.ts @@ -1,38 +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 type { WorkerToMainMessageType } from "../../types/internal"; - -/// pthread_t in C -export type PThreadPtr = { - __brand: "PThreadPtr" -} -export const PThreadPtrNull: PThreadPtr = 0; - -export interface PThreadInfo { - pthreadId: PThreadPtr; - - reuseCount: number, - updateCount: number, - - threadName: string, - threadPrefix: string, - - isLoaded?: boolean, - isRegistered?: boolean, - isRunning?: boolean, - isAttached?: boolean, - isExternalEventLoop?: boolean, - isUI?: boolean; - isBackground?: boolean, - isDebugger?: boolean, - isThreadPoolWorker?: boolean, - isTimer?: boolean, - isLongRunning?: boolean, - isThreadPoolGate?: boolean, - isFinalizer?: boolean, - isDirtyBecauseOfInterop?: boolean, -} +import type { PThreadInfo, WorkerToMainMessageType } from "../../types/internal"; /// Messages sent from the main thread using Worker.postMessage or from the worker using DedicatedWorkerGlobalScope.postMessage /// should use this interface. The message event is also used by emscripten internals (and possibly by 3rd party libraries targeting Emscripten). diff --git a/src/mono/browser/runtime/pthreads/worker/index.ts b/src/mono/browser/runtime/pthreads/worker/index.ts index 29229c23e5fd8..71e2284af4586 100644 --- a/src/mono/browser/runtime/pthreads/worker/index.ts +++ b/src/mono/browser/runtime/pthreads/worker/index.ts @@ -5,11 +5,9 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { ENVIRONMENT_IS_PTHREAD, loaderHelpers, mono_assert } from "../../globals"; +import { ENVIRONMENT_IS_PTHREAD, loaderHelpers, mono_assert, runtimeHelpers } from "../../globals"; import { mono_wasm_pthread_ptr, postMessageToMain, update_thread_info } from "../shared"; -import { PThreadInfo, PThreadPtr, PThreadPtrNull } from "../shared/types"; -import { WorkerToMainMessageType, is_nullish } from "../../types/internal"; -import { MonoThreadMessage } from "../shared"; +import { MonoThreadMessage, PThreadInfo, PThreadPtr, PThreadPtrNull, WorkerToMainMessageType, is_nullish } from "../../types/internal"; import { makeWorkerThreadEvent, dotnetPthreadCreated, @@ -58,13 +56,14 @@ class WorkerSelf implements PThreadSelf { // we are lying that this is never null, but afterThreadInit should be the first time we get to run any code // in the worker, so this becomes non-null very early. export let pthread_self: PThreadSelf = null as any as PThreadSelf; -export const monoThreadInfo: PThreadInfo = { +const monoThreadInfoPartial: Partial = { pthreadId: PThreadPtrNull, reuseCount: 0, updateCount: 0, threadPrefix: " - ", threadName: "emscripten-loaded", }; +export let monoThreadInfo: PThreadInfo = monoThreadInfoPartial as PThreadInfo; /// This is the "public internal" API for runtime subsystems that wish to be notified about /// pthreads that are running on the current worker. @@ -78,6 +77,7 @@ export let currentWorkerThreadEvents: WorkerThreadEventTarget = undefined as any export function initWorkerThreadEvents() { // treeshake if threads are disabled currentWorkerThreadEvents = WasmEnableThreads ? new globalThis.EventTarget() : null as any as WorkerThreadEventTarget; + monoThreadInfo = Object.assign(monoThreadInfo, runtimeHelpers.monoThreadInfo); } // this is the message handler for the worker that receives messages from the main thread diff --git a/src/mono/browser/runtime/pthreads/worker/replacements.ts b/src/mono/browser/runtime/pthreads/worker/replacements.ts new file mode 100644 index 0000000000000..e08293899295e --- /dev/null +++ b/src/mono/browser/runtime/pthreads/worker/replacements.ts @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import WasmEnableThreads from "consts:wasmEnableThreads"; +import { PThreadLibrary, PThreadPtr } from "../../types/internal"; +import { mono_wasm_pthread_on_pthread_created, onRunMessage as on_emscripten_thread_init } from "."; +import { Module } from "../../globals"; + +export function replaceEmscriptenPThreadWorker(modulePThread: PThreadLibrary): void { + if (!WasmEnableThreads) return; + + const originalThreadInitTLS = modulePThread.threadInitTLS; + const original_emscripten_thread_init = (Module as any)["__emscripten_thread_init"]; + + (Module as any)["__emscripten_thread_init"] = (pthread_ptr: PThreadPtr, isMainBrowserThread: number, isMainRuntimeThread: number, canBlock: number) => { + on_emscripten_thread_init(pthread_ptr); + original_emscripten_thread_init(pthread_ptr, isMainBrowserThread, isMainRuntimeThread, canBlock); + }; + modulePThread.threadInitTLS = (): void => { + originalThreadInitTLS(); + mono_wasm_pthread_on_pthread_created(); + }; +} \ No newline at end of file diff --git a/src/mono/browser/runtime/scheduling.ts b/src/mono/browser/runtime/scheduling.ts index 4651516271fe6..225b4d852d629 100644 --- a/src/mono/browser/runtime/scheduling.ts +++ b/src/mono/browser/runtime/scheduling.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import cwraps from "./cwraps"; import { ENVIRONMENT_IS_WORKER, Module, loaderHelpers } from "./globals"; -import { is_thread_available } from "./pthreads/shared/emscripten-replacements"; +import { is_thread_available } from "./pthreads/browser/replacements"; import { forceThreadMemoryViewRefresh } from "./memory"; let spread_timers_maximum = 0; diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 127d65dea4e81..5d7afe2f5f0c0 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { DotnetModuleInternal, CharPtrNull } from "./types/internal"; 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 cwraps, { init_c_exports } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { toBase64StringImpl } from "./base64"; import { mono_wasm_init_aot_profiler, mono_wasm_init_browser_profiler } from "./profiler"; @@ -23,7 +23,7 @@ import { interp_pgo_load_data, interp_pgo_save_data } from "./interp-pgo"; import { mono_log_debug, mono_log_error, mono_log_warn } from "./logging"; // threads -import { preAllocatePThreadWorkerPool, mono_wasm_init_threads } from "./pthreads/browser"; +import { populateEmscriptenPool, mono_wasm_init_threads, init_finalizer_thread } from "./pthreads/browser"; import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents, monoThreadInfo } from "./pthreads/worker"; import { mono_wasm_pthread_ptr, update_thread_info } from "./pthreads/shared"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; @@ -31,6 +31,7 @@ import { localHeapViewU8 } from "./memory"; import { assertNoProxies } from "./gc-handles"; import { runtimeList } from "./exports"; import { nativeAbort, nativeExit } from "./run"; +import { mono_wasm_init_diagnostics } from "./diagnostics"; export async function configureRuntimeStartup(): Promise { await init_polyfills_async(); @@ -268,7 +269,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { Module.runtimeKeepalivePush(); // load runtime and apply environment settings (if necessary) - start_runtime(); + await start_runtime(); if (runtimeHelpers.config.interpreterPgo) { await interp_pgo_load_data(); @@ -326,10 +327,6 @@ async function postRunAsync(userpostRun: (() => void)[]) { Module["FS_createPath"]("/", "usr", true, true); Module["FS_createPath"]("/", "usr/share", true, true); - if (WasmEnableThreads) { - tcwraps.mono_wasm_init_finalizer_thread(); - } - // all user Module.postRun callbacks userpostRun.map(fn => fn()); endMeasure(mark, MeasuredBlock.postRun); @@ -396,7 +393,7 @@ async function mono_wasm_pre_init_essential_async(): Promise { Module.addRunDependency("mono_wasm_pre_init_essential_async"); if (WasmEnableThreads) { - preAllocatePThreadWorkerPool(runtimeHelpers.config.pthreadPoolSize!); + populateEmscriptenPool(); } Module.removeRunDependency("mono_wasm_pre_init_essential_async"); @@ -483,7 +480,7 @@ async function ensureUsedWasmFeatures() { } } -export function start_runtime() { +export async function start_runtime() { try { const mark = startMeasure(); mono_log_debug("Initializing mono runtime"); @@ -503,6 +500,11 @@ export function start_runtime() { if (runtimeHelpers.config.browserProfilerOptions) mono_wasm_init_browser_profiler(runtimeHelpers.config.browserProfilerOptions); + if (WasmEnableThreads) { + // this is not mono-attached thread, so we can start it earlier + await mono_wasm_init_diagnostics(); + } + mono_wasm_load_runtime(); jiterpreter_allocate_tables(); @@ -514,10 +516,14 @@ export function start_runtime() { if (WasmEnableThreads) { monoThreadInfo.isAttached = true; monoThreadInfo.isRegistered = true; + monoThreadInfo.pthreadId = runtimeHelpers.managedThreadTID = mono_wasm_pthread_ptr(); + monoThreadInfo.workerNumber = 0; update_thread_info(); runtimeHelpers.proxyGCHandle = install_main_synchronization_context(); - runtimeHelpers.managedThreadTID = mono_wasm_pthread_ptr(); runtimeHelpers.isCurrentThread = true; + + // start finalizer thread, lazy + init_finalizer_thread(); } // get GCHandle of the ctx diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index e0f76ea3a546e..8ea520f30ef06 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -144,6 +144,10 @@ export type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number, + /** + * initial number of unused workers keep in the emscripten pthread pool after startup + */ + pthreadPoolReady?: number, /** * If true, a list of the methods optimized by the interpreter will be saved and used for faster startup * on future runs of the application diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index a2eb9b623f90c..2d991a21f4166 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, RuntimeAPI, SingleAssetBehaviors } from "."; -import type { PThreadLibrary } from "../pthreads/shared/emscripten-internals"; -import { PThreadPtr } from "../pthreads/shared/types"; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -15,6 +13,9 @@ export type JSHandle = { export type JSFnHandle = { __brand: "JSFnHandle" } +export type PThreadPtr = { + __brand: "PThreadPtr" // like pthread_t in C +} export interface MonoObject extends ManagedPointer { __brandMonoObject: "MonoObject" } @@ -61,6 +62,7 @@ export const GCHandleInvalid: GCHandle = -1; export const VoidPtrNull: VoidPtr = 0; export const CharPtrNull: CharPtr = 0; export const NativePointerNull: NativePointer = 0; +export const PThreadPtrNull: PThreadPtr = 0; export function coerceNull(ptr: T | null | undefined): T { if ((ptr === null) || (ptr === undefined)) @@ -129,6 +131,8 @@ export type LoaderHelpers = { scriptUrl: string modulesUniqueQuery?: string preferredIcuAsset?: string | null, + loadingWorkers: PThreadWorker[], + workerNextNumber: number, actual_downloaded_assets_count: number, actual_instantiated_assets_count: number, @@ -200,6 +204,7 @@ export type RuntimeHelpers = { getMemory(): WebAssembly.Memory, getWasmIndirectFunctionTable(): WebAssembly.Table, runtimeReady: boolean, + monoThreadInfo: PThreadInfo, proxyGCHandle: GCHandle | undefined, managedThreadTID: PThreadPtr, isCurrentThread: boolean, @@ -488,3 +493,65 @@ export const enum WorkerToMainMessageType { export const enum MainToWorkerMessageType { applyConfig = "apply_mono_config", } + +export interface PThreadWorker extends Worker { + pthread_ptr: PThreadPtr; + loaded: boolean; + // this info is updated via async messages from the worker, it could be stale + info: PThreadInfo; + thread?: Thread; +} + +export interface PThreadInfo { + pthreadId: PThreadPtr; + + workerNumber: number, + reuseCount: number, + updateCount: number, + + threadName: string, + threadPrefix: string, + + isLoaded?: boolean, + isRegistered?: boolean, + isRunning?: boolean, + isAttached?: boolean, + isExternalEventLoop?: boolean, + isUI?: boolean; + isBackground?: boolean, + isDebugger?: boolean, + isThreadPoolWorker?: boolean, + isTimer?: boolean, + isLongRunning?: boolean, + isThreadPoolGate?: boolean, + isFinalizer?: boolean, + isDirtyBecauseOfInterop?: boolean, +} + +export interface PThreadLibrary { + unusedWorkers: PThreadWorker[]; + runningWorkers: PThreadWorker[]; + pthreads: PThreadInfoMap; + allocateUnusedWorker: () => void; + loadWasmModuleToWorker: (worker: PThreadWorker) => Promise; + threadInitTLS: () => void, + getNewWorker: () => PThreadWorker, + returnWorkerToPool: (worker: PThreadWorker) => void, +} + +export interface PThreadInfoMap { + [key: number]: PThreadWorker; +} + +export interface Thread { + readonly pthreadPtr: PThreadPtr; + readonly port: MessagePort; + postMessageToWorker(message: T): void; +} + +export interface MonoThreadMessage { + // Type of message. Generally a subsystem like "diagnostic_server", or "event_pipe", "debugger", etc. + type: string; + // A particular kind of message. For example, "started", "stopped", "stopped_with_error", etc. + cmd: string; +} diff --git a/src/mono/browser/test-main.js b/src/mono/browser/test-main.js index 78c697de19962..5a577dd964098 100644 --- a/src/mono/browser/test-main.js +++ b/src/mono/browser/test-main.js @@ -312,6 +312,8 @@ async function run() { App.runtime = await dotnet.create(); App.runArgs = runArgs + // globalThis.INTERNAL.dumpThreads(); + console.info("Initializing dotnet version " + App.runtime.runtimeBuildInfo.productVersion + " commit hash " + App.runtime.runtimeBuildInfo.gitHash); for (let i = 0; i < runArgs.profilers.length; ++i) {