From c930a99307941edeca814fcc8def1c184e1d254e Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 18 Feb 2024 22:20:09 +0100 Subject: [PATCH 1/5] wip --- src/mono/browser/runtime/assets.ts | 3 +- src/mono/browser/runtime/cwraps.ts | 3 +- src/mono/browser/runtime/debug.ts | 1 + .../runtime/diagnostics/browser/controller.ts | 4 +- src/mono/browser/runtime/diagnostics/index.ts | 9 +- .../diagnostics/shared/controller-commands.ts | 2 +- src/mono/browser/runtime/dotnet.d.ts | 4 + src/mono/browser/runtime/exports-internal.ts | 10 ++- src/mono/browser/runtime/exports.ts | 4 + src/mono/browser/runtime/interp-pgo.ts | 1 + src/mono/browser/runtime/loader/assets.ts | 22 ++++- src/mono/browser/runtime/loader/config.ts | 5 +- src/mono/browser/runtime/loader/globals.ts | 2 + src/mono/browser/runtime/loader/run.ts | 3 +- src/mono/browser/runtime/loader/worker.ts | 11 +-- src/mono/browser/runtime/polyfills.ts | 9 +- .../browser/runtime/pthreads/browser/index.ts | 84 ++++++++++--------- .../replacements.ts} | 55 ++++++------ .../pthreads/shared/emscripten-internals.ts | 65 -------------- .../browser/runtime/pthreads/shared/index.ts | 13 +-- .../browser/runtime/pthreads/shared/types.ts | 33 +------- .../browser/runtime/pthreads/worker/index.ts | 10 +-- .../runtime/pthreads/worker/replacements.ts | 23 +++++ src/mono/browser/runtime/scheduling.ts | 2 +- src/mono/browser/runtime/startup.ts | 26 +++--- src/mono/browser/runtime/types/index.ts | 4 + src/mono/browser/runtime/types/internal.ts | 71 +++++++++++++++- src/mono/browser/test-main.js | 2 + 28 files changed, 264 insertions(+), 217 deletions(-) rename src/mono/browser/runtime/pthreads/{shared/emscripten-replacements.ts => browser/replacements.ts} (70%) delete mode 100644 src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts create mode 100644 src/mono/browser/runtime/pthreads/worker/replacements.ts diff --git a/src/mono/browser/runtime/assets.ts b/src/mono/browser/runtime/assets.ts index bd88949b6234c5..40dc39349ff565 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 7dc7c207697ec9..dbb0babce82319 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 1c919426bc3a84..74c0128f2e4e3b 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 7bcdf62b868505..c03b2e677e1312 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 474fd8a824fd56..a9d6f9414267ae 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 5e08f56c627ea4..b1d9fb02016701 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 e60265f04a2813..b76ba531d5efd6 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 5fe5773f975777..dc03bcf80a9ebb 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 2f3aa96a0ec6ba..b2515b3220c405 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 dfe894f5698263..5be22e37a42c19 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 624244a63ff9c7..5cfc457c4d108e 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 6f64d327fe7add..f7e5c45e5342ad 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 c9d8ffd8e8c9d8..7e9708845e65b0 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 1f85b4e2412002..730a692b4ebf5b 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 81a9cecad6a741..baeeaf0165f673 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 7aae92566b34a0..5f18ba3fd79741 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 84c58b694d13a1..cf384e77cb3c3f 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 0acb8b6615c69d..d4eab5b05bb06d 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 0529744570efda..00000000000000 --- 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 aadab84fa12067..b2d48a112c00aa 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 1ea8e0c65e5796..ce171079172b22 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 29229c23e5fd8a..71e2284af4586e 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 00000000000000..e08293899295e1 --- /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 4651516271fe6b..225b4d852d6291 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 127d65dea4e818..5d7afe2f5f0c0f 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 e0f76ea3a546e8..8ea520f30ef06e 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 a2eb9b623f90c4..2d991a21f41666 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 78c697de19962d..5a577dd964098f 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) { From e63d5b406f9396a91cdf3d0d23fe14c02885e12f Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 19 Feb 2024 11:54:05 +0100 Subject: [PATCH 2/5] Update src/mono/browser/runtime/types/index.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marek Fišera --- src/mono/browser/runtime/types/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index 8ea520f30ef06e..20720dc6f2b30a 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -145,7 +145,7 @@ export type MonoConfig = { */ pthreadPoolSize?: number, /** - * initial number of unused workers keep in the emscripten pthread pool after startup + * initial number of unused workers kept in the emscripten pthread pool after startup */ pthreadPoolReady?: number, /** From 7565533a095073d2c400db73c978d30172ffb3be Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 19 Feb 2024 13:24:19 +0100 Subject: [PATCH 3/5] feedback + refactoring of pthreads folder --- src/mono/browser/build/BrowserWasmApp.targets | 6 +- .../runtime/diagnostics/browser/controller.ts | 2 +- .../runtime/diagnostics/mock/environment.ts | 2 +- .../diagnostics/server_pthread/index.ts | 2 +- .../diagnostics/shared/controller-commands.ts | 2 +- src/mono/browser/runtime/dotnet.d.ts | 10 +- src/mono/browser/runtime/exports-binding.ts | 7 +- src/mono/browser/runtime/exports-internal.ts | 2 +- src/mono/browser/runtime/exports.ts | 6 +- src/mono/browser/runtime/interp-pgo.ts | 5 +- src/mono/browser/runtime/loader/assets.ts | 8 +- src/mono/browser/runtime/loader/config.ts | 11 +- src/mono/browser/runtime/managed-exports.ts | 2 +- src/mono/browser/runtime/marshal-to-cs.ts | 2 +- src/mono/browser/runtime/polyfills.ts | 4 +- src/mono/browser/runtime/pthreads/README.md | 4 +- .../runtime/pthreads/browser/replacements.ts | 140 ---------------- src/mono/browser/runtime/pthreads/index.ts | 18 ++ .../pthreads/{shared/index.ts => shared.ts} | 41 ++++- .../runtime/pthreads/shared/tsconfig.json | 8 - .../browser/runtime/pthreads/shared/types.ts | 13 -- .../{browser/index.ts => ui-thread.ts} | 154 ++++++++++++++++-- .../eventloop.ts => worker-eventloop.ts} | 1 - .../{worker/events.ts => worker-events.ts} | 2 +- .../{worker/index.ts => worker-thread.ts} | 64 ++++---- .../runtime/pthreads/worker/replacements.ts | 23 --- .../runtime/pthreads/worker/tsconfig.json | 7 - src/mono/browser/runtime/run.ts | 2 +- src/mono/browser/runtime/scheduling.ts | 2 +- src/mono/browser/runtime/startup.ts | 6 +- src/mono/browser/runtime/types/index.ts | 10 +- src/mono/browser/test-main.js | 2 - .../BootJsonData.cs | 9 +- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 20 ++- 34 files changed, 301 insertions(+), 296 deletions(-) delete mode 100644 src/mono/browser/runtime/pthreads/browser/replacements.ts create mode 100644 src/mono/browser/runtime/pthreads/index.ts rename src/mono/browser/runtime/pthreads/{shared/index.ts => shared.ts} (77%) delete mode 100644 src/mono/browser/runtime/pthreads/shared/tsconfig.json delete mode 100644 src/mono/browser/runtime/pthreads/shared/types.ts rename src/mono/browser/runtime/pthreads/{browser/index.ts => ui-thread.ts} (57%) rename src/mono/browser/runtime/pthreads/{shared/eventloop.ts => worker-eventloop.ts} (99%) rename src/mono/browser/runtime/pthreads/{worker/events.ts => worker-events.ts} (98%) rename src/mono/browser/runtime/pthreads/{worker/index.ts => worker-thread.ts} (83%) delete mode 100644 src/mono/browser/runtime/pthreads/worker/replacements.ts delete mode 100644 src/mono/browser/runtime/pthreads/worker/tsconfig.json diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index ec2a81e1e066b9..6174769ef02ea4 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -121,7 +121,8 @@ - <_WasmPThreadPoolSize Condition="'$(_WasmPThreadPoolSize)' == ''">-1 + <_WasmPThreadPoolInitialSize Condition="'$(_WasmPThreadPoolInitialSize)' == ''">-1 + <_WasmPThreadPoolUnusedSize Condition="'$(_WasmPThreadPoolUnusedSize)' == ''">-1 @@ -148,7 +149,8 @@ NativeAssets="@(WasmNativeAsset)" DebugLevel="$(WasmDebugLevel)" IncludeThreadsWorker="$(WasmEnableThreads)" - PThreadPoolSize="$(_WasmPThreadPoolSize)" + PThreadPoolInitialSize="$(_WasmPThreadPoolInitialSize)" + PThreadPoolUnusedSize="$(_WasmPThreadPoolUnusedSize)" UseWebcil="$(WasmEnableWebcil)" WasmIncludeFullIcuData="$(WasmIncludeFullIcuData)" WasmIcuDataFileName="$(WasmIcuDataFileName)" diff --git a/src/mono/browser/runtime/diagnostics/browser/controller.ts b/src/mono/browser/runtime/diagnostics/browser/controller.ts index c03b2e677e1312..799972b1fd0e66 100644 --- a/src/mono/browser/runtime/diagnostics/browser/controller.ts +++ b/src/mono/browser/runtime/diagnostics/browser/controller.ts @@ -7,7 +7,7 @@ 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 { waitForThread } from "../../pthreads/browser"; +import { waitForThread } from "../../pthreads"; import { isDiagnosticMessage, makeDiagnosticServerControlCommand } from "../shared/controller-commands"; import monoDiagnosticsMock from "consts:monoDiagnosticsMock"; import { PThreadPtr, Thread } from "../../types/internal"; diff --git a/src/mono/browser/runtime/diagnostics/mock/environment.ts b/src/mono/browser/runtime/diagnostics/mock/environment.ts index bcbdf390a4dfe2..0de8c8b7acafc2 100644 --- a/src/mono/browser/runtime/diagnostics/mock/environment.ts +++ b/src/mono/browser/runtime/diagnostics/mock/environment.ts @@ -6,7 +6,7 @@ import type { FilterPredicate, MockEnvironment } from "./types"; import Serializer from "../server_pthread/ipc-protocol/base-serializer"; import { CommandSetId, EventPipeCommandId, ProcessCommandId } from "../server_pthread/ipc-protocol/types"; import { assertNever } from "../../types/internal"; -import { pthread_self } from "../../pthreads/worker"; +import { pthread_self } from "../../pthreads"; import { createPromiseController, mono_assert } from "../../globals"; diff --git a/src/mono/browser/runtime/diagnostics/server_pthread/index.ts b/src/mono/browser/runtime/diagnostics/server_pthread/index.ts index 1fbca276f3c897..cba9d5fba7d8dc 100644 --- a/src/mono/browser/runtime/diagnostics/server_pthread/index.ts +++ b/src/mono/browser/runtime/diagnostics/server_pthread/index.ts @@ -6,7 +6,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import monoDiagnosticsMock from "consts:monoDiagnosticsMock"; import { PromiseAndController, assertNever } from "../../types/internal"; -import { pthread_self } from "../../pthreads/worker"; +import { pthread_self } from "../../pthreads"; import { createPromiseController, mono_assert } from "../../globals"; import { threads_c_functions as cwraps } from "../../cwraps"; import { EventPipeSessionIDImpl } from "../shared/types"; diff --git a/src/mono/browser/runtime/diagnostics/shared/controller-commands.ts b/src/mono/browser/runtime/diagnostics/shared/controller-commands.ts index b1d9fb02016701..16aa6ad85944f2 100644 --- a/src/mono/browser/runtime/diagnostics/shared/controller-commands.ts +++ b/src/mono/browser/runtime/diagnostics/shared/controller-commands.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 { isMonoThreadMessage } from "../../pthreads/shared"; +import { isMonoThreadMessage } from "../../pthreads"; import type { MonoThreadMessage } from "../../types/internal"; // Messages from the main thread to the diagnostic server thread diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index b76ba531d5efd6..7b901972e8b8f0 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -189,11 +189,15 @@ type MonoConfig = { /** * initial number of workers to add to the emscripten pthread pool */ - pthreadPoolSize?: number; + pthreadPoolInitialSize?: number; /** - * initial number of unused workers keep in the emscripten pthread pool after startup + * number of unused workers kept in the emscripten pthread pool after startup */ - pthreadPoolReady?: number; + pthreadPoolUnusedSize?: number; + /** + * Delay in milliseconds before starting the finalizer thread + */ + finalizerThreadStartDelayMs?: 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-binding.ts b/src/mono/browser/runtime/exports-binding.ts index c410dca9e75881..6ab2b430ab0481 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -10,8 +10,7 @@ import { mono_interp_tier_prepare_jiterpreter, mono_jiterp_free_method_data_js } import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry } from "./jiterpreter-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, mono_wasm_pthread_set_name } from "./pthreads/worker"; +import { mono_wasm_eventloop_has_unsettled_interop_promises } from "./pthreads"; 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"; @@ -22,13 +21,15 @@ import { mono_wasm_profiler_leave, mono_wasm_profiler_enter } from "./profiler"; import { mono_wasm_change_case, mono_wasm_change_case_invariant } from "./hybrid-globalization/change-case"; import { mono_wasm_compare_string, mono_wasm_ends_with, mono_wasm_starts_with, mono_wasm_index_of } from "./hybrid-globalization/collations"; import { mono_wasm_get_calendar_info } from "./hybrid-globalization/calendar"; -import { mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop } from "./pthreads/shared"; import { mono_wasm_get_culture_info } from "./hybrid-globalization/culture-info"; import { mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year } from "./hybrid-globalization/locales"; import { mono_wasm_browser_entropy } from "./crypto"; import { mono_wasm_cancel_promise } from "./cancelable-promise"; +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"; +import { mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop } from "./pthreads"; + // the JS methods would be visible to EMCC linker and become imports of the WASM module export const mono_wasm_threads_imports = !WasmEnableThreads ? [] : [ diff --git a/src/mono/browser/runtime/exports-internal.ts b/src/mono/browser/runtime/exports-internal.ts index dc03bcf80a9ebb..80c52669fb3ad6 100644 --- a/src/mono/browser/runtime/exports-internal.ts +++ b/src/mono/browser/runtime/exports-internal.ts @@ -23,7 +23,7 @@ import { mono_wasm_get_func_id_to_name_mappings } from "./logging"; import { monoStringToStringUnsafe } from "./strings"; import { mono_wasm_bind_cs_function } from "./invoke-cs"; -import { dumpThreads, thread_available } from "./pthreads/browser"; +import { dumpThreads, thread_available } from "./pthreads"; export function export_internal(): any { return { diff --git a/src/mono/browser/runtime/exports.ts b/src/mono/browser/runtime/exports.ts index b2515b3220c405..8b2984f393b1f0 100644 --- a/src/mono/browser/runtime/exports.ts +++ b/src/mono/browser/runtime/exports.ts @@ -22,7 +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"; +import { dumpThreads } from "./pthreads"; export let runtimeList: RuntimeList; @@ -70,10 +70,6 @@ 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 5be22e37a42c19..79ea1e29ab5df1 100644 --- a/src/mono/browser/runtime/interp-pgo.ts +++ b/src/mono/browser/runtime/interp-pgo.ts @@ -197,8 +197,9 @@ export async function getCacheKey(prefix: string): Promise { delete inputs.interopCleanupOnExit; delete inputs.dumpThreadsOnNonZeroExit; delete inputs.logExitCode; - delete inputs.pthreadPoolSize; - delete inputs.pthreadPoolReady; + delete inputs.pthreadPoolInitialSize; + delete inputs.pthreadPoolUnusedSize; + delete inputs.finalizerThreadStartDelayMs; 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 5cfc457c4d108e..a4acf82d8f7e63 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -20,6 +20,8 @@ let parallel_count = 0; const assetsToLoad: AssetEntryInternal[] = []; const singleAssets: Map = new Map(); +const worker_empty_prefix = " - "; + const jsRuntimeModulesAssetTypes: { [k: string]: boolean } = { @@ -738,7 +740,7 @@ export async function streamingCompileWasm() { export function preloadWorkers() { if (!WasmEnableThreads) return; const jsModuleWorker = resolve_single_asset_path("js-module-threads"); - for (let i = 0; i < loaderHelpers.config.pthreadPoolSize!; i++) { + for (let i = 0; i < loaderHelpers.config.pthreadPoolInitialSize!; i++) { const workerNumber = loaderHelpers.workerNextNumber++; const worker: Partial = new Worker(jsModuleWorker.resolvedUrl!, { name: "dotnet-worker-" + workerNumber.toString().padStart(3, "0"), @@ -748,9 +750,9 @@ export function preloadWorkers() { pthreadId: PThreadPtrNull, reuseCount: 0, updateCount: 0, - threadPrefix: " - ", + threadPrefix: worker_empty_prefix, 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 f7e5c45e5342ad..8aec3557437212 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -188,11 +188,14 @@ export function normalizeConfig() { } // ActiveIssue https://github.com/dotnet/runtime/issues/75602 - if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolSize)) { - config.pthreadPoolSize = 7; + if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolInitialSize)) { + config.pthreadPoolInitialSize = 7; } - if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolReady)) { - config.pthreadPoolReady = 3; + if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolUnusedSize)) { + config.pthreadPoolUnusedSize = 3; + } + if (WasmEnableThreads && !Number.isInteger(config.finalizerThreadStartDelayMs)) { + config.finalizerThreadStartDelayMs = 200; } // 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 diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts index 87da9ac8d259ef..c8aabae88e37b7 100644 --- a/src/mono/browser/runtime/managed-exports.ts +++ b/src/mono/browser/runtime/managed-exports.ts @@ -11,7 +11,7 @@ import { marshal_array_to_cs, marshal_array_to_cs_impl, marshal_bool_to_cs, mars import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js, marshal_exception_to_js } from "./marshal-to-js"; import { do_not_force_dispose } from "./gc-handles"; import { assert_c_interop, assert_js_interop } from "./invoke-js"; -import { mono_wasm_main_thread_ptr } from "./pthreads/shared"; +import { mono_wasm_main_thread_ptr } from "./pthreads"; import { _zero_region } from "./memory"; import { stringToUTF8Ptr } from "./strings"; diff --git a/src/mono/browser/runtime/marshal-to-cs.ts b/src/mono/browser/runtime/marshal-to-cs.ts index fa0f299a5f2b80..a42eb724a92151 100644 --- a/src/mono/browser/runtime/marshal-to-cs.ts +++ b/src/mono/browser/runtime/marshal-to-cs.ts @@ -22,7 +22,7 @@ import { _zero_region, forceThreadMemoryViewRefresh, localHeapViewF64, localHeap import { stringToMonoStringRoot, stringToUTF16 } from "./strings"; import { JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; -import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads/shared/eventloop"; +import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads"; import { mono_log_debug } from "./logging"; import { complete_task } from "./managed-exports"; import { gc_locked } from "./gc-lock"; diff --git a/src/mono/browser/runtime/polyfills.ts b/src/mono/browser/runtime/polyfills.ts index 5f18ba3fd79741..0f8700f84754ec 100644 --- a/src/mono/browser/runtime/polyfills.ts +++ b/src/mono/browser/runtime/polyfills.ts @@ -5,8 +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 { replaceEmscriptenPThreadUI } from "./pthreads/browser/replacements"; -import { replaceEmscriptenPThreadWorker } from "./pthreads/worker/replacements"; +import { replaceEmscriptenPThreadWorker } from "./pthreads"; +import { replaceEmscriptenPThreadUI } from "./pthreads"; const dummyPerformance = { now: function () { diff --git a/src/mono/browser/runtime/pthreads/README.md b/src/mono/browser/runtime/pthreads/README.md index 34f3508988cc9f..757cc73a85e420 100644 --- a/src/mono/browser/runtime/pthreads/README.md +++ b/src/mono/browser/runtime/pthreads/README.md @@ -17,11 +17,11 @@ On the other hand, pthreads in native code have a peer relationship: any two thr ## Main thread API -In the main thread, `pthreads/browser` provides a `getThread` function that returns a `{ pthread_ptr: pthread_ptr, worker: Worker, port: MessagePort }` object that can be used to communicate with the worker thread. +In the main thread, `pthreads/ui-thread` provides a `getThread` function that returns a `{ pthread_ptr: pthread_ptr, worker: Worker, port: MessagePort }` object that can be used to communicate with the worker thread. ## Worker thread API -In the worker threads, `pthread/worker` provides `currentWorkerThreadEvents` which is an [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) that fires `'dotnet:pthread:created'` and `'dotnet:pthread:attached'` events when a pthread is started on the worker, and when that pthread attaches to the Mono runtime. A good place to add event listeners is in `mono_wasm_pthread_worker_init` in `startup.ts`. +In the worker threads, `pthread/worker-*` provides `currentWorkerThreadEvents` which is an [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) that fires `'dotnet:pthread:created'` and `'dotnet:pthread:attached'` events when a pthread is started on the worker, and when that pthread attaches to the Mono runtime. A good place to add event listeners is in `mono_wasm_pthread_worker_init` in `startup.ts`. The events have a `portToMain` property which is a dotnet-specific `MessagePort` for posting messages to the main thread and for listening for messages from the main thread. ## Implementation diff --git a/src/mono/browser/runtime/pthreads/browser/replacements.ts b/src/mono/browser/runtime/pthreads/browser/replacements.ts deleted file mode 100644 index d4eab5b05bb06d..00000000000000 --- a/src/mono/browser/runtime/pthreads/browser/replacements.ts +++ /dev/null @@ -1,140 +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 WasmEnableThreads from "consts:wasmEnableThreads"; -import BuildConfiguration from "consts:configuration"; - -import { dumpThreads, onWorkerLoadInitiated, resolveThreadPromises } from "."; -import { Module, loaderHelpers, mono_assert } from "../../globals"; -import { mono_log_warn } from "../../logging"; -import { PThreadLibrary, PThreadPtr, PThreadPtrNull, PThreadWorker } from "../../types/internal"; - -export function replaceEmscriptenPThreadUI(modulePThread: PThreadLibrary): void { - if (!WasmEnableThreads) return; - - const originalLoadWasmModuleToWorker = modulePThread.loadWasmModuleToWorker; - const originalReturnWorkerToPool = modulePThread.returnWorkerToPool; - - modulePThread.loadWasmModuleToWorker = (worker: PThreadWorker): Promise => { - const afterLoaded = originalLoadWasmModuleToWorker(worker); - afterLoaded.then(() => { - availableThreadCount++; - }); - onWorkerLoadInitiated(worker, afterLoaded); - if (loaderHelpers.config.exitOnUnhandledError) { - worker.onerror = (e) => { - loaderHelpers.mono_exit(1, e); - }; - } - return afterLoaded; - }; - modulePThread.allocateUnusedWorker = allocateUnusedWorker; - modulePThread.getNewWorker = () => getNewWorker(modulePThread); - modulePThread.returnWorkerToPool = (worker: PThreadWorker) => { - // when JS interop is installed on JSWebWorker - // we can't reuse the worker, because user code could leave the worker JS globals in a dirty state - worker.info.isRunning = false; - resolveThreadPromises(worker.pthread_ptr, undefined); - worker.info.pthreadId = PThreadPtrNull; - if (worker.thread?.port) { - worker.thread.port.close(); - } - worker.thread = undefined; - if (worker.info && worker.info.isDirtyBecauseOfInterop) { - // we are on UI thread, invoke the handler directly to destroy the dirty worker - worker.onmessage!(new MessageEvent("message", { - data: { - "cmd": "killThread", - thread: worker.pthread_ptr - } - })); - } else { - availableThreadCount++; - originalReturnWorkerToPool(worker); - } - }; - if (BuildConfiguration === "Debug") { - (globalThis as any).dumpThreads = dumpThreads; - (globalThis as any).getModulePThread = getModulePThread; - } -} - -let availableThreadCount = 0; -export function is_thread_available() { - if (!WasmEnableThreads) return true; - return availableThreadCount > 0; -} - -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 pthreadPoolReady. Running threads ${modulePThread.runningWorkers.length}. Loading workers: ${modulePThread.unusedWorkers.length}`); - const worker = allocateUnusedWorker(); - modulePThread.loadWasmModuleToWorker(worker); - availableThreadCount--; - return worker; - } - - // keep them pre-allocated all the time, not just during startup - if (modulePThread.unusedWorkers.length <= loaderHelpers.config.pthreadPoolReady!) { - const worker = allocateUnusedWorker(); - modulePThread.loadWasmModuleToWorker(worker); - } - - for (let i = 0; i < modulePThread.unusedWorkers.length; i++) { - const worker = modulePThread.unusedWorkers[i]; - if (worker.loaded) { - modulePThread.unusedWorkers.splice(i, 1); - availableThreadCount--; - return worker; - } - } - 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()!; -} - -/// We replace Module["PThreads"].allocateUnusedWorker with this version that knows about assets -function allocateUnusedWorker(): PThreadWorker { - if (!WasmEnableThreads) return null as any; - - 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 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, - threadPrefix: " - ", - threadName: "emscripten-pool", - }; - 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/index.ts b/src/mono/browser/runtime/pthreads/index.ts new file mode 100644 index 00000000000000..a7b5da11e03fbe --- /dev/null +++ b/src/mono/browser/runtime/pthreads/index.ts @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +export { + mono_wasm_main_thread_ptr, mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop, + mono_wasm_pthread_ptr, update_thread_info, isMonoThreadMessage, monoThreadInfo, +} from "./shared"; +export { + dumpThreads, thread_available, cancelThreads, is_thread_available, + populateEmscriptenPool, mono_wasm_init_threads, init_finalizer_thread, + waitForThread, replaceEmscriptenPThreadUI +} from "./ui-thread"; +export { addUnsettledPromise, settleUnsettledPromise, mono_wasm_eventloop_has_unsettled_interop_promises } from "./worker-eventloop"; +export { + mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_unregistered, + mono_wasm_pthread_on_pthread_registered, mono_wasm_pthread_set_name, currentWorkerThreadEvents, + dotnetPthreadCreated, initWorkerThreadEvents, replaceEmscriptenPThreadWorker, pthread_self +} from "./worker-thread"; diff --git a/src/mono/browser/runtime/pthreads/shared/index.ts b/src/mono/browser/runtime/pthreads/shared.ts similarity index 77% rename from src/mono/browser/runtime/pthreads/shared/index.ts rename to src/mono/browser/runtime/pthreads/shared.ts index b2d48a112c00aa..03fd849fb7e96f 100644 --- a/src/mono/browser/runtime/pthreads/shared/index.ts +++ b/src/mono/browser/runtime/pthreads/shared.ts @@ -4,15 +4,24 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; -import { ENVIRONMENT_IS_PTHREAD, Module, loaderHelpers, mono_assert, runtimeHelpers } from "../../globals"; -import { set_thread_prefix } from "../../logging"; -import { bindings_init } from "../../startup"; -import { forceDisposeProxies } from "../../gc-handles"; -import { GCHandle, GCHandleNull, MonoThreadMessage, PThreadPtr, PThreadPtrNull, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal"; -import { MonoWorkerToMainMessage } from "./types"; -import { monoThreadInfo } from "../worker"; +import type { GCHandle, MonoThreadMessage, PThreadInfo, PThreadPtr } from "../types/internal"; -/// Messages sent on the dedicated mono channel between a pthread and the browser thread +import { ENVIRONMENT_IS_PTHREAD, Module, loaderHelpers, mono_assert, runtimeHelpers } from "../globals"; +import { set_thread_prefix } from "../logging"; +import { bindings_init } from "../startup"; +import { forceDisposeProxies } from "../gc-handles"; +import { monoMessageSymbol, GCHandleNull, PThreadPtrNull, WorkerToMainMessageType } from "../types/internal"; + +export const worker_empty_prefix = " - "; + +const monoThreadInfoPartial: Partial = { + pthreadId: PThreadPtrNull, + reuseCount: 0, + updateCount: 0, + threadPrefix: worker_empty_prefix, + threadName: "emscripten-loaded", +}; +export const monoThreadInfo: PThreadInfo = monoThreadInfoPartial as PThreadInfo; export function isMonoThreadMessage(x: unknown): x is MonoThreadMessage { if (typeof (x) !== "object" || x === null) { @@ -103,4 +112,18 @@ export function postMessageToMain(message: MonoWorkerToMainMessage, transfer?: T self.postMessage({ [monoMessageSymbol]: message }, transfer ? transfer : []); -} \ No newline at end of file +} + +export interface MonoWorkerToMainMessage { + monoCmd: WorkerToMainMessageType; + info: PThreadInfo; + port?: MessagePort; +} + +/// Identification of the current thread executing on a worker +export interface PThreadSelf { + info: PThreadInfo; + portToBrowser: MessagePort; + postMessageToBrowser: (message: T, transfer?: Transferable[]) => void; + addEventListenerFromBrowser: (listener: (event: MessageEvent) => void) => void; +} diff --git a/src/mono/browser/runtime/pthreads/shared/tsconfig.json b/src/mono/browser/runtime/pthreads/shared/tsconfig.json deleted file mode 100644 index 8986477dd8fc35..00000000000000 --- a/src/mono/browser/runtime/pthreads/shared/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.shared.json", - "include": [ - "../../**/*.ts", - "../../**/*.d.ts" - ] - -} diff --git a/src/mono/browser/runtime/pthreads/shared/types.ts b/src/mono/browser/runtime/pthreads/shared/types.ts deleted file mode 100644 index ce171079172b22..00000000000000 --- a/src/mono/browser/runtime/pthreads/shared/types.ts +++ /dev/null @@ -1,13 +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 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). -/// We should just use this to establish a dedicated MessagePort for Mono's uses. -export interface MonoWorkerToMainMessage { - monoCmd: WorkerToMainMessageType; - info: PThreadInfo; - port?: MessagePort; -} diff --git a/src/mono/browser/runtime/pthreads/browser/index.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts similarity index 57% rename from src/mono/browser/runtime/pthreads/browser/index.ts rename to src/mono/browser/runtime/pthreads/ui-thread.ts index cf384e77cb3c3f..878ee38eadbb9a 100644 --- a/src/mono/browser/runtime/pthreads/browser/index.ts +++ b/src/mono/browser/runtime/pthreads/ui-thread.ts @@ -2,15 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. import WasmEnableThreads from "consts:wasmEnableThreads"; +import BuildConfiguration from "consts:configuration"; -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 { getRunningWorkers, getUnusedWorkerPool, getWorker, loadWasmModuleToWorker } from "./replacements"; -import { threads_c_functions as cwraps } from "../../cwraps"; +import { } from "../globals"; +import { mono_log_warn } from "../logging"; +import { MonoWorkerToMainMessage, monoThreadInfo, mono_wasm_pthread_ptr, update_thread_info, worker_empty_prefix } from "./shared"; +import { Module, ENVIRONMENT_IS_WORKER, createPromiseController, loaderHelpers, mono_assert, runtimeHelpers } from "../globals"; +import { PThreadLibrary, MainToWorkerMessageType, MonoThreadMessage, PThreadInfo, PThreadPtr, PThreadPtrNull, PThreadWorker, PromiseAndController, PromiseController, Thread, WorkerToMainMessageType, monoMessageSymbol } from "../types/internal"; +import { mono_log_error, mono_log_info } from "../logging"; +import { threads_c_functions as cwraps } from "../cwraps"; const threadPromises: Map[]> = new Map(); @@ -141,7 +141,7 @@ export function populateEmscriptenPool(): void { for (const worker of loaderHelpers.loadingWorkers) { unused.push(worker); } - loaderHelpers.loadingWorkers.length = 0; // GC + loaderHelpers.loadingWorkers = []; } export async function mono_wasm_init_threads() { @@ -179,7 +179,7 @@ export function dumpThreads(): void { const emptyInfo: PThreadInfo = { workerNumber: -1, pthreadId: PThreadPtrNull, - threadPrefix: " - ", + threadPrefix: worker_empty_prefix, threadName: "????", isRunning: false, isAttached: false, @@ -217,5 +217,135 @@ export function init_finalizer_thread() { mono_log_error("init_finalizer_thread() failed", err); loaderHelpers.mono_exit(1, err); } - }, 200); -} \ No newline at end of file + }, loaderHelpers.config.finalizerThreadStartDelayMs); +} + +export function replaceEmscriptenPThreadUI(modulePThread: PThreadLibrary): void { + if (!WasmEnableThreads) return; + + const originalLoadWasmModuleToWorker = modulePThread.loadWasmModuleToWorker; + const originalReturnWorkerToPool = modulePThread.returnWorkerToPool; + + modulePThread.loadWasmModuleToWorker = (worker: PThreadWorker): Promise => { + const afterLoaded = originalLoadWasmModuleToWorker(worker); + afterLoaded.then(() => { + availableThreadCount++; + }); + onWorkerLoadInitiated(worker, afterLoaded); + if (loaderHelpers.config.exitOnUnhandledError) { + worker.onerror = (e) => { + loaderHelpers.mono_exit(1, e); + }; + } + return afterLoaded; + }; + modulePThread.allocateUnusedWorker = allocateUnusedWorker; + modulePThread.getNewWorker = () => getNewWorker(modulePThread); + modulePThread.returnWorkerToPool = (worker: PThreadWorker) => { + // when JS interop is installed on JSWebWorker + // we can't reuse the worker, because user code could leave the worker JS globals in a dirty state + worker.info.isRunning = false; + resolveThreadPromises(worker.pthread_ptr, undefined); + worker.info.pthreadId = PThreadPtrNull; + if (worker.thread?.port) { + worker.thread.port.close(); + } + worker.thread = undefined; + if (worker.info && worker.info.isDirtyBecauseOfInterop) { + // we are on UI thread, invoke the handler directly to destroy the dirty worker + worker.onmessage!(new MessageEvent("message", { + data: { + "cmd": "killThread", + thread: worker.pthread_ptr + } + })); + } else { + availableThreadCount++; + originalReturnWorkerToPool(worker); + } + }; + if (BuildConfiguration === "Debug") { + (globalThis as any).dumpThreads = dumpThreads; + (globalThis as any).getModulePThread = getModulePThread; + } +} + +let availableThreadCount = 0; +export function is_thread_available() { + if (!WasmEnableThreads) return true; + return availableThreadCount > 0; +} + +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 pthreadPoolReady. Running threads ${modulePThread.runningWorkers.length}. Loading workers: ${modulePThread.unusedWorkers.length}`); + const worker = allocateUnusedWorker(); + modulePThread.loadWasmModuleToWorker(worker); + availableThreadCount--; + return worker; + } + + // keep them pre-allocated all the time, not just during startup + if (modulePThread.unusedWorkers.length <= loaderHelpers.config.pthreadPoolUnusedSize!) { + const worker = allocateUnusedWorker(); + modulePThread.loadWasmModuleToWorker(worker); + } + + for (let i = 0; i < modulePThread.unusedWorkers.length; i++) { + const worker = modulePThread.unusedWorkers[i]; + if (worker.loaded) { + modulePThread.unusedWorkers.splice(i, 1); + availableThreadCount--; + return worker; + } + } + 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()!; +} + +/// We replace Module["PThreads"].allocateUnusedWorker with this version that knows about assets +function allocateUnusedWorker(): PThreadWorker { + if (!WasmEnableThreads) return null as any; + + 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 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, + threadPrefix: worker_empty_prefix, + threadName: "emscripten-pool", + }; + 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/eventloop.ts b/src/mono/browser/runtime/pthreads/worker-eventloop.ts similarity index 99% rename from src/mono/browser/runtime/pthreads/shared/eventloop.ts rename to src/mono/browser/runtime/pthreads/worker-eventloop.ts index 602c3fb221fbd1..7b8a42b05ac660 100644 --- a/src/mono/browser/runtime/pthreads/shared/eventloop.ts +++ b/src/mono/browser/runtime/pthreads/worker-eventloop.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. - let perThreadUnsettledPromiseCount = 0; export function addUnsettledPromise() { diff --git a/src/mono/browser/runtime/pthreads/worker/events.ts b/src/mono/browser/runtime/pthreads/worker-events.ts similarity index 98% rename from src/mono/browser/runtime/pthreads/worker/events.ts rename to src/mono/browser/runtime/pthreads/worker-events.ts index ace256459d4393..709e1980d82f58 100644 --- a/src/mono/browser/runtime/pthreads/worker/events.ts +++ b/src/mono/browser/runtime/pthreads/worker-events.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import WasmEnableThreads from "consts:wasmEnableThreads"; -import { PThreadSelf } from "./index"; +import { PThreadSelf } from "./shared"; export const dotnetPthreadCreated = "dotnet:pthread:created" as const; export const dotnetPthreadAttached = "dotnet:pthread:attached" as const; diff --git a/src/mono/browser/runtime/pthreads/worker/index.ts b/src/mono/browser/runtime/pthreads/worker-thread.ts similarity index 83% rename from src/mono/browser/runtime/pthreads/worker/index.ts rename to src/mono/browser/runtime/pthreads/worker-thread.ts index 71e2284af4586e..083f87d7de025e 100644 --- a/src/mono/browser/runtime/pthreads/worker/index.ts +++ b/src/mono/browser/runtime/pthreads/worker-thread.ts @@ -5,20 +5,22 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { ENVIRONMENT_IS_PTHREAD, loaderHelpers, mono_assert, runtimeHelpers } from "../../globals"; -import { mono_wasm_pthread_ptr, postMessageToMain, update_thread_info } from "../shared"; -import { MonoThreadMessage, PThreadInfo, PThreadPtr, PThreadPtrNull, WorkerToMainMessageType, is_nullish } from "../../types/internal"; +import { Module } from "../globals"; + +import { ENVIRONMENT_IS_PTHREAD, loaderHelpers, mono_assert, runtimeHelpers } from "../globals"; +import { PThreadSelf, monoThreadInfo, mono_wasm_pthread_ptr, postMessageToMain, update_thread_info } from "./shared"; +import { PThreadLibrary, MonoThreadMessage, PThreadInfo, PThreadPtr, WorkerToMainMessageType, is_nullish } from "../types/internal"; import { makeWorkerThreadEvent, dotnetPthreadCreated, dotnetPthreadAttached, WorkerThreadEventTarget -} from "./events"; -import { postRunWorker, preRunWorker } from "../../startup"; -import { mono_log_debug, mono_log_error } from "../../logging"; -import { CharPtr } from "../../types/emscripten"; -import { utf8ToString } from "../../strings"; -import { forceThreadMemoryViewRefresh } from "../../memory"; +} from "./worker-events"; +import { postRunWorker, preRunWorker } from "../startup"; +import { mono_log_debug, mono_log_error } from "../logging"; +import { CharPtr } from "../types/emscripten"; +import { utf8ToString } from "../strings"; +import { forceThreadMemoryViewRefresh } from "../memory"; // re-export some of the events types export { @@ -27,15 +29,9 @@ export { dotnetPthreadCreated, WorkerThreadEvent, WorkerThreadEventTarget, -} from "./events"; - -/// Identification of the current thread executing on a worker -export interface PThreadSelf { - info: PThreadInfo; - portToBrowser: MessagePort; - postMessageToBrowser: (message: T, transfer?: Transferable[]) => void; - addEventListenerFromBrowser: (listener: (event: MessageEvent) => void) => void; -} +} from "./worker-events"; + +export let pthread_self: PThreadSelf = null as any as PThreadSelf; class WorkerSelf implements PThreadSelf { constructor(public info: PThreadInfo, public portToBrowser: MessagePort) { @@ -53,18 +49,6 @@ 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; -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. /// Example: @@ -77,7 +61,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); + Object.assign(monoThreadInfo, runtimeHelpers.monoThreadInfo); } // this is the message handler for the worker that receives messages from the main thread @@ -86,7 +70,7 @@ function monoDedicatedChannelMessageFromMainToWorker(event: MessageEvent mono_log_debug("got message from main on the dedicated channel", event.data); } -export function onRunMessage(pthread_ptr: PThreadPtr) { +export function on_emscripten_thread_init(pthread_ptr: PThreadPtr) { monoThreadInfo.pthreadId = pthread_ptr; forceThreadMemoryViewRefresh(); } @@ -221,3 +205,19 @@ export function mono_wasm_pthread_on_pthread_unregistered(pthread_id: PThreadPtr throw err; } } + +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/pthreads/worker/replacements.ts b/src/mono/browser/runtime/pthreads/worker/replacements.ts deleted file mode 100644 index e08293899295e1..00000000000000 --- a/src/mono/browser/runtime/pthreads/worker/replacements.ts +++ /dev/null @@ -1,23 +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 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/pthreads/worker/tsconfig.json b/src/mono/browser/runtime/pthreads/worker/tsconfig.json deleted file mode 100644 index 071a4d824c62a4..00000000000000 --- a/src/mono/browser/runtime/pthreads/worker/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.worker.json", - "include": [ - "../../**/*.ts", - "../../**/*.d.ts" - ] -} diff --git a/src/mono/browser/runtime/run.ts b/src/mono/browser/runtime/run.ts index 8760aab6514cad..e35c6dc833aecd 100644 --- a/src/mono/browser/runtime/run.ts +++ b/src/mono/browser/runtime/run.ts @@ -8,7 +8,7 @@ import { mono_wasm_wait_for_debugger } from "./debug"; import { mono_wasm_set_main_args } from "./startup"; import cwraps from "./cwraps"; import { mono_log_info } from "./logging"; -import { cancelThreads } from "./pthreads/browser"; +import { cancelThreads } from "./pthreads"; import { call_entry_point } from "./managed-exports"; /** diff --git a/src/mono/browser/runtime/scheduling.ts b/src/mono/browser/runtime/scheduling.ts index e24516db36366c..c9cbf205d42d41 100644 --- a/src/mono/browser/runtime/scheduling.ts +++ b/src/mono/browser/runtime/scheduling.ts @@ -5,8 +5,8 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import cwraps from "./cwraps"; import { ENVIRONMENT_IS_WORKER, Module, loaderHelpers } from "./globals"; -import { is_thread_available } from "./pthreads/browser/replacements"; import { forceThreadMemoryViewRefresh } from "./memory"; +import { is_thread_available } from "./pthreads"; let spread_timers_maximum = 0; let pump_count = 0; diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 5d7afe2f5f0c0f..9759e879254b08 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -23,9 +23,9 @@ 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 { 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 { populateEmscriptenPool, mono_wasm_init_threads, init_finalizer_thread } from "./pthreads"; +import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents, monoThreadInfo } from "./pthreads"; +import { mono_wasm_pthread_ptr, update_thread_info } from "./pthreads"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; import { localHeapViewU8 } from "./memory"; import { assertNoProxies } from "./gc-handles"; diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index 20720dc6f2b30a..8d9c8a28ba1480 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -143,11 +143,15 @@ export type MonoConfig = { /** * initial number of workers to add to the emscripten pthread pool */ - pthreadPoolSize?: number, + pthreadPoolInitialSize?: number, /** - * initial number of unused workers kept in the emscripten pthread pool after startup + * number of unused workers kept in the emscripten pthread pool after startup */ - pthreadPoolReady?: number, + pthreadPoolUnusedSize?: number, + /** + * Delay in milliseconds before starting the finalizer thread + */ + finalizerThreadStartDelayMs?: 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/test-main.js b/src/mono/browser/test-main.js index 5a577dd964098f..78c697de19962d 100644 --- a/src/mono/browser/test-main.js +++ b/src/mono/browser/test-main.js @@ -312,8 +312,6 @@ 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) { diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 97bd05110bfbfc..22edc2c68fbe1a 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -108,9 +108,14 @@ public class BootJsonData public object diagnosticTracing { get; set; } /// - /// Gets or sets pthread pool size. + /// Gets or sets pthread pool initial size. /// - public int? pthreadPoolSize { get; set; } + public int? pthreadPoolInitialSize { get; set; } + + /// + /// Gets or sets pthread pool unused size. + /// + public int? pthreadPoolUnusedSize { get; set; } } public class ResourcesData diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 48ce5d34d04bbe..7847039163b11a 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -23,7 +23,8 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask { public ITaskItem[]? RemoteSources { get; set; } public bool IncludeThreadsWorker { get; set; } - public int PThreadPoolSize { get; set; } + public int PThreadPoolInitialSize { get; set; } + public int PThreadPoolUnusedSize { get; set; } public bool UseWebcil { get; set; } public bool WasmIncludeFullIcuData { get; set; } public string? WasmIcuDataFileName { get; set; } @@ -334,13 +335,22 @@ protected override bool ExecuteInternal() var extraConfiguration = new Dictionary(); - if (PThreadPoolSize < -1) + if (PThreadPoolInitialSize < -1) { - throw new LogAsErrorException($"PThreadPoolSize must be -1, 0 or positive, but got {PThreadPoolSize}"); + throw new LogAsErrorException($"PThreadPoolInitialSize must be -1, 0 or positive, but got {PThreadPoolInitialSize}"); } - else if (PThreadPoolSize > -1) + else if (PThreadPoolInitialSize > -1) { - bootConfig.pthreadPoolSize = PThreadPoolSize; + bootConfig.pthreadPoolInitialSize = PThreadPoolInitialSize; + } + + if (PThreadPoolUnusedSize < -1) + { + throw new LogAsErrorException($"PThreadPoolUnusedSize must be -1, 0 or positive, but got {PThreadPoolUnusedSize}"); + } + else if (PThreadPoolUnusedSize > -1) + { + bootConfig.pthreadPoolUnusedSize = PThreadPoolUnusedSize; } foreach (ITaskItem extra in ExtraConfig ?? Enumerable.Empty()) From 7b5291ba2865fe7203ca48a034ec985f2ba907d5 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 19 Feb 2024 13:33:33 +0100 Subject: [PATCH 4/5] Update src/mono/browser/runtime/pthreads/shared.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marek Fišera --- src/mono/browser/runtime/pthreads/shared.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/browser/runtime/pthreads/shared.ts b/src/mono/browser/runtime/pthreads/shared.ts index 03fd849fb7e96f..97c41197d35e04 100644 --- a/src/mono/browser/runtime/pthreads/shared.ts +++ b/src/mono/browser/runtime/pthreads/shared.ts @@ -12,6 +12,7 @@ import { bindings_init } from "../startup"; import { forceDisposeProxies } from "../gc-handles"; import { monoMessageSymbol, GCHandleNull, PThreadPtrNull, WorkerToMainMessageType } from "../types/internal"; +// A duplicate in loader/assets.ts export const worker_empty_prefix = " - "; const monoThreadInfoPartial: Partial = { From 6aefd853a82d5c28bbb0511b623fb067d55cb55b Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 19 Feb 2024 13:33:40 +0100 Subject: [PATCH 5/5] Update src/mono/browser/runtime/loader/assets.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marek Fišera --- src/mono/browser/runtime/loader/assets.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/browser/runtime/loader/assets.ts b/src/mono/browser/runtime/loader/assets.ts index a4acf82d8f7e63..49d2f0f8ac6d8c 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -20,6 +20,7 @@ let parallel_count = 0; const assetsToLoad: AssetEntryInternal[] = []; const singleAssets: Map = new Map(); +// A duplicate in pthreads/shared.ts const worker_empty_prefix = " - "; const jsRuntimeModulesAssetTypes: {