Skip to content

Commit

Permalink
[browser][MT] pre-load threads early in dotnet.js (#98637)
Browse files Browse the repository at this point in the history
Co-authored-by: Marek Fišera <[email protected]>
  • Loading branch information
pavelsavara and maraf authored Feb 19, 2024
1 parent eeadd65 commit ae4c05a
Show file tree
Hide file tree
Showing 42 changed files with 653 additions and 599 deletions.
6 changes: 4 additions & 2 deletions src/mono/browser/build/BrowserWasmApp.targets
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@

<PropertyGroup>
<!-- TODO: set this from some user-facing property? -1 means use the default baked into dotnet.native.js -->
<_WasmPThreadPoolSize Condition="'$(_WasmPThreadPoolSize)' == ''">-1</_WasmPThreadPoolSize>
<_WasmPThreadPoolInitialSize Condition="'$(_WasmPThreadPoolInitialSize)' == ''">-1</_WasmPThreadPoolInitialSize>
<_WasmPThreadPoolUnusedSize Condition="'$(_WasmPThreadPoolUnusedSize)' == ''">-1</_WasmPThreadPoolUnusedSize>
</PropertyGroup>

<ItemGroup>
Expand All @@ -148,7 +149,8 @@
NativeAssets="@(WasmNativeAsset)"
DebugLevel="$(WasmDebugLevel)"
IncludeThreadsWorker="$(WasmEnableThreads)"
PThreadPoolSize="$(_WasmPThreadPoolSize)"
PThreadPoolInitialSize="$(_WasmPThreadPoolInitialSize)"
PThreadPoolUnusedSize="$(_WasmPThreadPoolUnusedSize)"
UseWebcil="$(WasmEnableWebcil)"
WasmIncludeFullIcuData="$(WasmIncludeFullIcuData)"
WasmIcuDataFileName="$(WasmIcuDataFileName)"
Expand Down
3 changes: 2 additions & 1 deletion src/mono/browser/runtime/assets.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
3 changes: 1 addition & 2 deletions src/mono/browser/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
1 change: 1 addition & 0 deletions src/mono/browser/runtime/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
4 changes: 2 additions & 2 deletions src/mono/browser/runtime/diagnostics/browser/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 3 additions & 6 deletions src/mono/browser/runtime/diagnostics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,9 @@ let diagnosticsServerEnabled = false;
let diagnosticsInitialized = false;

export async function mono_wasm_init_diagnostics(): Promise<void> {
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;
Expand Down
2 changes: 1 addition & 1 deletion src/mono/browser/runtime/diagnostics/mock/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
10 changes: 9 additions & 1 deletion src/mono/browser/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions src/mono/browser/runtime/exports-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 ? [] : [
Expand Down
10 changes: 7 additions & 3 deletions src/mono/browser/runtime/exports-internal.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/mono/browser/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 3 additions & 1 deletion src/mono/browser/runtime/interp-pgo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ export async function getCacheKey(prefix: string): Promise<string | null> {
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;
Expand Down
27 changes: 25 additions & 2 deletions src/mono/browser/runtime/loader/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -20,6 +20,9 @@ let parallel_count = 0;
const assetsToLoad: AssetEntryInternal[] = [];
const singleAssets: Map<string, AssetEntryInternal> = new Map();

// A duplicate in pthreads/shared.ts
const worker_empty_prefix = " - ";

const jsRuntimeModulesAssetTypes: {
[k: string]: boolean
} = {
Expand Down Expand Up @@ -733,4 +736,24 @@ export async function streamingCompileWasm() {
catch (err) {
loaderHelpers.wasmCompilePromise.promise_control.reject(err);
}
}
}

export function preloadWorkers() {
if (!WasmEnableThreads) return;
const jsModuleWorker = resolve_single_asset_path("js-module-threads");
for (let i = 0; i < loaderHelpers.config.pthreadPoolInitialSize!; i++) {
const workerNumber = loaderHelpers.workerNextNumber++;
const worker: Partial<PThreadWorker> = 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);
}
}
12 changes: 9 additions & 3 deletions src/mono/browser/runtime/loader/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/mono/browser/runtime/loader/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/mono/browser/runtime/loader/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -487,6 +487,7 @@ async function createEmscriptenMain(): Promise<RuntimeAPI> {
setTimeout(async () => {
try {
init_globalization();
preloadWorkers();
await mono_download_assets();
}
catch (err) {
Expand Down
11 changes: 6 additions & 5 deletions src/mono/browser/runtime/loader/worker.ts
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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 });
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/mono/browser/runtime/managed-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
2 changes: 1 addition & 1 deletion src/mono/browser/runtime/marshal-to-cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading

0 comments on commit ae4c05a

Please sign in to comment.