diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets
index ec2a81e1e066b..6174769ef02ea 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/assets.ts b/src/mono/browser/runtime/assets.ts
index bd88949b6234c..40dc39349ff56 100644
--- a/src/mono/browser/runtime/assets.ts
+++ b/src/mono/browser/runtime/assets.ts
@@ -1,13 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+import type { AssetEntryInternal } from "./types/internal";
+
import cwraps from "./cwraps";
import { mono_wasm_load_icu_data } from "./icu";
import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
import { mono_log_info, mono_log_debug, parseSymbolMapFile } from "./logging";
import { mono_wasm_load_bytes_into_heap } from "./memory";
import { endMeasure, MeasuredBlock, startMeasure } from "./profiler";
-import { AssetEntryInternal } from "./types/internal";
import { AssetEntry } from "./types";
import { VoidPtr } from "./types/emscripten";
import { setSegmentationRulesFromJson } from "./hybrid-globalization/grapheme-segmenter";
diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts
index 7dc7c207697ec..dbb0babce8231 100644
--- a/src/mono/browser/runtime/cwraps.ts
+++ b/src/mono/browser/runtime/cwraps.ts
@@ -6,13 +6,12 @@ import WasmEnableThreads from "consts:wasmEnableThreads";
import type {
MonoAssembly, MonoClass,
MonoMethod, MonoObject,
- MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments
+ MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments, PThreadPtr
} from "./types/internal";
import type { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten";
import { Module, runtimeHelpers } from "./globals";
import { mono_log_error } from "./logging";
import { mono_assert } from "./globals";
-import { PThreadPtr } from "./pthreads/shared/types";
type SigLine = [lazyOrSkip: boolean | (() => boolean), name: string, returnType: string | null, argTypes?: string[], opts?: any];
diff --git a/src/mono/browser/runtime/debug.ts b/src/mono/browser/runtime/debug.ts
index 1c919426bc3a8..74c0128f2e4e3 100644
--- a/src/mono/browser/runtime/debug.ts
+++ b/src/mono/browser/runtime/debug.ts
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
import BuildConfiguration from "consts:configuration";
+
import { INTERNAL, Module, loaderHelpers, runtimeHelpers } from "./globals";
import { toBase64StringImpl } from "./base64";
import cwraps from "./cwraps";
diff --git a/src/mono/browser/runtime/diagnostics/browser/controller.ts b/src/mono/browser/runtime/diagnostics/browser/controller.ts
index 7bcdf62b86850..799972b1fd0e6 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";
import { isDiagnosticMessage, makeDiagnosticServerControlCommand } from "../shared/controller-commands";
import monoDiagnosticsMock from "consts:monoDiagnosticsMock";
-import { PThreadPtr } from "../../pthreads/shared/types";
+import { PThreadPtr, Thread } from "../../types/internal";
/// An object that can be used to control the diagnostic server.
export interface ServerController {
diff --git a/src/mono/browser/runtime/diagnostics/index.ts b/src/mono/browser/runtime/diagnostics/index.ts
index 474fd8a824fd5..a9d6f9414267a 100644
--- a/src/mono/browser/runtime/diagnostics/index.ts
+++ b/src/mono/browser/runtime/diagnostics/index.ts
@@ -37,12 +37,9 @@ let diagnosticsServerEnabled = false;
let diagnosticsInitialized = false;
export async function mono_wasm_init_diagnostics(): Promise {
- if (diagnosticsInitialized)
- return;
- if (!WasmEnableThreads) {
- mono_log_warn("ignoring diagnostics options because this runtime does not support diagnostics");
- return;
- }
+ if (!WasmEnableThreads) return;
+ if (diagnosticsInitialized) return;
+
const options = diagnostic_options_from_environment();
if (!options)
return;
diff --git a/src/mono/browser/runtime/diagnostics/mock/environment.ts b/src/mono/browser/runtime/diagnostics/mock/environment.ts
index bcbdf390a4dfe..0de8c8b7acafc 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 1fbca276f3c89..cba9d5fba7d8d 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 5e08f56c627ea..16aa6ad85944f 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 { isMonoThreadMessage } from "../../pthreads";
+import type { MonoThreadMessage } from "../../types/internal";
// Messages from the main thread to the diagnostic server thread
export interface DiagnosticMessage extends MonoThreadMessage {
diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts
index e60265f04a281..7b901972e8b8f 100644
--- a/src/mono/browser/runtime/dotnet.d.ts
+++ b/src/mono/browser/runtime/dotnet.d.ts
@@ -189,7 +189,15 @@ type MonoConfig = {
/**
* initial number of workers to add to the emscripten pthread pool
*/
- pthreadPoolSize?: number;
+ pthreadPoolInitialSize?: number;
+ /**
+ * number of unused workers kept in the emscripten pthread pool after startup
+ */
+ 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 c410dca9e7588..6ab2b430ab048 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 5fe5773f97577..80c52669fb3ad 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";
+
export function export_internal(): any {
return {
// tests
mono_wasm_exit: (exit_code: number) => { Module.err("early exit " + exit_code); },
forceDisposeProxies,
+ dumpThreads: WasmEnableThreads ? dumpThreads : undefined,
// with mono_wasm_debugger_log and mono_wasm_trace_logger
logging: undefined,
@@ -57,7 +61,7 @@ export function export_internal(): any {
get_global_this,
get_dotnet_instance: () => exportedRuntimeAPI,
dynamic_import,
- thread_available,
+ thread_available: WasmEnableThreads ? thread_available : undefined,
mono_wasm_bind_cs_function,
// BrowserWebSocket
diff --git a/src/mono/browser/runtime/exports.ts b/src/mono/browser/runtime/exports.ts
index 2f3aa96a0ec6b..8b2984f393b1f 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;
diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts
index dfe894f569826..79ea1e29ab5df 100644
--- a/src/mono/browser/runtime/interp-pgo.ts
+++ b/src/mono/browser/runtime/interp-pgo.ts
@@ -197,7 +197,9 @@ export async function getCacheKey(prefix: string): Promise {
delete inputs.interopCleanupOnExit;
delete inputs.dumpThreadsOnNonZeroExit;
delete inputs.logExitCode;
- delete inputs.pthreadPoolSize;
+ 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 624244a63ff9c..49d2f0f8ac6d8 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";
@@ -20,6 +20,9 @@ let parallel_count = 0;
const assetsToLoad: AssetEntryInternal[] = [];
const singleAssets: Map = new Map();
+// A duplicate in pthreads/shared.ts
+const worker_empty_prefix = " - ";
+
const jsRuntimeModulesAssetTypes: {
[k: string]: boolean
} = {
@@ -733,4 +736,24 @@ export async function streamingCompileWasm() {
catch (err) {
loaderHelpers.wasmCompilePromise.promise_control.reject(err);
}
-}
\ No newline at end of file
+}
+
+export function preloadWorkers() {
+ if (!WasmEnableThreads) return;
+ const jsModuleWorker = resolve_single_asset_path("js-module-threads");
+ 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"),
+ });
+ worker.info = {
+ workerNumber,
+ pthreadId: PThreadPtrNull,
+ reuseCount: 0,
+ updateCount: 0,
+ threadPrefix: worker_empty_prefix,
+ threadName: "emscripten-pool",
+ } as any;
+ loaderHelpers.loadingWorkers.push(worker as any);
+ }
+}
diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts
index 6f64d327fe7ad..8aec355743721 100644
--- a/src/mono/browser/runtime/loader/config.ts
+++ b/src/mono/browser/runtime/loader/config.ts
@@ -187,9 +187,15 @@ export function normalizeConfig() {
config.cachedResourcesPurgeDelay = 10000;
}
- if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolSize)) {
- // ActiveIssue https://github.com/dotnet/runtime/issues/75602
- config.pthreadPoolSize = 7;
+ // ActiveIssue https://github.com/dotnet/runtime/issues/75602
+ if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolInitialSize)) {
+ config.pthreadPoolInitialSize = 7;
+ }
+ 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/loader/globals.ts b/src/mono/browser/runtime/loader/globals.ts
index c9d8ffd8e8c9d..7e9708845e65b 100644
--- a/src/mono/browser/runtime/loader/globals.ts
+++ b/src/mono/browser/runtime/loader/globals.ts
@@ -92,6 +92,8 @@ export function setLoaderGlobals(
loadedFiles: [],
loadedAssemblies: [],
libraryInitializers: [],
+ loadingWorkers: [],
+ workerNextNumber: 1,
actual_downloaded_assets_count: 0,
actual_instantiated_assets_count: 0,
expected_downloaded_assets_count: 0,
diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts
index 1f85b4e241200..730a692b4ebf5 100644
--- a/src/mono/browser/runtime/loader/run.ts
+++ b/src/mono/browser/runtime/loader/run.ts
@@ -10,7 +10,7 @@ import { ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, emscriptenModule, exportedRu
import { deep_merge_config, deep_merge_module, mono_wasm_load_config } from "./config";
import { installUnhandledErrorHandler, mono_exit, registerEmscriptenExitHandlers } from "./exit";
import { setup_proxy_console, mono_log_info, mono_log_debug } from "./logging";
-import { mono_download_assets, prepareAssets, prepareAssetsWorker, resolve_single_asset_path, streamingCompileWasm } from "./assets";
+import { mono_download_assets, preloadWorkers, prepareAssets, prepareAssetsWorker, resolve_single_asset_path, streamingCompileWasm } from "./assets";
import { detect_features_and_polyfill } from "./polyfills";
import { runtimeHelpers, loaderHelpers } from "./globals";
import { init_globalization } from "./icu";
@@ -487,6 +487,7 @@ async function createEmscriptenMain(): Promise {
setTimeout(async () => {
try {
init_globalization();
+ preloadWorkers();
await mono_download_assets();
}
catch (err) {
diff --git a/src/mono/browser/runtime/loader/worker.ts b/src/mono/browser/runtime/loader/worker.ts
index 81a9cecad6a74..baeeaf0165f67 100644
--- a/src/mono/browser/runtime/loader/worker.ts
+++ b/src/mono/browser/runtime/loader/worker.ts
@@ -1,10 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { MonoConfigInternal, WorkerToMainMessageType, monoMessageSymbol } from "../types/internal";
+import { MonoConfigInternal, PThreadInfo, WorkerToMainMessageType, monoMessageSymbol } from "../types/internal";
import { MonoConfig } from "../types";
import { deep_merge_config, normalizeConfig } from "./config";
-import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals";
+import { ENVIRONMENT_IS_WEB, loaderHelpers, runtimeHelpers } from "./globals";
import { mono_log_debug } from "./logging";
export function setupPreloadChannelToMainThread() {
@@ -13,7 +13,8 @@ export function setupPreloadChannelToMainThread() {
const mainPort = channel.port2;
workerPort.addEventListener("message", (event) => {
const config = JSON.parse(event.data.config) as MonoConfig;
- onMonoConfigReceived(config);
+ const monoThreadInfo = JSON.parse(event.data.monoThreadInfo) as PThreadInfo;
+ onMonoConfigReceived(config, monoThreadInfo);
workerPort.close();
mainPort.close();
}, { once: true });
@@ -30,13 +31,13 @@ export function setupPreloadChannelToMainThread() {
let workerMonoConfigReceived = false;
// called when the main thread sends us the mono config
-function onMonoConfigReceived(config: MonoConfigInternal): void {
+function onMonoConfigReceived(config: MonoConfigInternal, monoThreadInfo: PThreadInfo): void {
if (workerMonoConfigReceived) {
mono_log_debug("mono config already received");
return;
}
-
deep_merge_config(loaderHelpers.config, config);
+ runtimeHelpers.monoThreadInfo = monoThreadInfo;
normalizeConfig();
mono_log_debug("mono config received");
workerMonoConfigReceived = true;
diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts
index 87da9ac8d259e..c8aabae88e37b 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 fa0f299a5f2b8..a42eb724a9215 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 7aae92566b34a..0f8700f84754e 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 { replaceEmscriptenPThreadWorker } from "./pthreads";
+import { replaceEmscriptenPThreadUI } from "./pthreads";
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/README.md b/src/mono/browser/runtime/pthreads/README.md
index 34f3508988cc9..757cc73a85e42 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/index.ts b/src/mono/browser/runtime/pthreads/browser/index.ts
deleted file mode 100644
index 84c58b694d13a..0000000000000
--- a/src/mono/browser/runtime/pthreads/browser/index.ts
+++ /dev/null
@@ -1,219 +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 { 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 { monoThreadInfo } from "../worker";
-import { mono_wasm_init_diagnostics } from "../../diagnostics";
-
-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 {
- this.port.postMessage(message);
- }
-}
-
-/// 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;
- const worker = getWorker(pthreadPtr);
- if (worker?.thread) {
- return Promise.resolve(worker?.thread);
- }
- const promiseAndController = createPromiseController();
- const arr = threadPromises.get(pthreadPtr);
- if (arr === undefined) {
- threadPromises.set(pthreadPtr, [promiseAndController.promise_control]);
- } else {
- arr.push(promiseAndController.promise_control);
- }
- return promiseAndController.promise;
-}
-
-export function resolveThreadPromises(pthreadPtr: PThreadPtr, thread?: Thread): void {
- if (!WasmEnableThreads) return;
- const arr = threadPromises.get(pthreadPtr);
- if (arr !== undefined) {
- arr.forEach((controller) => {
- if (thread) {
- controller.resolve(thread);
- } else {
- controller.reject();
- }
- });
- threadPromises.delete(pthreadPtr);
- }
-}
-
-// handler that runs in the main thread when a message is received from a pthread worker
-function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): void {
- if (!WasmEnableThreads) return;
- let pthreadId: PThreadPtr;
- // this is emscripten message
- if (ev.data.cmd === "killThread") {
- pthreadId = ev.data["thread"];
- mono_assert(pthreadId == worker.info.pthreadId, "expected pthreadId to match");
- worker.info.isRunning = false;
- worker.info.pthreadId = PThreadPtrNull;
- return;
- }
-
- const message = ev.data[monoMessageSymbol] as MonoWorkerToMainMessage;
- if (message === undefined) {
- /// N.B. important to ignore messages we don't recognize - Emscripten uses the message event to send internal messages
- return;
- }
-
- let port: MessagePort;
- let thread: Thread;
- pthreadId = message.info?.pthreadId ?? 0;
-
- worker.info = Object.assign(worker.info, message.info, {});
- switch (message.monoCmd) {
- case WorkerToMainMessageType.preload:
- // this one shot port from setupPreloadChannelToMainThread
- port = message.port!;
- port.postMessage({
- type: "pthread",
- cmd: MainToWorkerMessageType.applyConfig,
- config: JSON.stringify(runtimeHelpers.config)
- });
- port.close();
- break;
- case WorkerToMainMessageType.pthreadCreated:
- port = message.port!;
- thread = new ThreadImpl(pthreadId, worker, port);
- worker.thread = thread;
- worker.info.isRunning = true;
- resolveThreadPromises(pthreadId, thread);
- break;
- case WorkerToMainMessageType.monoRegistered:
- case WorkerToMainMessageType.monoAttached:
- case WorkerToMainMessageType.enabledInterop:
- case WorkerToMainMessageType.monoUnRegistered:
- case WorkerToMainMessageType.updateInfo:
- // just worker.info updates above
- break;
- default:
- throw new Error(`Unhandled message from worker: ${message.monoCmd}`);
- }
-}
-
-let pendingWorkerLoad: PromiseAndController | undefined;
-
-/// Called by Emscripten internals on the browser thread when a new pthread worker is created and added to the pthread worker pool.
-/// At this point the worker doesn't have any pthread assigned to it, yet.
-export function onWorkerLoadInitiated(worker: PThreadWorker, loaded: Promise): void {
- if (!WasmEnableThreads) return;
- worker.addEventListener("message", (ev) => monoWorkerMessageHandler(worker, ev));
- if (pendingWorkerLoad == undefined) {
- pendingWorkerLoad = createPromiseController();
- }
- loaded.then(() => {
- worker.info.isLoaded = true;
- if (pendingWorkerLoad != undefined) {
- pendingWorkerLoad.promise_control.resolve();
- pendingWorkerLoad = undefined;
- }
- });
-}
-
-export function thread_available(): Promise {
- if (!WasmEnableThreads) return null as any;
- if (pendingWorkerLoad == undefined) {
- return Promise.resolve();
- }
- return pendingWorkerLoad.promise;
-}
-
-export async function mono_wasm_init_threads() {
- if (!WasmEnableThreads) return;
- 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"
- const workers = getUnusedWorkerPool();
- if (workers.length > 0) {
- const promises = workers.map(loadWasmModuleToWorker);
- await Promise.all(promises);
- }
-}
-
-// 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() {
- const workers: PThreadWorker[] = getRunningWorkers();
- for (const worker of workers) {
- if (worker.info.isExternalEventLoop) {
- worker.postMessage({ cmd: "cancel" });
- }
- }
-}
-
-export function dumpThreads(): void {
- if (!WasmEnableThreads) return;
- mono_log_info("Dumping web worker info as seen by UI thread, it could be stale: ");
- const emptyInfo = {
- pthreadId: 0,
- threadPrefix: " - ",
- threadName: "????",
- isRunning: false,
- isAttached: false,
- isExternalEventLoop: false,
- reuseCount: 0,
- };
- const threadInfos: PThreadInfo[] = [
- Object.assign({}, emptyInfo, monoThreadInfo), // UI thread
- ];
- for (const worker of getRunningWorkers()) {
- threadInfos.push(Object.assign({}, emptyInfo, worker.info));
- }
- for (const worker of getUnusedWorkerPool()) {
- threadInfos.push(Object.assign({}, emptyInfo, worker.info));
- }
- threadInfos.forEach((info, i) => {
- const idx = (i + "").padStart(2, "0");
- const isRunning = (info.isRunning + "").padStart(5, " ");
- const isAttached = (info.isAttached + "").padStart(5, " ");
- const isEventLoop = (info.isExternalEventLoop + "").padStart(5, " ");
- const reuseCount = (info.reuseCount + "").padStart(3, " ");
- // eslint-disable-next-line no-console
- console.info(`${idx} | ${info.threadPrefix}: isRunning:${isRunning} isAttached:${isAttached} isEventLoop:${isEventLoop} reuseCount:${reuseCount} - ${info.threadName}`);
- });
-}
diff --git a/src/mono/browser/runtime/pthreads/index.ts b/src/mono/browser/runtime/pthreads/index.ts
new file mode 100644
index 0000000000000..a7b5da11e03fb
--- /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 75%
rename from src/mono/browser/runtime/pthreads/shared/index.ts
rename to src/mono/browser/runtime/pthreads/shared.ts
index aadab84fa1206..97c41197d35e0 100644
--- a/src/mono/browser/runtime/pthreads/shared/index.ts
+++ b/src/mono/browser/runtime/pthreads/shared.ts
@@ -4,23 +4,25 @@
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, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal";
-import { MonoWorkerToMainMessage, PThreadPtr, PThreadPtrNull } 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;
-}
+import type { GCHandle, MonoThreadMessage, PThreadInfo, PThreadPtr } from "../types/internal";
+
+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";
+
+// A duplicate in loader/assets.ts
+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) {
@@ -65,6 +67,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"
@@ -110,4 +113,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/emscripten-internals.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts
deleted file mode 100644
index 0529744570efd..0000000000000
--- a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-import { Module } from "../../globals";
-import { Thread } from "../browser";
-import { PThreadInfo, PThreadPtr } from "./types";
-
-/** @module emscripten-internals accessors to the functions in the emscripten PThreads library, including
- * the low-level representations of {@linkcode PThreadPtr} thread info structs, etc.
- * Additionally, note that some of these functions are replaced by {@linkcode file://./emscripten-replacements.ts}.
- * These have a hard dependency on the version of Emscripten that we are using and may need to be kept in sync with
- * {@linkcode file://./../../../emsdk/upstream/emscripten/src/library_pthread.js}
- */
-
-// This is what we know about the Emscripten PThread library
-export interface PThreadLibrary {
- unusedWorkers: PThreadWorker[];
- runningWorkers: PThreadWorker[];
- pthreads: PThreadInfoMap;
- allocateUnusedWorker: () => void;
- loadWasmModuleToWorker: (worker: PThreadWorker) => Promise;
- threadInitTLS: () => void,
- getNewWorker: () => PThreadWorker,
- returnWorkerToPool: (worker: PThreadWorker) => void,
-}
-
-
-/// N.B. emscripten deletes the `pthread` property from the worker when it is not actively running a pthread
-export interface PThreadWorker extends Worker {
- pthread_ptr: PThreadPtr;
- loaded: boolean;
- // this info is updated via async messages from the worker, it could be stale
- info: PThreadInfo;
- thread?: Thread;
-}
-
-interface PThreadInfoMap {
- [key: number]: PThreadWorker;
-}
-
-
-export function getWorker(pthreadPtr: PThreadPtr): PThreadWorker | undefined {
- return getModulePThread().pthreads[pthreadPtr as any];
-}
-
-export function allocateUnusedWorker(): void {
- /// See library_pthread.js in Emscripten.
- /// This function allocates a new worker and adds it to the pool of workers.
- /// It's called when the pool of workers is empty and a new thread is created.
- getModulePThread().allocateUnusedWorker();
-}
-export function getUnusedWorkerPool(): PThreadWorker[] {
- return getModulePThread().unusedWorkers;
-}
-export function getRunningWorkers(): PThreadWorker[] {
- return getModulePThread().runningWorkers;
-}
-
-export function loadWasmModuleToWorker(worker: PThreadWorker): Promise {
- return getModulePThread().loadWasmModuleToWorker(worker);
-}
-
-export function getModulePThread(): PThreadLibrary {
- return (Module).PThread as PThreadLibrary;
-}
diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts
deleted file mode 100644
index 0acb8b6615c69..0000000000000
--- a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts
+++ /dev/null
@@ -1,137 +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 "../browser";
-import { mono_wasm_pthread_on_pthread_created, onRunMessage } from "../worker";
-import { PThreadLibrary, PThreadWorker, getModulePThread, getUnusedWorkerPool } from "./emscripten-internals";
-import { Module, loaderHelpers, mono_assert } from "../../globals";
-import { mono_log_warn } from "../../logging";
-import { PThreadPtr, PThreadPtrNull } from "./types";
-
-/** @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 {
- 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);
- afterLoaded.then(() => {
- availableThreadCount++;
- });
- onWorkerLoadInitiated(worker, afterLoaded);
- if (loaderHelpers.config.exitOnUnhandledError) {
- worker.onerror = (e) => {
- loaderHelpers.mono_exit(1, e);
- };
- }
- return afterLoaded;
- };
- modulePThread.threadInitTLS = (): void => {
- originalThreadInitTLS();
- mono_wasm_pthread_on_pthread_created();
- };
- 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 pthreadPoolSize. 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 (loaderHelpers.config.pthreadPoolSize && modulePThread.unusedWorkers.length <= loaderHelpers.config.pthreadPoolSize) {
- 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 pthreadPoolSize. 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 worker = new Worker(uri) as PThreadWorker;
- getUnusedWorkerPool().push(worker);
- worker.loaded = false;
- worker.info = {
- pthreadId: PThreadPtrNull,
- reuseCount: 0,
- updateCount: 0,
- threadPrefix: " - ",
- threadName: "emscripten-pool",
- };
- return worker;
-}
-
-
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 8986477dd8fc3..0000000000000
--- 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 1ea8e0c65e579..0000000000000
--- a/src/mono/browser/runtime/pthreads/shared/types.ts
+++ /dev/null
@@ -1,44 +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 { 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,
-}
-
-/// 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/ui-thread.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts
new file mode 100644
index 0000000000000..878ee38eadbb9
--- /dev/null
+++ b/src/mono/browser/runtime/pthreads/ui-thread.ts
@@ -0,0 +1,351 @@
+// 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 { } 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();
+
+class ThreadImpl implements Thread {
+ constructor(readonly pthreadPtr: PThreadPtr, readonly worker: Worker, readonly port: MessagePort) { }
+ postMessageToWorker(message: T): void {
+ this.port.postMessage(message);
+ }
+}
+
+/// 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);
+ }
+ const promiseAndController = createPromiseController();
+ const arr = threadPromises.get(pthreadPtr);
+ if (arr === undefined) {
+ threadPromises.set(pthreadPtr, [promiseAndController.promise_control]);
+ } else {
+ arr.push(promiseAndController.promise_control);
+ }
+ return promiseAndController.promise;
+}
+
+export function resolveThreadPromises(pthreadPtr: PThreadPtr, thread?: Thread): void {
+ if (!WasmEnableThreads) return;
+ const arr = threadPromises.get(pthreadPtr);
+ if (arr !== undefined) {
+ arr.forEach((controller) => {
+ if (thread) {
+ controller.resolve(thread);
+ } else {
+ controller.reject();
+ }
+ });
+ threadPromises.delete(pthreadPtr);
+ }
+}
+
+// handler that runs in the main thread when a message is received from a pthread worker
+function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): void {
+ if (!WasmEnableThreads) return;
+ let pthreadId: PThreadPtr;
+ // this is emscripten message
+ if (ev.data.cmd === "killThread") {
+ pthreadId = ev.data["thread"];
+ mono_assert(pthreadId == worker.info.pthreadId, "expected pthreadId to match");
+ worker.info.isRunning = false;
+ worker.info.pthreadId = PThreadPtrNull;
+ return;
+ }
+
+ const message = ev.data[monoMessageSymbol] as MonoWorkerToMainMessage;
+ if (message === undefined) {
+ /// N.B. important to ignore messages we don't recognize - Emscripten uses the message event to send internal messages
+ return;
+ }
+
+ let port: MessagePort;
+ let thread: Thread;
+ pthreadId = message.info?.pthreadId ?? 0;
+ worker.info = Object.assign({}, worker.info, message.info);
+ switch (message.monoCmd) {
+ case WorkerToMainMessageType.preload:
+ // this one shot port from setupPreloadChannelToMainThread
+ port = message.port!;
+ port.postMessage({
+ type: "pthread",
+ cmd: MainToWorkerMessageType.applyConfig,
+ config: JSON.stringify(runtimeHelpers.config),
+ monoThreadInfo: JSON.stringify(worker.info),
+ });
+ port.close();
+ break;
+ case WorkerToMainMessageType.pthreadCreated:
+ port = message.port!;
+ thread = new ThreadImpl(pthreadId, worker, port);
+ worker.thread = thread;
+ worker.info.isRunning = true;
+ resolveThreadPromises(pthreadId, thread);
+ break;
+ case WorkerToMainMessageType.monoRegistered:
+ case WorkerToMainMessageType.monoAttached:
+ case WorkerToMainMessageType.enabledInterop:
+ case WorkerToMainMessageType.monoUnRegistered:
+ case WorkerToMainMessageType.updateInfo:
+ // just worker.info updates above
+ break;
+ default:
+ throw new Error(`Unhandled message from worker: ${message.monoCmd}`);
+ }
+}
+
+let pendingWorkerLoad: PromiseAndController | undefined;
+
+/// Called by Emscripten internals on the browser thread when a new pthread worker is created and added to the pthread worker pool.
+/// At this point the worker doesn't have any pthread assigned to it, yet.
+export function onWorkerLoadInitiated(worker: PThreadWorker, loaded: Promise): void {
+ if (!WasmEnableThreads) return;
+ worker.addEventListener("message", (ev) => monoWorkerMessageHandler(worker, ev));
+ if (pendingWorkerLoad == undefined) {
+ pendingWorkerLoad = createPromiseController();
+ }
+ loaded.then(() => {
+ worker.info.isLoaded = true;
+ if (pendingWorkerLoad != undefined) {
+ pendingWorkerLoad.promise_control.resolve();
+ pendingWorkerLoad = undefined;
+ }
+ });
+}
+
+export function thread_available(): Promise {
+ if (!WasmEnableThreads) return null as any;
+ if (pendingWorkerLoad == undefined) {
+ return Promise.resolve();
+ }
+ return pendingWorkerLoad.promise;
+}
+
+export function populateEmscriptenPool(): void {
+ if (!WasmEnableThreads) return;
+ const unused = getUnusedWorkerPool();
+ for (const worker of loaderHelpers.loadingWorkers) {
+ unused.push(worker);
+ }
+ loaderHelpers.loadingWorkers = [];
+}
+
+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();
+
+ // 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);
+ await Promise.all(promises);
+ }
+}
+
+// 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) {
+ worker.postMessage({ cmd: "cancel" });
+ }
+ }
+}
+
+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: PThreadInfo = {
+ workerNumber: -1,
+ pthreadId: PThreadPtrNull,
+ threadPrefix: worker_empty_prefix,
+ threadName: "????",
+ isRunning: false,
+ isAttached: false,
+ isExternalEventLoop: false,
+ reuseCount: 0,
+ updateCount: 0,
+ };
+ const threadInfos: PThreadInfo[] = [
+ Object.assign({}, emptyInfo, monoThreadInfo), // UI thread
+ ];
+ for (const worker of getRunningWorkers()) {
+ threadInfos.push(Object.assign({}, emptyInfo, worker.info));
+ }
+ for (const worker of getUnusedWorkerPool()) {
+ threadInfos.push(Object.assign({}, emptyInfo, worker.info));
+ }
+ threadInfos.forEach((info) => {
+ 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, " ");
+ const reuseCount = (info.reuseCount + "").padStart(3, " ");
+ // eslint-disable-next-line no-console
+ console.info(`${idx} | ${info.threadPrefix}: isRunning:${isRunning} isAttached:${isAttached} isEventLoop:${isEventLoop} reuseCount:${reuseCount} - ${info.threadName}`);
+ });
+}
+
+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);
+ }
+ }, 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 602c3fb221fbd..7b8a42b05ac66 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 ace256459d439..709e1980d82f5 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 29229c23e5fd8..083f87d7de025 100644
--- a/src/mono/browser/runtime/pthreads/worker/index.ts
+++ b/src/mono/browser/runtime/pthreads/worker-thread.ts
@@ -5,22 +5,22 @@
import WasmEnableThreads from "consts:wasmEnableThreads";
-import { ENVIRONMENT_IS_PTHREAD, loaderHelpers, mono_assert } 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 { 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 {
@@ -29,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) {
@@ -55,17 +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;
-export const monoThreadInfo: PThreadInfo = {
- pthreadId: PThreadPtrNull,
- reuseCount: 0,
- updateCount: 0,
- threadPrefix: " - ",
- threadName: "emscripten-loaded",
-};
-
/// This is the "public internal" API for runtime subsystems that wish to be notified about
/// pthreads that are running on the current worker.
/// Example:
@@ -78,6 +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;
+ 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/tsconfig.json b/src/mono/browser/runtime/pthreads/worker/tsconfig.json
deleted file mode 100644
index 071a4d824c62a..0000000000000
--- 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 8760aab6514ca..e35c6dc833aec 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 4b01536b735ee..c9cbf205d42d4 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/shared/emscripten-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 127d65dea4e81..9759e879254b0 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,14 +23,15 @@ 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 { 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";
import { runtimeList } from "./exports";
import { nativeAbort, nativeExit } from "./run";
+import { mono_wasm_init_diagnostics } from "./diagnostics";
export async function configureRuntimeStartup(): Promise {
await init_polyfills_async();
@@ -268,7 +269,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) {
Module.runtimeKeepalivePush();
// load runtime and apply environment settings (if necessary)
- start_runtime();
+ await start_runtime();
if (runtimeHelpers.config.interpreterPgo) {
await interp_pgo_load_data();
@@ -326,10 +327,6 @@ async function postRunAsync(userpostRun: (() => void)[]) {
Module["FS_createPath"]("/", "usr", true, true);
Module["FS_createPath"]("/", "usr/share", true, true);
- if (WasmEnableThreads) {
- tcwraps.mono_wasm_init_finalizer_thread();
- }
-
// all user Module.postRun callbacks
userpostRun.map(fn => fn());
endMeasure(mark, MeasuredBlock.postRun);
@@ -396,7 +393,7 @@ async function mono_wasm_pre_init_essential_async(): Promise {
Module.addRunDependency("mono_wasm_pre_init_essential_async");
if (WasmEnableThreads) {
- preAllocatePThreadWorkerPool(runtimeHelpers.config.pthreadPoolSize!);
+ populateEmscriptenPool();
}
Module.removeRunDependency("mono_wasm_pre_init_essential_async");
@@ -483,7 +480,7 @@ async function ensureUsedWasmFeatures() {
}
}
-export function start_runtime() {
+export async function start_runtime() {
try {
const mark = startMeasure();
mono_log_debug("Initializing mono runtime");
@@ -503,6 +500,11 @@ export function start_runtime() {
if (runtimeHelpers.config.browserProfilerOptions)
mono_wasm_init_browser_profiler(runtimeHelpers.config.browserProfilerOptions);
+ if (WasmEnableThreads) {
+ // this is not mono-attached thread, so we can start it earlier
+ await mono_wasm_init_diagnostics();
+ }
+
mono_wasm_load_runtime();
jiterpreter_allocate_tables();
@@ -514,10 +516,14 @@ export function start_runtime() {
if (WasmEnableThreads) {
monoThreadInfo.isAttached = true;
monoThreadInfo.isRegistered = true;
+ monoThreadInfo.pthreadId = runtimeHelpers.managedThreadTID = mono_wasm_pthread_ptr();
+ monoThreadInfo.workerNumber = 0;
update_thread_info();
runtimeHelpers.proxyGCHandle = install_main_synchronization_context();
- runtimeHelpers.managedThreadTID = mono_wasm_pthread_ptr();
runtimeHelpers.isCurrentThread = true;
+
+ // start finalizer thread, lazy
+ init_finalizer_thread();
}
// get GCHandle of the ctx
diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts
index e0f76ea3a546e..8d9c8a28ba148 100644
--- a/src/mono/browser/runtime/types/index.ts
+++ b/src/mono/browser/runtime/types/index.ts
@@ -143,7 +143,15 @@ export type MonoConfig = {
/**
* initial number of workers to add to the emscripten pthread pool
*/
- pthreadPoolSize?: number,
+ pthreadPoolInitialSize?: number,
+ /**
+ * number of unused workers kept in the emscripten pthread pool after startup
+ */
+ 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/types/internal.ts b/src/mono/browser/runtime/types/internal.ts
index 3f021a50a01ce..ae4ce1ff4813a 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/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs
index 97bd05110bfbf..22edc2c68fbe1 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 48ce5d34d04bb..7847039163b11 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())