diff --git a/src/mono/sample/wasm/browser-eventpipe/Makefile b/src/mono/sample/wasm/browser-eventpipe/Makefile index 6f4130b5b64b55..b029e47ddbd15d 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Makefile +++ b/src/mono/sample/wasm/browser-eventpipe/Makefile @@ -2,6 +2,8 @@ TOP=../../../../.. include ../wasm.mk +override MSBUILD_ARGS+=/p:WasmEnablePerfTracing=true + ifneq ($(AOT),) override MSBUILD_ARGS+=/p:RunAOTCompilation=true endif diff --git a/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj b/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj index eb58b28f8cfd2d..5f698b4d9a4b6c 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj +++ b/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj @@ -13,6 +13,10 @@ CA2007 + + + + true diff --git a/src/mono/sample/wasm/browser-threads/Makefile b/src/mono/sample/wasm/browser-threads/Makefile index 6b60d3bcc93b64..ca2c7d1c22fed4 100644 --- a/src/mono/sample/wasm/browser-threads/Makefile +++ b/src/mono/sample/wasm/browser-threads/Makefile @@ -2,6 +2,8 @@ TOP=../../../../.. include ../wasm.mk +override MSBUILD_ARGS+=/p:WasmEnableThreads=true + ifneq ($(AOT),) override MSBUILD_ARGS+=/p:RunAOTCompilation=true endif diff --git a/src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj b/src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj index e53241bccba160..06dd22e1141f49 100644 --- a/src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj +++ b/src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj @@ -16,6 +16,10 @@ + + + + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index d9a5f1f06ed70f..602896b55da0f0 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -316,6 +316,12 @@ DependsOnTargets="_WasmGenerateRuntimeConfig;_GetWasmGenerateAppBundleDependencies"> + + <_WasmAppIncludeThreadsWorker Condition="'$(WasmEnableThreads)' == 'true' or '$(WasmEnablePerfTracing)' == 'true'">true + + <_WasmPThreadPoolSize Condition="'$(_WasmPThreadPoolSize)' == '' and ('$(WasmEnableThreads)' == 'true' or '$(WasmEnablePerfTracing)' == 'true')">-1 + + + DebugLevel="$(WasmDebugLevel)" + IncludeThreadsWorker="$(_WasmAppIncludeThreadsWorker)" + PThreadPoolSize="$(_WasmPThreadPoolSize)" + > diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index eacddc7a017bbe..832513510e3a03 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -81,6 +81,7 @@ declare type MonoConfig = { coverageProfilerOptions?: CoverageProfilerOptions; ignorePdbLoadErrors?: boolean; waitForDebugger?: number; + pthreadPoolSize?: number; }; interface ResourceRequest { name: string; diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 6c5ed1b2ea1175..74aed3030e8a86 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -26,6 +26,7 @@ let __dotnet_replacement_PThread = ${usePThreads} ? {} : undefined; if (${usePThreads}) { __dotnet_replacement_PThread.loadWasmModuleToWorker = PThread.loadWasmModuleToWorker; __dotnet_replacement_PThread.threadInitTLS = PThread.threadInitTLS; + __dotnet_replacement_PThread.allocateUnusedWorker = PThread.allocateUnusedWorker; } let __dotnet_replacements = {scriptUrl: import.meta.url, fetch: globalThis.fetch, require, updateGlobalBufferAndViews, pthreadReplacements: __dotnet_replacement_PThread}; if (ENVIRONMENT_IS_NODE) { @@ -47,6 +48,7 @@ var noExitRuntime = __dotnet_replacements.noExitRuntime; if (${usePThreads}) { PThread.loadWasmModuleToWorker = __dotnet_replacements.pthreadReplacements.loadWasmModuleToWorker; PThread.threadInitTLS = __dotnet_replacements.pthreadReplacements.threadInitTLS; + PThread.allocateUnusedWorker = __dotnet_replacements.pthreadReplacements.allocateUnusedWorker; } `, }; diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts index afd92c71074896..13a912926c844b 100644 --- a/src/mono/wasm/runtime/polyfills.ts +++ b/src/mono/wasm/runtime/polyfills.ts @@ -2,8 +2,7 @@ import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, INTERNAL, Module, runtimeHelpers } from "./imports"; import { afterUpdateGlobalBufferAndViews } from "./memory"; -import { afterLoadWasmModuleToWorker } from "./pthreads/browser"; -import { afterThreadInitTLS } from "./pthreads/worker"; +import { replaceEmscriptenPThreadLibrary } from "./pthreads/shared/emscripten-replacements"; import { DotnetModuleConfigImports, EarlyReplacements } from "./types"; let node_fs: any | undefined = undefined; @@ -173,16 +172,7 @@ export function init_polyfills(replacements: EarlyReplacements): void { // threads if (MonoWasmThreads) { if (replacements.pthreadReplacements) { - const originalLoadWasmModuleToWorker = replacements.pthreadReplacements.loadWasmModuleToWorker; - replacements.pthreadReplacements.loadWasmModuleToWorker = (worker: Worker, onFinishedLoading: Function): void => { - originalLoadWasmModuleToWorker(worker, onFinishedLoading); - afterLoadWasmModuleToWorker(worker); - }; - const originalThreadInitTLS = replacements.pthreadReplacements.threadInitTLS; - replacements.pthreadReplacements.threadInitTLS = (): void => { - originalThreadInitTLS(); - afterThreadInitTLS(); - }; + replaceEmscriptenPThreadLibrary(replacements.pthreadReplacements); } } @@ -297,4 +287,4 @@ function isPathAbsolute(path: string): boolean { // windows file:///C:/x.json // windows http://C:/x.json return protocolRx.test(path); -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/pthreads/browser/index.ts b/src/mono/wasm/runtime/pthreads/browser/index.ts index f71ad533cfaea3..021bc6c70a666d 100644 --- a/src/mono/wasm/runtime/pthreads/browser/index.ts +++ b/src/mono/wasm/runtime/pthreads/browser/index.ts @@ -1,10 +1,13 @@ // 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 "../../imports"; -import { pthread_ptr, MonoWorkerMessageChannelCreated, isMonoWorkerMessageChannelCreated, monoSymbol } from "../shared"; +import { MonoWorkerMessageChannelCreated, isMonoWorkerMessageChannelCreated, monoSymbol, makeMonoThreadMessageApplyMonoConfig } from "../shared"; +import { pthread_ptr } from "../shared/types"; import { MonoThreadMessage } from "../shared"; import { PromiseController, createPromiseController } from "../../promise-controller"; +import { MonoConfig, mono_assert } from "../../types"; +import Internals from "../shared/emscripten-internals"; +import { runtimeHelpers } from "../../imports"; const threads: Map = new Map(); @@ -92,6 +95,7 @@ function monoWorkerMessageHandler(worker: Worker, ev: MessageEvent monoDedicatedChannelMessageFromWorkerToMain(ev, thread)); port.start(); + port.postMessage(makeMonoThreadMessageApplyMonoConfig(runtimeHelpers.config)); resolvePromises(pthread_id, thread); } } @@ -103,21 +107,40 @@ export function afterLoadWasmModuleToWorker(worker: Worker): void { console.debug("MONO_WASM: afterLoadWasmModuleToWorker added message event handler", worker); } -/// These utility functions dig into Emscripten internals -const Internals = { - getWorker: (pthread_ptr: pthread_ptr): Worker => { - // see https://github.com/emscripten-core/emscripten/pull/16239 - return (Module).PThread.pthreads[pthread_ptr].worker; - }, - getThreadId: (worker: Worker): pthread_ptr | undefined => { - /// See library_pthread.js in Emscripten. - /// They hang a "pthread" object from the worker if the worker is running a thread, and remove it when the thread stops by doing `pthread_exit` or when it's joined using `pthread_join`. - const emscriptenThreadInfo = (worker)["pthread"]; - if (emscriptenThreadInfo === undefined) { - return undefined; - } - return emscriptenThreadInfo.threadInfoStruct; +/// 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(defaultPthreadPoolSize: number, config: MonoConfig): void { + const poolSizeSpec = config?.pthreadPoolSize; + let n: number; + if (poolSizeSpec === undefined) { + n = defaultPthreadPoolSize; + } else { + mono_assert(typeof poolSizeSpec === "number", "pthreadPoolSize must be a number"); + if (poolSizeSpec < 0) + n = defaultPthreadPoolSize; + else + n = poolSizeSpec; } -}; - + for (let i = 0; i < n; i++) { + Internals.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 { + // this is largely copied from emscripten's "receiveInstance" in "createWasm" in "src/preamble.js" + const workers = Internals.getUnusedWorkerPool(); + const allLoaded = createPromiseController(); + let leftToLoad = workers.length; + workers.forEach((w) => { + Internals.loadWasmModuleToWorker(w, function () { + if (!--leftToLoad) allLoaded.promise_control.resolve(); + }); + }); + await allLoaded.promise; +} diff --git a/src/mono/wasm/runtime/pthreads/shared/emscripten-internals.ts b/src/mono/wasm/runtime/pthreads/shared/emscripten-internals.ts new file mode 100644 index 00000000000000..606907ec19b1fa --- /dev/null +++ b/src/mono/wasm/runtime/pthreads/shared/emscripten-internals.ts @@ -0,0 +1,76 @@ +// 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 "../../imports"; +import { pthread_ptr } from "./types"; + +/** @module emscripten-internals accessors to the functions in the emscripten PThreads library, including + * the low-level representations of {@linkcode pthread_ptr} 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 +interface PThreadLibrary { + unusedWorkers: Worker[]; + pthreads: PThreadInfoMap; + allocateUnusedWorker: () => void; + loadWasmModuleToWorker: (worker: Worker, onFinishedLoading?: (worker: Worker) => void) => void; +} + +interface EmscriptenPThreadInfo { + threadInfoStruct: pthread_ptr; +} + +/// N.B. emscripten deletes the `pthread` property from the worker when it is not actively running a pthread +interface PThreadWorker extends Worker { + pthread: EmscriptenPThreadInfo; +} + +interface PThreadObject { + worker: PThreadWorker; +} + +interface PThreadInfoMap { + [key: pthread_ptr]: PThreadObject | undefined; +} + + +function isRunningPThreadWorker(w: Worker): w is PThreadWorker { + return (w).pthread !== undefined; +} + +/// These utility functions dig into Emscripten internals +const Internals = { + get modulePThread(): PThreadLibrary { + return (Module).PThread as PThreadLibrary; + }, + getWorker: (pthread_ptr: pthread_ptr): PThreadWorker | undefined => { + // see https://github.com/emscripten-core/emscripten/pull/16239 + return Internals.modulePThread.pthreads[pthread_ptr]?.worker; + }, + getThreadId: (worker: Worker): pthread_ptr | undefined => { + /// See library_pthread.js in Emscripten. + /// They hang a "pthread" object from the worker if the worker is running a thread, and remove it when the thread stops by doing `pthread_exit` or when it's joined using `pthread_join`. + if (!isRunningPThreadWorker(worker)) + return undefined; + const emscriptenThreadInfo = worker.pthread; + return emscriptenThreadInfo.threadInfoStruct; + }, + 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. + Internals.modulePThread.allocateUnusedWorker(); + }, + getUnusedWorkerPool: (): Worker[] => { + return Internals.modulePThread.unusedWorkers; + }, + loadWasmModuleToWorker: (worker: Worker, onFinishedLoading: () => void): void => { + Internals.modulePThread.loadWasmModuleToWorker(worker, onFinishedLoading); + } +}; + + +export default Internals; diff --git a/src/mono/wasm/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/wasm/runtime/pthreads/shared/emscripten-replacements.ts new file mode 100644 index 00000000000000..53b9f0b0be373a --- /dev/null +++ b/src/mono/wasm/runtime/pthreads/shared/emscripten-replacements.ts @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import MonoWasmThreads from "consts:monoWasmThreads"; +import { PThreadReplacements } from "../../types"; +import { afterLoadWasmModuleToWorker } from "../browser"; +import { afterThreadInitTLS } from "../worker"; +import Internals from "./emscripten-internals"; +import { resolve_asset_path } from "../../assets"; +import { mono_assert } from "../../types"; +import { runtimeHelpers } from "../../imports"; + +/** @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(replacements: PThreadReplacements): void { + if (MonoWasmThreads) { + const originalLoadWasmModuleToWorker = replacements.loadWasmModuleToWorker; + replacements.loadWasmModuleToWorker = (worker: Worker, onFinishedLoading?: (worker: Worker) => void): void => { + originalLoadWasmModuleToWorker(worker, onFinishedLoading); + afterLoadWasmModuleToWorker(worker); + }; + const originalThreadInitTLS = replacements.threadInitTLS; + replacements.threadInitTLS = (): void => { + originalThreadInitTLS(); + afterThreadInitTLS(); + }; + // const originalAllocateUnusedWorker = replacements.allocateUnusedWorker; + replacements.allocateUnusedWorker = replacementAllocateUnusedWorker; + } +} + +/// We replace Module["PThreads"].allocateUnusedWorker with this version that knows about assets +function replacementAllocateUnusedWorker(): void { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: replacementAllocateUnusedWorker"); + const asset = resolve_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); + Internals.getUnusedWorkerPool().push(worker); +} diff --git a/src/mono/wasm/runtime/pthreads/shared/index.ts b/src/mono/wasm/runtime/pthreads/shared/index.ts index ef71b31022af30..b03207afb231a1 100644 --- a/src/mono/wasm/runtime/pthreads/shared/index.ts +++ b/src/mono/wasm/runtime/pthreads/shared/index.ts @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. import { Module } from "../../imports"; - -/// pthread_t in C -export type pthread_ptr = number; +import { MonoConfig } from "../../types"; +import { pthread_ptr } from "./types"; export interface PThreadInfo { readonly pthread_id: pthread_ptr; @@ -44,6 +43,29 @@ export function isMonoThreadMessage(x: unknown): x is MonoThreadMessage { return typeof (xmsg.type) === "string" && typeof (xmsg.cmd) === "string"; } +// message from the main thread to the pthread worker that passes the MonoConfig to the worker +export interface MonoThreadMessageApplyMonoConfig extends MonoThreadMessage { + type: "pthread"; + cmd: "apply_mono_config"; + config: string; +} + +export function isMonoThreadMessageApplyMonoConfig(x: unknown): x is MonoThreadMessageApplyMonoConfig { + if (!isMonoThreadMessage(x)) { + return false; + } + const xmsg = x as MonoThreadMessageApplyMonoConfig; + return xmsg.type === "pthread" && xmsg.cmd === "apply_mono_config" && typeof (xmsg.config) === "string"; +} + +export function makeMonoThreadMessageApplyMonoConfig(config: MonoConfig): MonoThreadMessageApplyMonoConfig { + return { + type: "pthread", + cmd: "apply_mono_config", + config: JSON.stringify(config) + }; +} + /// Messages sent using the worker object's postMessage() method /// /// a symbol that we use as a key on messages on the global worker-to-main channel to identify our own messages diff --git a/src/mono/wasm/runtime/pthreads/shared/tsconfig.json b/src/mono/wasm/runtime/pthreads/shared/tsconfig.json index 7b8ecd91fcc4f1..8986477dd8fc35 100644 --- a/src/mono/wasm/runtime/pthreads/shared/tsconfig.json +++ b/src/mono/wasm/runtime/pthreads/shared/tsconfig.json @@ -1,3 +1,8 @@ { - "extends": "../../tsconfig.shared.json" + "extends": "../../tsconfig.shared.json", + "include": [ + "../../**/*.ts", + "../../**/*.d.ts" + ] + } diff --git a/src/mono/wasm/runtime/pthreads/shared/types.ts b/src/mono/wasm/runtime/pthreads/shared/types.ts new file mode 100644 index 00000000000000..daa1113ee60aea --- /dev/null +++ b/src/mono/wasm/runtime/pthreads/shared/types.ts @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/// pthread_t in C +export type pthread_ptr = number; diff --git a/src/mono/wasm/runtime/pthreads/worker/events.ts b/src/mono/wasm/runtime/pthreads/worker/events.ts index 7022497d0a4fc4..a170345ef2b0d1 100644 --- a/src/mono/wasm/runtime/pthreads/worker/events.ts +++ b/src/mono/wasm/runtime/pthreads/worker/events.ts @@ -1,5 +1,6 @@ import MonoWasmThreads from "consts:monoWasmThreads"; -import type { pthread_ptr, PThreadInfo, MonoThreadMessage } from "../shared"; +import type { pthread_ptr } from "../shared/types"; +import type { PThreadInfo, MonoThreadMessage } from "../shared"; /// Identification of the current thread executing on a worker export interface PThreadSelf extends PThreadInfo { diff --git a/src/mono/wasm/runtime/pthreads/worker/index.ts b/src/mono/wasm/runtime/pthreads/worker/index.ts index 810a396db8f96d..69900f8da7fb87 100644 --- a/src/mono/wasm/runtime/pthreads/worker/index.ts +++ b/src/mono/wasm/runtime/pthreads/worker/index.ts @@ -5,8 +5,9 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import { Module, ENVIRONMENT_IS_PTHREAD } from "../../imports"; -import { makeChannelCreatedMonoMessage, pthread_ptr } from "../shared"; -import { mono_assert, is_nullish } from "../../types"; +import { isMonoThreadMessageApplyMonoConfig, makeChannelCreatedMonoMessage } from "../shared"; +import type { pthread_ptr } from "../shared/types"; +import { mono_assert, is_nullish, MonoConfig } from "../../types"; import type { MonoThreadMessage } from "../shared"; import { PThreadSelf, @@ -15,6 +16,7 @@ import { dotnetPthreadAttached, WorkerThreadEventTarget } from "./events"; +import { setup_proxy_console } from "../../logging"; // re-export some of the events types export { @@ -53,7 +55,16 @@ export let pthread_self: PThreadSelf = null as any as PThreadSelf; export const currentWorkerThreadEvents: WorkerThreadEventTarget = MonoWasmThreads ? new EventTarget() : null as any as WorkerThreadEventTarget; // treeshake if threads are disabled + +// this is the message handler for the worker that receives messages from the main thread +// extend this with new cases as needed function monoDedicatedChannelMessageFromMainToWorker(event: MessageEvent): void { + if (isMonoThreadMessageApplyMonoConfig(event.data)) { + const config = JSON.parse(event.data.config) as MonoConfig; + console.debug("MONO_WASM: applying mono config from main", event.data.config); + onMonoConfigReceived(config); + return; + } console.debug("MONO_WASM: got message from main on the dedicated channel", event.data); } @@ -69,6 +80,22 @@ function setupChannelToMainThread(pthread_ptr: pthread_ptr): PThreadSelf { return pthread_self; } +// TODO: should we just assign to Module.config here? +let workerMonoConfig: MonoConfig = null as unknown as MonoConfig; + +// called when the main thread sends us the mono config +function onMonoConfigReceived(config: MonoConfig): void { + if (workerMonoConfig !== null) { + console.debug("MONO_WASM: mono config already received"); + return; + } + workerMonoConfig = config; + console.debug("MONO_WASM: mono config received", config); + if (workerMonoConfig.diagnosticTracing) { + setup_proxy_console("pthread-worker", console, self.location.href); + } +} + /// This is an implementation detail function. /// Called in the worker thread from mono when a pthread becomes attached to the mono runtime. export function mono_wasm_pthread_on_pthread_attached(pthread_id: pthread_ptr): void { diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index ff9f53f74a41a7..230618eba37b47 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -28,6 +28,7 @@ import { instantiate_wasm_asset, mono_download_assets, resolve_asset_path, start import { BINDING, MONO } from "./net6-legacy/imports"; import { readSymbolMapFile } from "./logging"; import { mono_wasm_init_diagnostics } from "./diagnostics"; +import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from "./pthreads/browser"; let config: MonoConfig = undefined as any; let configLoaded = false; @@ -41,6 +42,9 @@ export const beforeOnRuntimeInitialized = createPromiseController(); export const afterOnRuntimeInitialized = createPromiseController(); export const afterPostRun = createPromiseController(); +// default size if MonoConfig.pthreadPoolSize is undefined +const MONO_PTHREAD_POOL_SIZE = 4; + // we are making emscripten startup async friendly // emscripten is executing the events without awaiting it and so we need to block progress via PromiseControllers above export function configure_emscripten_startup(module: DotnetModule, exportedAPI: DotnetPublicAPI): void { @@ -149,6 +153,9 @@ async function preRunAsync(userPreRun: (() => void)[]) { await afterInstantiateWasm.promise; await afterPreInit.promise; if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: preRunAsync"); + if (MonoWasmThreads) { + await instantiateWasmPThreadWorkerPool(); + } try { // all user Module.preRun callbacks userPreRun.map(fn => fn()); @@ -247,6 +254,10 @@ async function mono_wasm_pre_init_essential_async(): Promise { await mono_wasm_load_config(Module.configSrc); init_crypto(); + if (MonoWasmThreads) { + preAllocatePThreadWorkerPool(MONO_PTHREAD_POOL_SIZE, config); + } + Module.removeRunDependency("mono_wasm_pre_init_essential_async"); } diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index d9edc82f9587b6..d3fae6c690cc1a 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -83,7 +83,8 @@ export type MonoConfig = { aotProfilerOptions?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized. coverageProfilerOptions?: CoverageProfilerOptions, // dictionary-style Object. If omitted, coverage profiler will not be initialized. ignorePdbLoadErrors?: boolean, - waitForDebugger?: number + waitForDebugger?: number, + pthreadPoolSize?: number, // initial number of workers to add to the emscripten pthread pool }; export type MonoConfigError = { @@ -298,8 +299,9 @@ export interface ExitStatusError { new(status: number): any; } export type PThreadReplacements = { - loadWasmModuleToWorker: Function, - threadInitTLS: Function + loadWasmModuleToWorker: (worker: Worker, onFinishedLoading?: (worker: Worker) => void) => void, + threadInitTLS: () => void, + allocateUnusedWorker: () => void, } /// Always throws. Used to handle unreachable switch branches when TypeScript refines the type of a variable diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 5d7f1facca07a9..8112b86ba11348 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -125,7 +125,7 @@ <_EmccCommonFlags Condition="'$(WasmEnableSIMD)' == 'true'" Include="-msimd128" /> <_EmccCommonFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-s USE_PTHREADS=1" /> <_EmccLinkFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-Wno-pthreads-mem-growth" /> - <_EmccLinkFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-s PTHREAD_POOL_SIZE=4" /> + <_EmccLinkFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-s PTHREAD_POOL_SIZE=0" /> <_EmccLinkFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-s PTHREAD_POOL_SIZE_STRICT=2" /> <_EmccLinkFlags Include="-s ALLOW_MEMORY_GROWTH=1" /> <_EmccLinkFlags Include="-s NO_EXIT_RUNTIME=1" /> diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 65f1a9a5b0f1ad..1de5abc301a869 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -46,6 +46,8 @@ public class WasmAppBuilder : Task public bool InvariantGlobalization { get; set; } public ITaskItem[]? ExtraFilesToDeploy { get; set; } public string? MainHTMLPath { get; set; } + public bool IncludeThreadsWorker {get; set; } + public int PThreadPoolSize {get; set; } // // Extra json elements to add to mono-config.json @@ -314,6 +316,8 @@ private bool ExecuteInternal () config.Assets.Add(new VfsEntry ("dotnet.timezones.blat") { VirtualPath = "/usr/share/zoneinfo/"}); config.Assets.Add(new WasmEntry ("dotnet.wasm") ); config.Assets.Add(new CryptoWorkerEntry ("dotnet-crypto-worker.js") ); + if (IncludeThreadsWorker) + config.Assets.Add(new ThreadsWorkerEntry ("dotnet.worker.js") ); if (RemoteSources?.Length > 0) { @@ -322,6 +326,15 @@ private bool ExecuteInternal () config.RemoteSources.Add(source.ItemSpec); } + if (PThreadPoolSize < -1) + { + throw new LogAsErrorException($"PThreadPoolSize must be -1, 0 or positive, but got {PThreadPoolSize}"); + } + else + { + config.Extra["pthreadPoolSize"] = PThreadPoolSize; + } + foreach (ITaskItem extra in ExtraConfig ?? Enumerable.Empty()) { string name = extra.ItemSpec;