From 895861dfa77ec61171cbbadc21042ecf4d3d535e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 28 Jul 2022 16:23:34 -0400 Subject: [PATCH 1/4] [wasm-ep] Use DOTNET_DiagnosticPorts to configure Diagnostic Server Parse it the same way that the C code does: ``` [,][,] ``` - uri should be a websocket uri - listen is not supported as it doesn't make sense with a WebSocket - connect is the default if omitted - suspend is the default if omitted --- Additionally, move `mono_wasm_diagnostics_init` to later in the startup flow. This gives Blazor a chance to set DOTNET_DiagnosticPorts from their `onRuntimeInitialized` callback. Fixes https://github.com/dotnet/runtime/issues/73011 --- src/mono/wasm/runtime/cwraps.ts | 4 +- src/mono/wasm/runtime/diagnostics/index.ts | 82 +++++++++++++++++++++- src/mono/wasm/runtime/driver.c | 6 ++ src/mono/wasm/runtime/memory.ts | 16 ++++- src/mono/wasm/runtime/startup.ts | 16 +++-- 5 files changed, 112 insertions(+), 12 deletions(-) diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index ccc2e72d23f677..d65947d498cdb5 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -81,6 +81,7 @@ const fn_signatures: SigLine[] = [ //INTERNAL [false, "mono_wasm_exit", "void", ["number"]], + [true, "mono_wasm_getenv", "number", ["string"]], [true, "mono_wasm_set_main_args", "void", ["number", "number"]], [false, "mono_wasm_enable_on_demand_gc", "void", ["number"]], [false, "mono_profiler_init_aot", "void", ["number"]], @@ -189,6 +190,7 @@ export interface t_Cwraps { //INTERNAL mono_wasm_exit(exit_code: number): number; + mono_wasm_getenv(name: string): CharPtr; mono_wasm_enable_on_demand_gc(enable: number): void; mono_wasm_set_main_args(argc: number, argv: VoidPtr): void; mono_profiler_init_aot(desc: string): void; @@ -232,4 +234,4 @@ export function init_c_exports(): void { wf[name] = fce; } } -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/diagnostics/index.ts b/src/mono/wasm/runtime/diagnostics/index.ts index 203000489311ae..b8ba3a1b776564 100644 --- a/src/mono/wasm/runtime/diagnostics/index.ts +++ b/src/mono/wasm/runtime/diagnostics/index.ts @@ -94,12 +94,20 @@ export const diagnostics: Diagnostics = getDiagnostics(); let suspendOnStartup = false; let diagnosticsServerEnabled = false; -export async function mono_wasm_init_diagnostics(options: DiagnosticOptions): Promise { +export async function mono_wasm_init_diagnostics(opts: "env" | DiagnosticOptions): Promise { if (!monoWasmThreads) { - console.warn("MONO_WASM: ignoring diagnostics options because this runtime does not support diagnostics", options); + console.warn("MONO_WASM: ignoring diagnostics options because this runtime does not support diagnostics", opts); return; } else { - if (!is_nullish(options.server)) { + let options: DiagnosticOptions | null; + if (opts === "env") { + options = diagnostic_options_from_environment(); + if (!options) + return; + } else { + options = opts; + } + if (!is_nullish(options?.server)) { if (options.server.connectUrl === undefined || typeof (options.server.connectUrl) !== "string") { throw new Error("server.connectUrl must be a string"); } @@ -130,6 +138,74 @@ function boolsyOption(x: string | boolean): boolean { throw new Error(`invalid option: "${x}", should be true, false, or "true" or "false"`); } +/// Parse environment variables for diagnostics configuration +/// +/// The environment variables are: +/// * DOTNET_DiagnosticPorts +/// +function diagnostic_options_from_environment(): DiagnosticOptions | null { + const val = memory.getEnv("DOTNET_DiagnosticPorts"); + if (is_nullish(val)) + return null; + // TODO: consider also parsing the DOTNET_EnableEventPipe and DOTNET_EventPipeOutputPath, DOTNET_EvnetPipeConfig variables + // to configure the startup sessions that will dump output to the VFS. + return diagnostic_options_from_ports_spec(val); +} + +/// Parse a DOTNET_DiagnosticPorts string and return a DiagnosticOptions object. +/// See https://docs.microsoft.com/en-us/dotnet/core/diagnostics/diagnostic-port#configure-additional-diagnostic-ports +function diagnostic_options_from_ports_spec(val: string): DiagnosticOptions | null { + if (val === "") + return null; + const ports = val.split(";"); + if (ports.length === 0) + return null; + if (ports.length !== 1) { + console.warn("MONO_WASM: multiple diagnostic ports specified, only the last one will be used"); + } + const portSpec = ports[ports.length - 1]; + const components = portSpec.split(","); + if (components.length < 1 || components.length > 3) { + console.warn("MONO_WASM: invalid diagnostic port specification, should be of the form [,],[]"); + return null; + } + const uri: string = components[0]; + let connect = true; + let suspend = true; + // the C Diagnostic Server goes through these parts in reverse, do the same here. + for (let i = components.length - 1; i >= 1; i--) { + const component = components[i]; + switch (component.toLowerCase()) { + case "nosuspend": + suspend = false; + break; + case "suspend": + suspend = true; + break; + case "listen": + connect = false; + break; + case "connect": + connect = true; + break; + default: + console.warn(`MONO_WASM: invalid diagnostic port specification component: ${component}`); + break; + } + } + if (!connect) { + console.warn("MONO_WASM: this runtime does not support listening on a diagnostic port; no diagnostic server started"); + return null; + } + return { + server: { + connectUrl: uri, + suspend: suspend, + } + }; + +} + export function mono_wasm_diagnostic_server_on_runtime_server_init(out_options: VoidPtr): void { if (diagnosticsServerEnabled) { /* called on the main thread when the runtime is sufficiently initialized */ diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index a4108177548f07..0ddc467ae5966f 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -281,6 +281,12 @@ mono_wasm_setenv (const char *name, const char *value) monoeg_g_setenv (strdup (name), strdup (value), 1); } +EMSCRIPTEN_KEEPALIVE char * +mono_wasm_getenv (const char *name) +{ + return monoeg_g_getenv (name); // JS must free +} + static void *sysglobal_native_handle; static void* diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 95373c22fb7bf6..3c3ab1ae6fe93a 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -1,7 +1,7 @@ import monoWasmThreads from "consts:monoWasmThreads"; import { Module, runtimeHelpers } from "./imports"; import { mono_assert, MemOffset, NumberOrPointer } from "./types"; -import { VoidPtr } from "./types/emscripten"; +import { VoidPtr, CharPtr, NativePointer, ManagedPointer } from "./types/emscripten"; import * as cuint64 from "./cuint64"; import cwraps, { I52Error } from "./cwraps"; @@ -263,6 +263,18 @@ export function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr { return memoryOffset; } +export function getEnv(name: string): string | null { + let charPtr: CharPtr = 0; + try { + charPtr = cwraps.mono_wasm_getenv(name); + if (charPtr === 0) + return null; + else return Module.UTF8ToString(charPtr); + } finally { + if (charPtr) Module._free(charPtr); + } +} + const BuiltinAtomics = globalThis.Atomics; export const Atomics = monoWasmThreads ? { @@ -276,4 +288,4 @@ export const Atomics = monoWasmThreads ? { } : { storeI32: setI32, notifyI32: () => { /*empty*/ } -}; \ No newline at end of file +}; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index b6e6f688030218..5cfe8b970d6670 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -1,7 +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 { mono_assert, CharPtrNull, DotnetModule, MonoConfig, MonoConfigError, LoadingResource, AssetEntry, ResourceRequest } from "./types"; +import MonoWasmThreads from "consts:monoWasmThreads"; +import { mono_assert, CharPtrNull, DotnetModule, MonoConfig, MonoConfigError, LoadingResource, AssetEntry, ResourceRequest, DiagnosticOptions } from "./types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, Module, runtimeHelpers } from "./imports"; import cwraps, { init_c_exports } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; @@ -25,6 +26,7 @@ import { cwraps_internal } from "./exports-internal"; import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legacy"; import { DotnetPublicAPI } from "./export-types"; import { BINDING, MONO } from "./net6-legacy/imports"; +import { mono_wasm_init_diagnostics } from "./diagnostics"; let all_assets_loaded_in_memory: Promise | null = null; const loaded_files: { url: string, file: string }[] = []; @@ -130,8 +132,8 @@ function preInit(isCustomStartup: boolean, userPreInit: (() => void)[]) { abort_startup(err, true); throw err; } - // this will start immediately but return on first await. - // It will block our `preRun` by afterPreInit promise + // this will start immediately but return on first await. + // It will block our `preRun` by afterPreInit promise // It will block emscripten `userOnRuntimeInitialized` by pending addRunDependency("mono_pre_init") (async () => { try { @@ -196,7 +198,7 @@ async function onRuntimeInitializedAsync(isCustomStartup: boolean, userOnRuntime _print_error("MONO_WASM: user callback onRuntimeInitialized() failed", err); throw err; } - // finish + // finish await mono_wasm_after_user_runtime_initialized(); } catch (err) { _print_error("MONO_WASM: onRuntimeInitializedAsync() failed", err); @@ -329,6 +331,10 @@ async function mono_wasm_after_user_runtime_initialized(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: Initializing mono runtime"); + if (MonoWasmThreads) { + await mono_wasm_init_diagnostics("env"); + } + if (Module.onDotnetReady) { try { await Module.onDotnetReady(); @@ -532,8 +538,6 @@ async function _apply_configuration_from_args() { if (config.coverageProfilerOptions) mono_wasm_init_coverage_profiler(config.coverageProfilerOptions); - - // FIXME await mono_wasm_init_diagnostics(config.diagnosticOptions); } export function mono_wasm_load_runtime(unused?: string, debugLevel?: number): void { From 772da3f92b464cae82ff63505a171d614257131d Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 4 Aug 2022 10:06:36 -0400 Subject: [PATCH 2/4] Initialize diagnostic server in different places for Blazor and non-Blazor It has to be after environment variables are set, but before mono_wasm_load_runtime is called. There is no good place that's common to both startup paths. Try it on both. Use a flag to make diagnostics initialization run at most once --- src/mono/wasm/runtime/diagnostics/index.ts | 5 +++++ src/mono/wasm/runtime/startup.ts | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/diagnostics/index.ts b/src/mono/wasm/runtime/diagnostics/index.ts index b8ba3a1b776564..68f79ed9f0163e 100644 --- a/src/mono/wasm/runtime/diagnostics/index.ts +++ b/src/mono/wasm/runtime/diagnostics/index.ts @@ -94,7 +94,11 @@ export const diagnostics: Diagnostics = getDiagnostics(); let suspendOnStartup = false; let diagnosticsServerEnabled = false; +let diagnosticsInitialized = false; + export async function mono_wasm_init_diagnostics(opts: "env" | DiagnosticOptions): Promise { + if (diagnosticsInitialized) + return; if (!monoWasmThreads) { console.warn("MONO_WASM: ignoring diagnostics options because this runtime does not support diagnostics", opts); return; @@ -107,6 +111,7 @@ export async function mono_wasm_init_diagnostics(opts: "env" | DiagnosticOptions } else { options = opts; } + diagnosticsInitialized = true; if (!is_nullish(options?.server)) { if (options.server.connectUrl === undefined || typeof (options.server.connectUrl) !== "string") { throw new Error("server.connectUrl must be a string"); diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 5cfe8b970d6670..23905525097359 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -328,13 +328,13 @@ async function mono_wasm_after_user_runtime_initialized(): Promise { } } } - - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: Initializing mono runtime"); - + // for Blazor, init diagnostics after their "onRuntimeInitalized" sets env variables, but before their postRun callback (which calls mono_wasm_load_runtime) if (MonoWasmThreads) { await mono_wasm_init_diagnostics("env"); } + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: Initializing mono runtime"); + if (Module.onDotnetReady) { try { await Module.onDotnetReady(); @@ -538,6 +538,10 @@ async function _apply_configuration_from_args() { if (config.coverageProfilerOptions) mono_wasm_init_coverage_profiler(config.coverageProfilerOptions); + // for non-Blazor, init diagnostics after environment variables are set + if (MonoWasmThreads) { + await mono_wasm_init_diagnostics("env"); + } } export function mono_wasm_load_runtime(unused?: string, debugLevel?: number): void { From 7d97577ff65f5e5380737c1ade0ea59a63bef6a4 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 4 Aug 2022 10:58:27 -0400 Subject: [PATCH 3/4] update browser-eventpipe sample to use DOTNET_DiagnosticPorts --- .../Wasm.Browser.EventPipe.Sample.csproj | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj b/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj index f3feef21cce348..eb58b28f8cfd2d 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj +++ b/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj @@ -20,25 +20,19 @@ - - - - - From 4f5ab158d9e123805e3d8252f99880af618f665a Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 4 Aug 2022 12:22:58 -0400 Subject: [PATCH 4/4] remove unused imports --- src/mono/wasm/runtime/memory.ts | 2 +- src/mono/wasm/runtime/startup.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 3c3ab1ae6fe93a..2ac62de35f726d 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -1,7 +1,7 @@ import monoWasmThreads from "consts:monoWasmThreads"; import { Module, runtimeHelpers } from "./imports"; import { mono_assert, MemOffset, NumberOrPointer } from "./types"; -import { VoidPtr, CharPtr, NativePointer, ManagedPointer } from "./types/emscripten"; +import { VoidPtr, CharPtr } from "./types/emscripten"; import * as cuint64 from "./cuint64"; import cwraps, { I52Error } from "./cwraps"; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 23905525097359..051c0a796c5e87 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import MonoWasmThreads from "consts:monoWasmThreads"; -import { mono_assert, CharPtrNull, DotnetModule, MonoConfig, MonoConfigError, LoadingResource, AssetEntry, ResourceRequest, DiagnosticOptions } from "./types"; +import { mono_assert, CharPtrNull, DotnetModule, MonoConfig, MonoConfigError, LoadingResource, AssetEntry, ResourceRequest } from "./types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, Module, runtimeHelpers } from "./imports"; import cwraps, { init_c_exports } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug";