Skip to content

Commit

Permalink
[browser] mono_exit improvements (#88387)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Jul 15, 2023
1 parent c93336a commit 7980421
Show file tree
Hide file tree
Showing 64 changed files with 615 additions and 241 deletions.
1 change: 1 addition & 0 deletions src/libraries/tests.proj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<ProjectExclusions Include="$(MonoProjectRoot)sample\wasm\browser-webpack\Wasm.Browser.WebPack.Sample.csproj" />
<ProjectExclusions Include="$(MonoProjectRoot)sample\wasm\node-webpack\Wasm.Node.WebPack.Sample.csproj" />
<ProjectExclusions Include="$(MonoProjectRoot)sample\wasm\browser-nextjs\Wasm.Browser.NextJs.Sample.csproj" />
<ProjectExclusions Include="$(MonoProjectRoot)sample\wasm\browser-shutdown\Wasm.Browser.Shutdown.Sample.csproj" />

<!-- These tests are completely disabled on wasm -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/System.Text.RegularExpressions.Generators.Tests.csproj" />
Expand Down
11 changes: 11 additions & 0 deletions src/mono/sample/wasm/browser-shutdown/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
TOP=../../../../..

include ../wasm.mk

ifneq ($(AOT),)
override MSBUILD_ARGS+=/p:RunAOTCompilation=true
endif

PROJECT_NAME=Wasm.Browser.Shutdown.Sample.csproj

run: run-browser
51 changes: 51 additions & 0 deletions src/mono/sample/wasm/browser-shutdown/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.InteropServices;

namespace Sample
{
public partial class Test
{
public static int Main(string[] args)
{
return 0;
}

[JSExport()]
public static void DoNothing ()
{
Console.WriteLine("You got it, boss! Doing nothing!");
}

[JSExport()]
public static void ThrowManagedException ()
{
throw new Exception("I'll make an exception to the rules just this once... and throw one.");
}

[JSExport()]
public static void CallFailFast ()
{
System.Environment.FailFast("User requested FailFast");
}

[JSImport("timerTick", "main.js")]
public static partial void TimerTick (int i);

[JSExport()]
public static void StartTimer ()
{
int i = 0;
var timer = new System.Timers.Timer(1000);
timer.Elapsed += (s, e) => {
TimerTick(i);
i += 1;
};
timer.AutoReset = true;
timer.Enabled = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\DefaultBrowserSample.targets" />
<ItemGroup>
<WasmExtraFilesToDeploy Include="main.js" />
</ItemGroup>
</Project>
24 changes: 24 additions & 0 deletions src/mono/sample/wasm/browser-shutdown/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>

<head>
<title>Wasm Browser Shutdown Sample</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type='module' src="./main.js"></script>
</head>

<body>
<h3 id="header">Wasm Browser Shutdown Sample</h3>
<button id="throw-managed-exc">Throw Managed Exception</button>
<button id="trigger-native-assert">Trigger Native Assert</button>
<button id="trigger-failfast">Trigger Environment.FailFast</button>
<button id="call-jsexport">Call Harmless JSExport</button>
<button id="call-exit">Call exit</button>
<button id="start-timer">Start Timer</button><br>
Timer Value: <span id="timer-value"></span>
</body>

</html>
87 changes: 87 additions & 0 deletions src/mono/sample/wasm/browser-shutdown/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import { dotnet, exit } from './_framework/dotnet.js'

let exports = undefined,
setenv = undefined;

window.addEventListener("load", onLoad);

try {
const { setModuleImports, getAssemblyExports, setEnvironmentVariable, getConfig } = await dotnet
.withModuleConfig()
.withExitOnUnhandledError()
.withExitCodeLogging()
.withElementOnExit()
.withAssertAfterExit()
.withOnConfigLoaded(() => {
// you can test abort of the startup by opening http://localhost:8000/?throwError=true
const params = new URLSearchParams(location.search);
if (params.get("throwError") === "true") {
throw new Error("Error thrown from OnConfigLoaded");
}
})
.create();

setModuleImports("main.js", {
timerTick: (i) => {
document.querySelector("#timer-value").textContent = i;
},
});

setenv = setEnvironmentVariable;
const config = getConfig();
exports = await getAssemblyExports(config.mainAssemblyName);
}
catch (err) {
exit(2, err);
}

function onLoad() {
document.querySelector("#throw-managed-exc").addEventListener("click", () => {
try {
exports.Sample.Test.ThrowManagedException();
alert("No JS exception was thrown!");
} catch (exc) {
alert(exc);
}
});
document.querySelector("#trigger-failfast").addEventListener("click", () => {
try {
exports.Sample.Test.CallFailFast();
alert("No JS exception was thrown!");
} catch (exc) {
alert(exc);
}
});
document.querySelector("#start-timer").addEventListener("click", () => {
try {
exports.Sample.Test.StartTimer();
} catch (exc) {
alert(exc);
}
});
document.querySelector("#trigger-native-assert").addEventListener("click", () => {
try {
setenv(null, null);
alert("No JS exception was thrown!");
} catch (exc) {
alert(exc);
}
});
document.querySelector("#call-jsexport").addEventListener("click", () => {
try {
exports.Sample.Test.DoNothing();
} catch (exc) {
alert(exc);
}
});
document.querySelector("#call-exit").addEventListener("click", () => {
try {
exit(7, "User clicked exit");
} catch (exc) {
alert(exc);
}
});
}
2 changes: 1 addition & 1 deletion src/mono/sample/wasm/browser-threads-minimal/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public static async Task<string> FetchBackground(string url)
Console.WriteLine($"smoke: FetchBackground 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
var x = JSHost.ImportAsync(fetchhelper, "../fetchhelper.js");
Console.WriteLine($"smoke: FetchBackground 3A ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
// using var import = await x.ConfigureAwait(false);
using var import = await x;
Console.WriteLine($"smoke: FetchBackground 3B ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
var r = await GlobalThisFetch(url);
Console.WriteLine($"smoke: FetchBackground 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import cwraps from "./cwraps";
import { mono_wasm_load_icu_data } from "./icu";
import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, Module, loaderHelpers, runtimeHelpers } from "./globals";
import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
import { mono_log_info, mono_log_debug, mono_log_warn, parseSymbolMapFile } from "./logging";
import { mono_wasm_load_bytes_into_heap } from "./memory";
import { endMeasure, MeasuredBlock, startMeasure } from "./profiler";
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/cancelable-promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

import { _lookup_js_owned_object } from "./gc-handles";
import { createPromiseController, loaderHelpers } from "./globals";
import { createPromiseController, loaderHelpers, mono_assert } from "./globals";
import { TaskCallbackHolder } from "./marshal-to-cs";
import { ControllablePromise, GCHandle } from "./types/internal";

Expand Down
5 changes: 4 additions & 1 deletion src/mono/wasm/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
import type { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten";
import { linkerDisableLegacyJsInterop, linkerEnableAotProfiler, linkerEnableBrowserProfiler, Module } from "./globals";
import { mono_log_error } from "./logging";
import { mono_assert } from "./globals";

type SigLine = [lazyOrSkip: boolean | (() => boolean), name: string, returnType: string | null, argTypes?: string[], opts?: any];

Expand Down Expand Up @@ -76,6 +77,7 @@ const fn_signatures: SigLine[] = [

//INTERNAL
[false, "mono_wasm_exit", "void", ["number"]],
[false, "mono_wasm_abort", "void", []],
[true, "mono_wasm_getenv", "number", ["string"]],
[true, "mono_wasm_set_main_args", "void", ["number", "number"]],
[false, "mono_wasm_enable_on_demand_gc", "void", ["number"]],
Expand Down Expand Up @@ -205,7 +207,8 @@ export interface t_Cwraps {
mono_wasm_intern_string_ref(strRef: MonoStringRef): void;

//INTERNAL
mono_wasm_exit(exit_code: number): number;
mono_wasm_exit(exit_code: number): void;
mono_wasm_abort(): void;
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;
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/diagnostics/browser/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import MonoWasmThreads from "consts:monoWasmThreads";

import { diagnostics_c_functions as cwraps } from "../../cwraps";
import { INTERNAL } from "../../globals";
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";
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/diagnostics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { VoidPtr } from "../types/emscripten";
import { getController, startDiagnosticServer } from "./browser/controller";
import * as memory from "../memory";
import { mono_log_warn } from "../logging";
import { runtimeHelpers } from "../globals";
import { mono_assert, runtimeHelpers } from "../globals";


// called from C on the main thread
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/diagnostics/mock/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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 { createPromiseController } from "../../globals";
import { createPromiseController, mono_assert } from "../../globals";


function expectAdvertise(data: ArrayBuffer): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/diagnostics/server_pthread/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import monoDiagnosticsMock from "consts:monoDiagnosticsMock";

import { PromiseAndController, assertNever } from "../../types/internal";
import { pthread_self } from "../../pthreads/worker";
import { createPromiseController } from "../../globals";
import { createPromiseController, mono_assert } from "../../globals";
import { diagnostics_c_functions as cwraps } from "../../cwraps";
import { EventPipeSessionIDImpl } from "../shared/types";
import { CharPtr } from "../../types/emscripten";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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 } from "../../../globals";
import Serializer from "./base-serializer";
import { CommandSetId, ServerCommandId } from "./types";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
EventPipeCollectTracingCommandProvider,
} from "./protocol-client-commands";
import { createEventPipeStreamingSession } from "../shared/create-session";
import { mono_assert } from "../../globals";

/// The streaming session holds all the pieces of an event pipe streaming session that the
/// diagnostic server knows about: the session ID, a
Expand Down
3 changes: 3 additions & 0 deletions src/mono/wasm/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ declare interface EmscriptenModule {
onAbort?: {
(error: any): void;
};
onExit?: {
(code: number): void;
};
}
type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module | undefined) => void;
type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any;
Expand Down
8 changes: 8 additions & 0 deletions src/mono/wasm/runtime/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ mono_wasm_add_satellite_assembly (const char *name, const char *culture, const u
EMSCRIPTEN_KEEPALIVE void
mono_wasm_setenv (const char *name, const char *value)
{
assert (name);
assert (value);
monoeg_g_setenv (strdup (name), strdup (value), 1);
}

Expand Down Expand Up @@ -1187,6 +1189,12 @@ mono_wasm_exit (int exit_code)
exit (exit_code);
}

EMSCRIPTEN_KEEPALIVE int
mono_wasm_abort ()
{
abort ();
}

EMSCRIPTEN_KEEPALIVE void
mono_wasm_set_main_args (int argc, char* argv[])
{
Expand Down
8 changes: 6 additions & 2 deletions src/mono/wasm/runtime/gc-handles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import { runtimeHelpers } from "./globals";
import { loaderHelpers, runtimeHelpers } from "./globals";
import { mono_log_warn } from "./logging";
import { GCHandle, GCHandleNull, JSHandle, JSHandleDisposed, JSHandleNull } from "./types/internal";
import { create_weak_ref } from "./weak-ref";
Expand Down Expand Up @@ -109,11 +109,15 @@ export function teardown_managed_proxy(result: any, gc_handle: GCHandle): void {

export function assert_not_disposed(result: any): GCHandle {
const gc_handle = result[js_owned_gc_handle_symbol];
mono_assert(gc_handle != GCHandleNull, "ObjectDisposedException");
mono_check(gc_handle != GCHandleNull, "ObjectDisposedException");
return gc_handle;
}

function _js_owned_object_finalized(gc_handle: GCHandle): void {
if (loaderHelpers.is_exited()) {
// We're shutting down, so don't bother doing anything else.
return;
}
teardown_managed_proxy(null, gc_handle);
}

Expand Down
17 changes: 17 additions & 0 deletions src/mono/wasm/runtime/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/// <reference path="./types/v8.d.ts" />
/// <reference path="./types/node.d.ts" />

import { mono_log_error } from "./logging";
import { RuntimeAPI } from "./types/index";
import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController } from "./types/internal";

Expand Down Expand Up @@ -72,4 +73,20 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) {

export function createPromiseController<T>(afterResolve?: () => void, afterReject?: () => void): PromiseAndController<T> {
return loaderHelpers.createPromiseController<T>(afterResolve, afterReject);
}

// this will abort the program if the condition is false
// see src\mono\wasm\runtime\rollup.config.js
// we inline the condition, because the lambda could allocate closure on hot path otherwise
export function mono_assert(condition: unknown, messageFactory: string | (() => string)): asserts condition {
if (condition) return;
const message = "Assert failed: " + (typeof messageFactory === "function"
? messageFactory()
: messageFactory);
const abort = runtimeHelpers.mono_wasm_abort;
if (abort) {
mono_log_error(message);
abort();
}
throw new Error(message);
}
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

import { wrap_as_cancelable_promise } from "./cancelable-promise";
import { ENVIRONMENT_IS_NODE, Module, loaderHelpers } from "./globals";
import { ENVIRONMENT_IS_NODE, Module, loaderHelpers, mono_assert } from "./globals";
import { MemoryViewType, Span } from "./marshal";
import type { VoidPtr } from "./types/emscripten";

Expand Down
Loading

0 comments on commit 7980421

Please sign in to comment.