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;