From 7b829f9d3b35abf037c72a83b242cdb222453c2f Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 Jan 2024 09:27:50 +0100 Subject: [PATCH 01/24] - `cancelThreads` will exit all JSWebWorker which are still running (idle in the browser event loop) preventing to be joined during mono_exit - new `-untilFailed` xunit parameter for WasmTestRunner - cleanup CancellationToken in WebWorkerTest --- .../tests/WasmTestRunner/WasmTestRunner.cs | 21 +++- ...me.InteropServices.JavaScript.Tests.csproj | 1 - .../JavaScript/WebWorkerTest.cs | 105 +++++++++++------- .../browser/runtime/pthreads/browser/index.ts | 9 ++ .../pthreads/shared/emscripten-internals.ts | 10 +- .../shared/emscripten-replacements.ts | 4 +- src/mono/browser/runtime/startup.ts | 17 ++- 7 files changed, 115 insertions(+), 52 deletions(-) diff --git a/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs b/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs index 9fb129d825d742..f89d3a3adf55a3 100644 --- a/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs +++ b/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs @@ -24,6 +24,7 @@ public static async Task Main(string[] args) var includedClasses = new List(); var includedMethods = new List(); var backgroundExec = false; + var untilFailed = false; for (int i = 1; i < args.Length; i++) { @@ -53,6 +54,9 @@ public static async Task Main(string[] args) case "-backgroundExec": backgroundExec = true; break; + case "-untilFailed": + untilFailed = true; + break; default: throw new ArgumentException($"Invalid argument '{option}'."); } @@ -72,10 +76,21 @@ public static async Task Main(string[] args) { await Task.Yield(); } - if (backgroundExec) + + var res = 0; + do { - return await Task.Run(() => runner.Run()); + if (backgroundExec) + { + res = await Task.Run(() => runner.Run()); + } + else + { + res = await runner.Run(); + } } - return await runner.Run(); + while(res == 0 && untilFailed); + + return res; } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj index 90b2454d6e375f..3c805f267237b6 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj @@ -18,7 +18,6 @@ false - $(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs index c0c03addd6bb87..cf3d97207ba9e1 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs @@ -11,6 +11,7 @@ using System.Net.WebSockets; using System.Text; using System.Linq; +using System.Runtime.CompilerServices; namespace System.Runtime.InteropServices.JavaScript.Tests { @@ -48,12 +49,14 @@ public async Task InitializeAsync() #region Executors - private CancellationTokenSource CreateTestCaseTimeoutSource() + private CancellationTokenSource CreateTestCaseTimeoutSource([CallerMemberName] string memberName = "") { + var start = DateTime.Now; var cts = new CancellationTokenSource(TimeoutMilliseconds); cts.Token.Register(() => { - Console.WriteLine($"Unexpected test case timeout at {DateTime.Now.ToString("u")} ManagedThreadId:{Environment.CurrentManagedThreadId}"); + var end = DateTime.Now; + Console.WriteLine($"Unexpected test case {memberName} timeout after {end - start} ManagedThreadId:{Environment.CurrentManagedThreadId}"); }); return cts; } @@ -247,11 +250,11 @@ public async Task JSSynchronizationContext_Send_Post_To_Canceled() Assert.False(shouldNotHitPost); } +#if !DEBUG [Fact] + // this will say something like `JSSynchronizationContext is still installed on worker 0x4ff0030.` in the console during shutdown. public async Task JSWebWorker_Abandon_Running() { - var cts = new CancellationTokenSource(); - TaskCompletionSource never = new TaskCompletionSource(); TaskCompletionSource ready = new TaskCompletionSource(); @@ -261,7 +264,7 @@ public async Task JSWebWorker_Abandon_Running() { ready.SetResult(); return never.Task; - }, cts.Token); + }, CancellationToken.None); #pragma warning restore CS4014 await ready.Task; @@ -273,10 +276,9 @@ public async Task JSWebWorker_Abandon_Running() } [Fact] + // this will say something like `JSSynchronizationContext is still installed on worker 0x4ff0030.` in the console during shutdown. public async Task JSWebWorker_Abandon_Running_JS() { - var cts = new CancellationTokenSource(); - TaskCompletionSource ready = new TaskCompletionSource(); #pragma warning disable CS4014 @@ -287,7 +289,7 @@ public async Task JSWebWorker_Abandon_Running_JS() var never = WebWorkerTestHelper.JSDelay(int.MaxValue); ready.SetResult(); await never; - }, cts.Token); + }, CancellationToken.None); #pragma warning restore CS4014 await ready.Task; @@ -297,11 +299,13 @@ public async Task JSWebWorker_Abandon_Running_JS() // it should not prevent mono and the test suite from exiting } +#endif + [Theory, MemberData(nameof(GetTargetThreads))] public async Task Executor_Propagates(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); bool hit = false; var failedTask = executor.Execute(() => { @@ -321,7 +325,7 @@ public async Task Executor_Propagates(Executor executor) [Theory, MemberData(nameof(GetTargetThreads))] public async Task ManagedConsole(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(() => { Console.WriteLine("C# Hello from ManagedThreadId: " + Environment.CurrentManagedThreadId); @@ -332,7 +336,7 @@ await executor.Execute(() => [Theory, MemberData(nameof(GetTargetThreads))] public async Task JSConsole(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(() => { WebWorkerTestHelper.Log("JS Hello from ManagedThreadId: " + Environment.CurrentManagedThreadId + " NativeThreadId: " + WebWorkerTestHelper.NativeThreadId); @@ -343,7 +347,7 @@ await executor.Execute(() => [Theory, MemberData(nameof(GetTargetThreads))] public async Task NativeThreadId(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(async () => { await executor.StickyAwait(WebWorkerTestHelper.InitializeAsync(), cts.Token); @@ -367,7 +371,7 @@ await executor.Execute(async () => public async Task ThreadingTimer(Executor executor) { var hit = false; - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(async () => { TaskCompletionSource tcs = new TaskCompletionSource(); @@ -389,7 +393,7 @@ await executor.Execute(async () => [Theory, MemberData(nameof(GetTargetThreads))] public async Task JSDelay_ContinueWith(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(async () => { await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); @@ -405,7 +409,7 @@ await WebWorkerTestHelper.JSDelay(10).ContinueWith(_ => [Theory, MemberData(nameof(GetTargetThreads))] public async Task JSDelay_ConfigureAwait_True(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(async () => { await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); @@ -420,7 +424,7 @@ await executor.Execute(async () => public async Task ManagedDelay_ContinueWith(Executor executor) { var hit = false; - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(async () => { await Task.Delay(10, cts.Token).ContinueWith(_ => @@ -434,7 +438,7 @@ await Task.Delay(10, cts.Token).ContinueWith(_ => [Theory, MemberData(nameof(GetTargetThreads))] public async Task ManagedDelay_ConfigureAwait_True(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(async () => { await Task.Delay(10, cts.Token).ConfigureAwait(true); @@ -446,7 +450,7 @@ await executor.Execute(async () => [Theory, MemberData(nameof(GetTargetThreads))] public async Task ManagedYield(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(async () => { await Task.Yield(); @@ -463,23 +467,37 @@ private async Task ActionsInDifferentThreads(Executor executor1, Executor exe { TaskCompletionSource readyTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource doneTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - + var e1Done = false; + var e2Done = false; var e1 = executor1.Execute(async () => { - await e1Job(doneTCS.Task, readyTCS); - if (!readyTCS.Task.IsCompleted) + try { - readyTCS.SetResult(default); + await e1Job(doneTCS.Task, readyTCS); + if (!readyTCS.Task.IsCompleted) + { + readyTCS.SetResult(default); + } + await doneTCS.Task; + } + finally + { + e1Done = true; } - await doneTCS.Task; }, cts.Token); var r1 = await readyTCS.Task.ConfigureAwait(true); var e2 = executor2.Execute(async () => { - await e2Job(r1); - + try + { + await e2Job(r1); + } + finally + { + e2Done = true; + } }, cts.Token); try @@ -488,9 +506,18 @@ private async Task ActionsInDifferentThreads(Executor executor1, Executor exe doneTCS.SetResult(); await e1; } - catch (Exception) + catch (Exception ex) { - cts.Cancel(); + if (ex is OperationCanceledException oce && cts.Token.IsCancellationRequested) + { + throw; + } + Console.WriteLine("ActionsInDifferentThreads failed with: \n" + ex); + if (!e1Done || !e2Done) + { + Console.WriteLine("ActionsInDifferentThreads canceling!"); + cts.Cancel(); + } throw; } } @@ -498,7 +525,7 @@ private async Task ActionsInDifferentThreads(Executor executor1, Executor exe [Theory, MemberData(nameof(GetTargetThreads2x))] public async Task JSObject_CapturesAffinity(Executor executor1, Executor executor2) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); var e1Job = async (Task e2done, TaskCompletionSource e1State) => { @@ -533,7 +560,7 @@ public async Task JSObject_CapturesAffinity(Executor executor1, Executor executo [Theory, MemberData(nameof(GetTargetThreads))] public async Task WebSocketClient_ContentInSameThread(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid()); var message = "hello"; @@ -556,9 +583,9 @@ await executor.Execute(async () => [Theory, MemberData(nameof(GetTargetThreads2x))] - public Task WebSocketClient_ResponseCloseInDifferentThread(Executor executor1, Executor executor2) + public async Task WebSocketClient_ResponseCloseInDifferentThread(Executor executor1, Executor executor2) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid()); var message = "hello"; @@ -587,13 +614,13 @@ public Task WebSocketClient_ResponseCloseInDifferentThread(Executor executor1, E await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None); }; - return ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); + await ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); } [Theory, MemberData(nameof(GetTargetThreads2x))] - public Task WebSocketClient_CancelInDifferentThread(Executor executor1, Executor executor2) + public async Task WebSocketClient_CancelInDifferentThread(Executor executor1, Executor executor2) { - var cts = new CancellationTokenSource(); + using var cts = CreateTestCaseTimeoutSource(); var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid()); var message = ".delay5sec"; // this will make the loopback server slower @@ -620,7 +647,7 @@ public Task WebSocketClient_CancelInDifferentThread(Executor executor1, Executor Assert.Equal(cts2.Token, ex.CancellationToken); }; - return ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); + await ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); } #endregion @@ -630,7 +657,7 @@ public Task WebSocketClient_CancelInDifferentThread(Executor executor1, Executor [Theory, MemberData(nameof(GetTargetThreads))] public async Task HttpClient_ContentInSameThread(Executor executor) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); var uri = WebWorkerTestHelper.GetOriginUrl() + "/_framework/blazor.boot.json"; await executor.Execute(async () => @@ -648,9 +675,9 @@ await executor.Execute(async () => private static string HelloJson = "{'hello':'world'}".Replace('\'', '"'); private static string EchoStart = "{\"Method\":\"POST\",\"Url\":\"/Echo.ashx"; - private Task HttpClient_ActionInDifferentThread(string url, Executor executor1, Executor executor2, Func e2Job) + private async Task HttpClient_ActionInDifferentThread(string url, Executor executor1, Executor executor2, Func e2Job) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); var e1Job = async (Task e2done, TaskCompletionSource e1State) => { @@ -669,7 +696,7 @@ private Task HttpClient_ActionInDifferentThread(string url, Executor executor1, await e2done; }; - return ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); + await ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); } [Theory, MemberData(nameof(GetTargetThreads2x))] diff --git a/src/mono/browser/runtime/pthreads/browser/index.ts b/src/mono/browser/runtime/pthreads/browser/index.ts index 2ff00afe4eda9f..a50bab785afc18 100644 --- a/src/mono/browser/runtime/pthreads/browser/index.ts +++ b/src/mono/browser/runtime/pthreads/browser/index.ts @@ -165,3 +165,12 @@ export async function instantiateWasmPThreadWorkerPool(): Promise { await Promise.all(promises); } } + +export function cancelThreads() { + const workers: PThreadWorker[] = Internals.getRunningWorkers(); + for (const worker of workers) { + if (worker.interopInstalled) { + worker.postMessage({ cmd: "cancel" }); + } + } +} \ No newline at end of file diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts index 0341a16ffc0c67..84d9fedb14c741 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts @@ -15,6 +15,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; // This is what we know about the Emscripten PThread library export interface PThreadLibrary { unusedWorkers: PThreadWorker[]; + runningWorkers: PThreadWorker[]; pthreads: PThreadInfoMap; allocateUnusedWorker: () => void; loadWasmModuleToWorker: (worker: Worker) => Promise; @@ -45,7 +46,7 @@ function isRunningPThreadWorker(w: Worker): w is PThreadWorker { } /// These utility functions dig into Emscripten internals -export const Internals = !MonoWasmThreads ? null as any : { +export const Internals = !MonoWasmThreads ? (null as any) : { get modulePThread(): PThreadLibrary { return (Module).PThread as PThreadLibrary; }, @@ -65,10 +66,13 @@ export const Internals = !MonoWasmThreads ? null as any : { /// It's called when the pool of workers is empty and a new thread is created. Internals.modulePThread.allocateUnusedWorker(); }, - getUnusedWorkerPool: (): Worker[] => { + getUnusedWorkerPool: (): PThreadWorker[] => { return Internals.modulePThread.unusedWorkers; }, - loadWasmModuleToWorker: (worker: Worker): Promise => { + getRunningWorkers: (): PThreadWorker[] => { + return Internals.modulePThread.runningWorkers; + }, + loadWasmModuleToWorker: (worker: PThreadWorker): Promise => { return Internals.modulePThread.loadWasmModuleToWorker(worker); } }; diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts index 30e150df221339..a66daa72254b21 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts @@ -62,7 +62,7 @@ function getNewWorker(modulePThread: PThreadLibrary): PThreadWorker { if (!MonoWasmThreads) return null as any; if (modulePThread.unusedWorkers.length == 0) { - mono_log_warn("Failed to find unused WebWorker, this may deadlock. Please increase the pthreadPoolSize."); + 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--; @@ -83,7 +83,7 @@ function getNewWorker(modulePThread: PThreadLibrary): PThreadWorker { return worker; } } - mono_log_warn("Failed to find loaded WebWorker, this may deadlock. Please increase the pthreadPoolSize."); + 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()!; } diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 55088f67fe72a0..bda57615bfda64 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -25,9 +25,9 @@ import { interp_pgo_load_data, interp_pgo_save_data } from "./interp-pgo"; import { mono_log_debug, mono_log_error, mono_log_warn, mono_set_thread_name } from "./logging"; // threads -import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from "./pthreads/browser"; +import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool, cancelThreads } from "./pthreads/browser"; import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents } from "./pthreads/worker"; -import { mono_wasm_main_thread_ptr } from "./pthreads/shared"; +import { mono_wasm_main_thread_ptr, mono_wasm_pthread_ptr } from "./pthreads/shared"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; import { localHeapViewU8 } from "./memory"; import { assertNoProxies } from "./gc-handles"; @@ -216,7 +216,10 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { await runtimeHelpers.afterPreRun.promise; mono_log_debug("onRuntimeInitialized"); - runtimeHelpers.mono_wasm_exit = cwraps.mono_wasm_exit; + runtimeHelpers.mono_wasm_exit = !MonoWasmThreads ? cwraps.mono_wasm_exit : (code) => { + cancelThreads(); + cwraps.mono_wasm_exit(code); + }; runtimeHelpers.abort = (reason: any) => { loaderHelpers.exitReason = reason; if (!loaderHelpers.is_exited()) { @@ -344,7 +347,13 @@ async function postRunAsync(userpostRun: (() => void)[]) { } export function postRunWorker() { - assertNoProxies(); + if (runtimeHelpers.proxy_context_gc_handle) { + const pthread_ptr = mono_wasm_pthread_ptr(); + mono_log_warn(`JSSynchronizationContext is still installed on worker 0x${pthread_ptr.toString(16)}.`); + } else { + assertNoProxies(); + } + // signal next stage runtimeHelpers.runtimeReady = false; runtimeHelpers.afterPreRun = createPromiseController(); From 875eae2431b25427f1a56e1977decb1cd5613653 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 Jan 2024 11:37:19 +0100 Subject: [PATCH 02/24] don't load snapshot on threads --- src/mono/browser/runtime/snapshot.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/snapshot.ts b/src/mono/browser/runtime/snapshot.ts index 7375c3399352f4..00fbc880dcf1fe 100644 --- a/src/mono/browser/runtime/snapshot.ts +++ b/src/mono/browser/runtime/snapshot.ts @@ -3,7 +3,7 @@ import ProductVersion from "consts:productVersion"; import MonoWasmThreads from "consts:monoWasmThreads"; -import { ENVIRONMENT_IS_WEB, loaderHelpers, runtimeHelpers } from "./globals"; +import { ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_warn } from "./logging"; export const memoryPrefix = "https://dotnet.generated.invalid/wasm-memory"; @@ -46,6 +46,9 @@ export async function openCache(): Promise { export async function checkMemorySnapshotSize(): Promise { try { + if (!ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + return; + } if (!runtimeHelpers.config.startupMemoryCache) { // we could start downloading DLLs because snapshot is disabled return; From 52483a4fe31c25c45fa432d73376c0fd22691982 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 Jan 2024 14:44:34 +0100 Subject: [PATCH 03/24] - only register runtime into globalThis when it's ready started - register it also with correct ID on the webworker - don't load memory snapshot in worker - omit managed stack trace after runtime exited - fix WebWorkerTest --- .../JavaScript/WebWorkerTest.cs | 46 +++++++++++++------ src/mono/browser/runtime/exports.ts | 15 +++--- src/mono/browser/runtime/gc-handles.ts | 4 +- src/mono/browser/runtime/marshal-to-cs.ts | 8 ++-- src/mono/browser/runtime/marshal.ts | 4 ++ src/mono/browser/runtime/startup.ts | 8 +++- src/mono/browser/runtime/types/internal.ts | 45 +----------------- 7 files changed, 60 insertions(+), 70 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs index cf3d97207ba9e1..bac91ad1991c70 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs @@ -465,30 +465,40 @@ await executor.Execute(async () => private async Task ActionsInDifferentThreads(Executor executor1, Executor executor2, Func, Task> e1Job, Func e2Job, CancellationTokenSource cts) { - TaskCompletionSource readyTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - TaskCompletionSource doneTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource job1ReadyTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource job2DoneTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var e1Done = false; var e2Done = false; - var e1 = executor1.Execute(async () => + var e1Failed = false; + Task e1; + Task e2; + T r1; + + async Task ActionsInDifferentThreads1() { try { - await e1Job(doneTCS.Task, readyTCS); - if (!readyTCS.Task.IsCompleted) + await e1Job(job2DoneTCS.Task, job1ReadyTCS); + if (!job1ReadyTCS.Task.IsCompleted) { - readyTCS.SetResult(default); + job1ReadyTCS.SetResult(default); } - await doneTCS.Task; + await job2DoneTCS.Task; + } + catch (Exception ex) + { + Console.WriteLine("ActionsInDifferentThreads1 failed\n" + ex); + job1ReadyTCS.SetResult(default); + e1Failed = true; + throw; } finally { e1Done = true; } - }, cts.Token); - - var r1 = await readyTCS.Task.ConfigureAwait(true); + } - var e2 = executor2.Execute(async () => + async Task ActionsInDifferentThreads2() { try { @@ -498,16 +508,26 @@ private async Task ActionsInDifferentThreads(Executor executor1, Executor exe { e2Done = true; } - }, cts.Token); + } + + + e1 = executor1.Execute(ActionsInDifferentThreads1, cts.Token); + r1 = await job1ReadyTCS.Task.ConfigureAwait(true); + if (e1Failed || e1.IsFaulted) + { + await e1; + } + e2 = executor2.Execute(ActionsInDifferentThreads2, cts.Token); try { await e2; - doneTCS.SetResult(); + job2DoneTCS.SetResult(); await e1; } catch (Exception ex) { + job2DoneTCS.TrySetException(ex); if (ex is OperationCanceledException oce && cts.Token.IsCancellationRequested) { throw; diff --git a/src/mono/browser/runtime/exports.ts b/src/mono/browser/runtime/exports.ts index 14032d5134a8d8..1cea8798bb4b40 100644 --- a/src/mono/browser/runtime/exports.ts +++ b/src/mono/browser/runtime/exports.ts @@ -5,7 +5,7 @@ import ProductVersion from "consts:productVersion"; import BuildConfiguration from "consts:configuration"; import type { RuntimeAPI } from "./types"; -import { Module, exportedRuntimeAPI, passEmscriptenInternals, runtimeHelpers, setRuntimeGlobals, } from "./globals"; +import { Module, exportedRuntimeAPI, loaderHelpers, passEmscriptenInternals, runtimeHelpers, setRuntimeGlobals, } from "./globals"; import { GlobalObjects } from "./types/internal"; import { configureEmscriptenStartup, configureRuntimeStartup, configureWorkerStartup } from "./startup"; @@ -19,6 +19,8 @@ import { instantiate_asset, instantiate_symbols_asset, instantiate_segmentation_ import { jiterpreter_dump_stats } from "./jiterpreter"; import { forceDisposeProxies } from "./gc-handles"; +export let runtimeList: RuntimeList; + function initializeExports(globalObjects: GlobalObjects): RuntimeAPI { const module = Module; const globals = globalObjects; @@ -47,15 +49,13 @@ function initializeExports(globalObjects: GlobalObjects): RuntimeAPI { }); // this code makes it possible to find dotnet runtime on a page via global namespace, even when there are multiple runtimes at the same time - let list: RuntimeList; if (!globalThisAny.getDotnetRuntime) { globalThisAny.getDotnetRuntime = (runtimeId: string) => globalThisAny.getDotnetRuntime.__list.getRuntime(runtimeId); - globalThisAny.getDotnetRuntime.__list = list = new RuntimeList(); + globalThisAny.getDotnetRuntime.__list = runtimeList = new RuntimeList(); } else { - list = globalThisAny.getDotnetRuntime.__list; + runtimeList = globalThisAny.getDotnetRuntime.__list; } - list.registerRuntime(exportedRuntimeAPI); return exportedRuntimeAPI; } @@ -64,8 +64,11 @@ class RuntimeList { private list: { [runtimeId: number]: WeakRef } = {}; public registerRuntime(api: RuntimeAPI): number { - api.runtimeId = Object.keys(this.list).length; + if (api.runtimeId === undefined) { + api.runtimeId = Object.keys(this.list).length; + } this.list[api.runtimeId] = create_weak_ref(api); + loaderHelpers.config.runtimeId = api.runtimeId; return api.runtimeId; } diff --git a/src/mono/browser/runtime/gc-handles.ts b/src/mono/browser/runtime/gc-handles.ts index d0c8c6094cba17..084c8e4f3cf5a5 100644 --- a/src/mono/browser/runtime/gc-handles.ts +++ b/src/mono/browser/runtime/gc-handles.ts @@ -151,7 +151,9 @@ export function teardown_managed_proxy(owner: any, gc_handle: GCHandle, skipMana } } if (gc_handle !== GCHandleNull && _js_owned_object_table.delete(gc_handle) && !skipManaged) { - runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle(gc_handle); + if (!loaderHelpers.is_runtime_running()) { + runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle(gc_handle); + } } if (is_gcv_handle(gc_handle)) { free_gcv_handle(gc_handle); diff --git a/src/mono/browser/runtime/marshal-to-cs.ts b/src/mono/browser/runtime/marshal-to-cs.ts index defc56cb0417a9..7f454bab84ecfc 100644 --- a/src/mono/browser/runtime/marshal-to-cs.ts +++ b/src/mono/browser/runtime/marshal-to-cs.ts @@ -349,8 +349,8 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: if (MonoWasmThreads) { settleUnsettledPromise(); } - // we can unregister the GC handle on JS side - teardown_managed_proxy(holder, gc_handle, true); + // we can unregister the GC handle just on JS side + teardown_managed_proxy(holder, gc_handle, /*skipManaged: */ true); // order of operations with teardown_managed_proxy matters // so that managed user code running in the continuation could allocate the same GCHandle number and the local registry would be already ok with that runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs); @@ -370,8 +370,8 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: if (MonoWasmThreads) { settleUnsettledPromise(); } - // we can unregister the GC handle on JS side - teardown_managed_proxy(holder, gc_handle, true); + // we can unregister the GC handle just on JS side + teardown_managed_proxy(holder, gc_handle, /*skipManaged: */ true); // order of operations with teardown_managed_proxy matters runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined); } diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index bf4c5145badd47..2762e84c4dbfe6 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -351,6 +351,10 @@ export class ManagedError extends Error implements IDisposable { } if (loaderHelpers.is_runtime_running() && (!MonoWasmThreads || runtimeHelpers.proxy_context_gc_handle)) { const gc_handle = (this)[js_owned_gc_handle_symbol]; + if (!loaderHelpers.is_runtime_running()) { + this.managed_stack = "... omitted managed stack trace."; + return this.managed_stack; + } if (gc_handle !== GCHandleNull) { const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle); if (managed_stack) { diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index bda57615bfda64..c2e5f29ad8b72a 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -31,6 +31,7 @@ import { mono_wasm_main_thread_ptr, mono_wasm_pthread_ptr } from "./pthreads/sha import { jiterpreter_allocate_tables } from "./jiterpreter-support"; import { localHeapViewU8 } from "./memory"; import { assertNoProxies } from "./gc-handles"; +import { runtimeList } from "./exports"; export async function configureRuntimeStartup(): Promise { await init_polyfills_async(); @@ -187,6 +188,8 @@ export function preRunWorker() { // signal next stage runtimeHelpers.runtimeReady = true; runtimeHelpers.afterPreRun.promise_control.resolve(); + exportedRuntimeAPI.runtimeId = loaderHelpers.config.runtimeId!; + runtimeList.registerRuntime(exportedRuntimeAPI); } async function preRunAsync(userPreRun: (() => void)[]) { @@ -260,7 +263,6 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { if (!ENVIRONMENT_IS_WORKER) { Module.runtimeKeepalivePush(); } - runtimeHelpers.runtimeReady = true; if (runtimeHelpers.config.virtualWorkingDirectory) { const FS = Module.FS; @@ -279,12 +281,14 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { bindings_init(); jiterpreter_allocate_tables(); - runtimeHelpers.runtimeReady = true; if (ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER) { Module.runtimeKeepalivePush(); } + runtimeHelpers.runtimeReady = true; + runtimeList.registerRuntime(exportedRuntimeAPI); + if (MonoWasmThreads) { runtimeHelpers.javaScriptExports.install_main_synchronization_context(); } diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 00b02c60ad3964..8e5c8caf3e93b2 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -83,6 +83,7 @@ export type MonoConfigInternal = MonoConfig & { asyncFlushOnExit?: boolean exitAfterSnapshot?: number loadAllSatelliteResources?: boolean + runtimeId?: number }; export type RunArguments = { @@ -232,50 +233,6 @@ export type BrowserProfilerOptions = { export type DotnetModule = EmscriptenModule & DotnetModuleConfig; export type DotnetModuleInternal = EmscriptenModule & DotnetModuleConfig & EmscriptenModuleInternal; -// see src/mono/wasm/driver.c MARSHAL_TYPE_xxx and Runtime.cs MarshalType -export const enum MarshalType { - NULL = 0, - INT = 1, - FP64 = 2, - STRING = 3, - VT = 4, - DELEGATE = 5, - TASK = 6, - OBJECT = 7, - BOOL = 8, - ENUM = 9, - URI = 22, - SAFEHANDLE = 23, - ARRAY_BYTE = 10, - ARRAY_UBYTE = 11, - ARRAY_UBYTE_C = 12, - ARRAY_SHORT = 13, - ARRAY_USHORT = 14, - ARRAY_INT = 15, - ARRAY_UINT = 16, - ARRAY_FLOAT = 17, - ARRAY_DOUBLE = 18, - FP32 = 24, - UINT32 = 25, - INT64 = 26, - UINT64 = 27, - CHAR = 28, - STRING_INTERNED = 29, - VOID = 30, - ENUM64 = 31, - POINTER = 32, - SPAN_BYTE = 33, -} - -// see src/mono/wasm/driver.c MARSHAL_ERROR_xxx and Runtime.cs -export const enum MarshalError { - BUFFER_TOO_SMALL = 512, - NULL_CLASS_POINTER = 513, - NULL_TYPE_POINTER = 514, - UNSUPPORTED_TYPE = 515, - FIRST = BUFFER_TOO_SMALL -} - // Evaluates whether a value is nullish (same definition used as the ?? operator, // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) export function is_nullish(value: T | null | undefined): value is null | undefined { From 0a3a1a81db81c10951ea2d42d9f2692338eb255e Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 Jan 2024 14:52:10 +0100 Subject: [PATCH 04/24] more MT smoke tests --- src/libraries/tests.proj | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 2d3d56e60caefd..62adb44efeb6d7 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -605,14 +605,9 @@ - - true - $(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true + $(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true --no-memory-snapshot @@ -22,9 +22,6 @@ - - - @@ -34,6 +31,11 @@ + + + + + diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index e1810e33eeeea6..b2dd78adc39037 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -41,7 +41,7 @@ - + diff --git a/src/mono/browser/runtime/http.ts b/src/mono/browser/runtime/http.ts index bf29d8e0eaa142..8246547eb62023 100644 --- a/src/mono/browser/runtime/http.ts +++ b/src/mono/browser/runtime/http.ts @@ -62,22 +62,32 @@ export function http_wasm_create_controller(): HttpController { } export function http_wasm_abort_request(controller: HttpController): void { - if (controller.streamWriter) { - controller.streamWriter.abort(); + try { + if (controller.streamWriter) { + controller.streamWriter.abort(); + } + } + catch (err) { + // ignore } http_wasm_abort_response(controller); } export function http_wasm_abort_response(controller: HttpController): void { if (BuildConfiguration === "Debug") commonAsserts(controller); - controller.abortController.abort(); - if (controller.streamReader) { - controller.streamReader.cancel().catch((err) => { - if (err && err.name !== "AbortError") { - Module.err("Error in http_wasm_abort_response: " + err); - } - // otherwise, it's expected - }); + try { + if (controller.streamReader) { + controller.streamReader.cancel().catch((err) => { + if (err && err.name !== "AbortError") { + Module.err("Error in http_wasm_abort_response: " + err); + } + // otherwise, it's expected + }); + } + controller.abortController.abort(); + } + catch (err) { + // ignore } } diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts index ddb9fcfb1aeeb0..5516f1a0f813db 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts @@ -60,6 +60,6 @@ export function loadWasmModuleToWorker(worker: PThreadWorker): PromiseModule).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 index 140b56bb7b8d43..48986b24cdc474 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts @@ -6,7 +6,7 @@ import BuildConfiguration from "consts:configuration"; import { onWorkerLoadInitiated } from "../browser"; import { afterThreadInitTLS } from "../worker"; -import { PThreadLibrary, PThreadWorker, getRunningWorkers, getUnusedWorkerPool } from "./emscripten-internals"; +import { PThreadLibrary, PThreadWorker, getModulePThread, getRunningWorkers, getUnusedWorkerPool } from "./emscripten-internals"; import { loaderHelpers, mono_assert } from "../../globals"; import { mono_log_warn } from "../../logging"; @@ -45,7 +45,7 @@ export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): worker.thread.port.close(); } worker.thread = undefined; - if (worker.info && worker.info.hasInterop) { + 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: { @@ -60,6 +60,7 @@ export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): }; if (BuildConfiguration === "Debug") { (globalThis as any).dumpThreads = dumpThreads; + (globalThis as any).getModulePThread = getModulePThread; } } diff --git a/src/mono/browser/runtime/pthreads/shared/index.ts b/src/mono/browser/runtime/pthreads/shared/index.ts index 93b7627049a672..824a50fcbc66bd 100644 --- a/src/mono/browser/runtime/pthreads/shared/index.ts +++ b/src/mono/browser/runtime/pthreads/shared/index.ts @@ -38,15 +38,14 @@ export function mono_wasm_install_js_worker_interop(context_gc_handle: GCHandle) mono_log_debug("Installed JSSynchronizationContext"); } Module.runtimeKeepalivePush(); + monoThreadInfo.isDirtyBecauseOfInterop = true; + update_thread_info(); if (ENVIRONMENT_IS_PTHREAD) { postMessageToMain({ monoCmd: WorkerToMainMessageType.enabledInterop, info: monoThreadInfo, }); } - - monoThreadInfo.hasInterop = true; - update_thread_info(); } export function mono_wasm_uninstall_js_worker_interop(): void { @@ -59,7 +58,6 @@ export function mono_wasm_uninstall_js_worker_interop(): void { runtimeHelpers.proxy_context_gc_handle = GCHandleNull; runtimeHelpers.mono_wasm_bindings_is_ready = false; - monoThreadInfo.hasInterop = false; update_thread_info(); } diff --git a/src/mono/browser/runtime/pthreads/shared/types.ts b/src/mono/browser/runtime/pthreads/shared/types.ts index f9bdf1a06e85cc..ab7e3b1a238bee 100644 --- a/src/mono/browser/runtime/pthreads/shared/types.ts +++ b/src/mono/browser/runtime/pthreads/shared/types.ts @@ -28,7 +28,7 @@ export interface PThreadInfo { isLongRunning?: boolean, isThreadPoolGate?: boolean, isFinalizer?: boolean, - hasInterop?: boolean, + isDirtyBecauseOfInterop?: boolean, } /// Messages sent from the main thread using Worker.postMessage or from the worker using DedicatedWorkerGlobalScope.postMessage From 63df065a898f47646a8f50605877f32bf5505e39 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 28 Jan 2024 18:41:13 +0100 Subject: [PATCH 18/24] thread events --- .../browser/runtime/pthreads/browser/index.ts | 9 +- .../shared/emscripten-replacements.ts | 7 +- .../browser/runtime/pthreads/worker/index.ts | 83 ++++++++++--------- src/mono/browser/runtime/types/internal.ts | 7 +- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/mono/browser/runtime/pthreads/browser/index.ts b/src/mono/browser/runtime/pthreads/browser/index.ts index 7dd6631cb589a2..5285cbe5f5af47 100644 --- a/src/mono/browser/runtime/pthreads/browser/index.ts +++ b/src/mono/browser/runtime/pthreads/browser/index.ts @@ -42,7 +42,7 @@ export function waitForThread(pthreadPtr: pthreadPtr): Promise { return promiseAndController.promise; } -function resolvePromises(pthreadPtr: pthreadPtr, thread: Thread): void { +export function resolveThreadPromises(pthreadPtr: pthreadPtr, thread?: Thread): void { if (!MonoWasmThreads) return; const arr = threadPromises.get(pthreadPtr); if (arr !== undefined) { @@ -96,11 +96,12 @@ function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): thread = new ThreadImpl(pthreadId, worker, port); worker.thread = thread; worker.info.isRunning = true; - resolvePromises(pthreadId, thread); + resolveThreadPromises(pthreadId, thread); // fall through - case WorkerToMainMessageType.enabledInterop: + case WorkerToMainMessageType.monoRegistered: case WorkerToMainMessageType.monoAttached: - case WorkerToMainMessageType.monoDetached: + case WorkerToMainMessageType.enabledInterop: + case WorkerToMainMessageType.monoUnRegistered: worker.info = Object.assign(worker.info!, message.info, {}); break; default: diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts index 48986b24cdc474..48c5a0c5ce34ed 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts @@ -4,8 +4,8 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import BuildConfiguration from "consts:configuration"; -import { onWorkerLoadInitiated } from "../browser"; -import { afterThreadInitTLS } from "../worker"; +import { onWorkerLoadInitiated, resolveThreadPromises } from "../browser"; +import { mono_wasm_pthread_on_pthread_created } from "../worker"; import { PThreadLibrary, PThreadWorker, getModulePThread, getRunningWorkers, getUnusedWorkerPool } from "./emscripten-internals"; import { loaderHelpers, mono_assert } from "../../globals"; import { mono_log_warn } from "../../logging"; @@ -32,7 +32,7 @@ export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): }; modulePThread.threadInitTLS = (): void => { originalThreadInitTLS(); - afterThreadInitTLS(); + mono_wasm_pthread_on_pthread_created(); }; modulePThread.allocateUnusedWorker = allocateUnusedWorker; modulePThread.getNewWorker = () => getNewWorker(modulePThread); @@ -40,6 +40,7 @@ export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): // 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 = 0; if (worker.thread?.port) { worker.thread.port.close(); diff --git a/src/mono/browser/runtime/pthreads/worker/index.ts b/src/mono/browser/runtime/pthreads/worker/index.ts index 618dbadde62e01..ee0377a0d9f523 100644 --- a/src/mono/browser/runtime/pthreads/worker/index.ts +++ b/src/mono/browser/runtime/pthreads/worker/index.ts @@ -83,10 +83,53 @@ function monoDedicatedChannelMessageFromMainToWorker(event: MessageEvent mono_log_debug("got message from main on the dedicated channel", event.data); } + +/// Called by emscripten when a pthread is setup to run on a worker. Can be called multiple times +/// for the same webworker, since emscripten can reuse workers. +/// This is an implementation detail, that shouldn't be used directly. +export function mono_wasm_pthread_on_pthread_created(): void { + if (!MonoWasmThreads) return; + + const pthread_id = mono_wasm_pthread_ptr(); + mono_assert(!is_nullish(pthread_id), "pthread_self() returned null"); + monoThreadInfo.pthreadId = pthread_id; + monoThreadInfo.reuseCount++; + monoThreadInfo.updateCount++; + monoThreadInfo.threadName = `0x${pthread_id.toString(16).padStart(8, "0")}`; + update_thread_info(); + + // don't do this callback for the main thread + if (!ENVIRONMENT_IS_PTHREAD) return; + + currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadCreated, pthread_self)); + + const channel = new MessageChannel(); + const workerPort = channel.port1; + const mainPort = channel.port2; + workerPort.addEventListener("message", monoDedicatedChannelMessageFromMainToWorker); + workerPort.start(); + + // this could be replacement + if (pthread_self && pthread_self.portToBrowser) { + pthread_self.portToBrowser.close(); + } + + pthread_self = new WorkerSelf(monoThreadInfo, workerPort); + postMessageToMain({ + monoCmd: WorkerToMainMessageType.pthreadCreated, + info: monoThreadInfo, + port: mainPort, + }, [mainPort]); +} + /// Called in the worker thread (not main thread) from mono when a pthread becomes registered to the mono runtime. export function mono_wasm_pthread_on_pthread_registered(pthread_id: number): void { if (!MonoWasmThreads) return; mono_assert(monoThreadInfo !== null && monoThreadInfo.pthreadId == pthread_id, "expected monoThreadInfo to be set already when registering"); + postMessageToMain({ + monoCmd: WorkerToMainMessageType.monoRegistered, + info: monoThreadInfo, + }); preRunWorker(); } @@ -132,45 +175,7 @@ export function mono_wasm_pthread_on_pthread_unregistered(pthread_id: number): v monoThreadInfo.threadName = monoThreadInfo.threadName + "=>detached"; update_thread_info(); postMessageToMain({ - monoCmd: WorkerToMainMessageType.monoDetached, + monoCmd: WorkerToMainMessageType.monoUnRegistered, info: monoThreadInfo, }); } - -/// This is an implementation detail function. -/// Called by emscripten when a pthread is setup to run on a worker. Can be called multiple times -/// for the same worker, since emscripten can reuse workers. This is an implementation detail, that shouldn't be used directly. -export function afterThreadInitTLS(): void { - if (!MonoWasmThreads) return; - - const pthread_id = mono_wasm_pthread_ptr(); - mono_assert(!is_nullish(pthread_id), "pthread_self() returned null"); - monoThreadInfo.pthreadId = pthread_id; - monoThreadInfo.reuseCount++; - monoThreadInfo.updateCount++; - monoThreadInfo.threadName = `0x${pthread_id.toString(16).padStart(8, "0")}`; - update_thread_info(); - - // don't do this callback for the main thread - if (!ENVIRONMENT_IS_PTHREAD) return; - - currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadCreated, pthread_self)); - - const channel = new MessageChannel(); - const workerPort = channel.port1; - const mainPort = channel.port2; - workerPort.addEventListener("message", monoDedicatedChannelMessageFromMainToWorker); - workerPort.start(); - - // this could be replacement - if (pthread_self && pthread_self.portToBrowser) { - pthread_self.portToBrowser.close(); - } - - pthread_self = new WorkerSelf(monoThreadInfo, workerPort); - postMessageToMain({ - monoCmd: WorkerToMainMessageType.pthreadCreated, - info: monoThreadInfo, - port: mainPort, - }, [mainPort]); -} \ No newline at end of file diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index d0a40719b49bf8..e0094b30d05592 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -490,10 +490,11 @@ export type WeakRefInternal = WeakRef & { export const monoMessageSymbol = "__mono_message__"; export const enum WorkerToMainMessageType { + monoRegistered = "monoRegistered", + monoAttached = "monoAttached", enabledInterop = "notify_enabled_interop", - monoAttached = "notify_mono_attached", - monoDetached = "notify_mono_detached", - pthreadCreated = "channel_created", + monoUnRegistered = "monoUnRegistered", + pthreadCreated = "pthreadCreated", preload = "preload", } From 8069389fab483190eb0303d106670a599e513099 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 28 Jan 2024 18:46:26 +0100 Subject: [PATCH 19/24] more cache logging --- src/mono/browser/runtime/interp-pgo.ts | 34 +++++++++----------------- src/mono/browser/runtime/snapshot.ts | 12 +++++---- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts index 0530552a59a4cc..f4d2ec8447dcc6 100644 --- a/src/mono/browser/runtime/interp-pgo.ts +++ b/src/mono/browser/runtime/interp-pgo.ts @@ -9,24 +9,7 @@ import cwraps from "./cwraps"; export const tablePrefix = "https://dotnet.generated.invalid/interp_pgo"; -export async function getInterpPgoTable(): Promise { - const cacheKey = await getCacheKey(tablePrefix); - if (!cacheKey) - return undefined; - return await getCacheEntry(cacheKey); -} - -async function storeInterpPgoTable(memory: ArrayBuffer) { - const cacheKey = await getCacheKey(tablePrefix); - if (!cacheKey) - return; - - await storeCacheEntry(cacheKey, memory, "application/octet-stream"); - - cleanupCache(tablePrefix, cacheKey); // no await -} - -export async function interp_pgo_save_data () { +export async function interp_pgo_save_data() { const cacheKey = await getCacheKey(tablePrefix); if (!cacheKey) { mono_log_error("Failed to save interp_pgo table (No cache key)"); @@ -52,17 +35,24 @@ export async function interp_pgo_save_data () { const u8 = localHeapViewU8(); const data = u8.slice(pData, pData + expectedSize); - await storeInterpPgoTable(data); + await storeCacheEntry(cacheKey, data, "application/octet-stream"); + + cleanupCache(tablePrefix, cacheKey); // no await - mono_log_info("Saved interp_pgo table to cache"); Module._free(pData); } catch (exc) { mono_log_error(`Failed to save interp_pgo table: ${exc}`); } } -export async function interp_pgo_load_data () { - const data = await getInterpPgoTable(); +export async function interp_pgo_load_data() { + const cacheKey = await getCacheKey(tablePrefix); + if (!cacheKey) { + mono_log_error("Failed to create cache key for interp_pgo table"); + return; + } + + const data = await getCacheEntry(cacheKey); if (!data) { mono_log_info("Failed to load interp_pgo table (No table found in cache)"); return; diff --git a/src/mono/browser/runtime/snapshot.ts b/src/mono/browser/runtime/snapshot.ts index 00fbc880dcf1fe..ca65cc2b5a6f33 100644 --- a/src/mono/browser/runtime/snapshot.ts +++ b/src/mono/browser/runtime/snapshot.ts @@ -10,14 +10,16 @@ export const memoryPrefix = "https://dotnet.generated.invalid/wasm-memory"; // adapted from Blazor's WebAssemblyResourceLoader.ts export async function openCache(): Promise { - // caches will be undefined if we're running on an insecure origin (secure means https or localhost) - if (typeof globalThis.caches === "undefined") { - return null; - } - // cache integrity is compromised if the first request has been served over http (except localhost) // in this case, we want to disable caching and integrity validation if (ENVIRONMENT_IS_WEB && globalThis.window.isSecureContext === false) { + mono_log_warn("Failed to open the cache, running on an insecure origin"); + return null; + } + + // caches will be undefined if we're running on an insecure origin (secure means https or localhost) + if (typeof globalThis.caches === "undefined") { + mono_log_warn("Failed to open the cache, probably running on an insecure origin"); return null; } From fefe4716183c168b3b15eacb1451233591c9a266 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 28 Jan 2024 21:24:30 +0100 Subject: [PATCH 20/24] fix --- src/mono/browser/runtime/interp-pgo.ts | 4 +++- src/mono/browser/runtime/snapshot.ts | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts index f4d2ec8447dcc6..23f1d200685a1d 100644 --- a/src/mono/browser/runtime/interp-pgo.ts +++ b/src/mono/browser/runtime/interp-pgo.ts @@ -35,7 +35,9 @@ export async function interp_pgo_save_data() { const u8 = localHeapViewU8(); const data = u8.slice(pData, pData + expectedSize); - await storeCacheEntry(cacheKey, data, "application/octet-stream"); + if (await storeCacheEntry(cacheKey, data, "application/octet-stream")) { + mono_log_info("Saved interp_pgo table to cache"); + } cleanupCache(tablePrefix, cacheKey); // no await diff --git a/src/mono/browser/runtime/snapshot.ts b/src/mono/browser/runtime/snapshot.ts index ca65cc2b5a6f33..db5c1ec7a12487 100644 --- a/src/mono/browser/runtime/snapshot.ts +++ b/src/mono/browser/runtime/snapshot.ts @@ -115,11 +115,11 @@ export async function getCacheEntry(cacheKey: string): Promise { try { const cache = await openCache(); if (!cache) { - return; + return false; } const copy = MonoWasmThreads // storing SHaredArrayBuffer in the cache is not working @@ -134,9 +134,11 @@ export async function storeCacheEntry(cacheKey: string, memory: ArrayBuffer, mim }); await cache.put(cacheKey, responseToCache); + + return true; } catch (ex) { mono_log_warn("Failed to store entry to the cache: " + cacheKey, ex); - return; + return false; } } From a99547fdf9d0f47380d4ee68d0c0ab5a12a4d5d8 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 29 Jan 2024 10:37:10 +0100 Subject: [PATCH 21/24] fix snapshot hash --- src/mono/browser/runtime/interp-pgo.ts | 6 +++++- src/mono/browser/runtime/snapshot.ts | 6 ++++-- src/mono/browser/runtime/types/internal.ts | 6 ++++++ src/mono/wasm/Wasm.Build.Tests/Templates/InterpPgoTests.cs | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts index 23f1d200685a1d..8e2b42af64499a 100644 --- a/src/mono/browser/runtime/interp-pgo.ts +++ b/src/mono/browser/runtime/interp-pgo.ts @@ -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 { Module } from "./globals"; +import { Module, loaderHelpers } from "./globals"; import { getCacheKey, cleanupCache, getCacheEntry, storeCacheEntry } from "./snapshot"; import { mono_log_info, mono_log_error } from "./logging"; import { localHeapViewU8 } from "./memory"; @@ -10,6 +10,10 @@ import cwraps from "./cwraps"; export const tablePrefix = "https://dotnet.generated.invalid/interp_pgo"; export async function interp_pgo_save_data() { + if (!loaderHelpers.is_runtime_running()) { + mono_log_info("Skipped saveing interp_pgo table (already exited)"); + return; + } const cacheKey = await getCacheKey(tablePrefix); if (!cacheKey) { mono_log_error("Failed to save interp_pgo table (No cache key)"); diff --git a/src/mono/browser/runtime/snapshot.ts b/src/mono/browser/runtime/snapshot.ts index db5c1ec7a12487..1806c748bc28f2 100644 --- a/src/mono/browser/runtime/snapshot.ts +++ b/src/mono/browser/runtime/snapshot.ts @@ -5,6 +5,7 @@ import ProductVersion from "consts:productVersion"; import MonoWasmThreads from "consts:monoWasmThreads"; import { ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_warn } from "./logging"; +import { MonoConfigInternal } from "./types/internal"; export const memoryPrefix = "https://dotnet.generated.invalid/wasm-memory"; @@ -174,10 +175,10 @@ export async function getCacheKey(prefix: string): Promise { if (!runtimeHelpers.subtle) { return null; } - const inputs = Object.assign({}, runtimeHelpers.config) as any; + const inputs = Object.assign({}, runtimeHelpers.config) as MonoConfigInternal; // Now we remove assets collection from the hash. - inputs.resourcesHash = inputs.resources.hash; + inputs.resourcesHash = inputs.resources!.hash; delete inputs.assets; delete inputs.resources; // some things are calculated at runtime, so we need to add them to the hash @@ -199,6 +200,7 @@ export async function getCacheKey(prefix: string): Promise { delete inputs.enableDownloadRetry; delete inputs.exitAfterSnapshot; delete inputs.extensions; + delete inputs.runtimeId; inputs.GitHash = loaderHelpers.gitHash; inputs.ProductVersion = ProductVersion; diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index e0094b30d05592..49202c86699a66 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -84,6 +84,12 @@ export type MonoConfigInternal = MonoConfig & { exitAfterSnapshot?: number loadAllSatelliteResources?: boolean runtimeId?: number + + // related to config hash + preferredIcuAsset: string | null, + resourcesHash: string | undefined, + GitHash: string | undefined, + ProductVersion: string | undefined, }; export type RunArguments = { diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/InterpPgoTests.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/InterpPgoTests.cs index dfdd86dcd77646..8982e50f7a301d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/InterpPgoTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/InterpPgoTests.cs @@ -107,6 +107,7 @@ public async Task FirstRunGeneratesTableAndSecondRunLoadsIt(string config) Assert.Contains("Hello, Browser!", output); // Verify that table data was loaded from cache + // if this breaks, it could be caused by change in config which affects the config hash and the cache storage hash key Assert.Contains(" bytes of interp_pgo data (table size == ", output); // Verify that the table was saved after the app ran Assert.Contains("Saved interp_pgo table", output); From 902f17379f596e998679809d1b94599fa56eb1d1 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 29 Jan 2024 11:32:49 +0100 Subject: [PATCH 22/24] fix --- src/mono/browser/runtime/types/internal.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 49202c86699a66..c8b88b4bdad02d 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -86,10 +86,10 @@ export type MonoConfigInternal = MonoConfig & { runtimeId?: number // related to config hash - preferredIcuAsset: string | null, - resourcesHash: string | undefined, - GitHash: string | undefined, - ProductVersion: string | undefined, + preferredIcuAsset?: string | null, + resourcesHash?: string, + GitHash?: string, + ProductVersion?: string, }; export type RunArguments = { @@ -125,7 +125,7 @@ export type LoaderHelpers = { scriptDirectory: string scriptUrl: string modulesUniqueQuery?: string - preferredIcuAsset: string | null, + preferredIcuAsset?: string | null, invariantMode: boolean, actual_downloaded_assets_count: number, From 988cee6fb54785039bd93fc6a624418f668d7b7d Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 29 Jan 2024 12:01:47 +0100 Subject: [PATCH 23/24] Update src/mono/browser/runtime/interp-pgo.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marek FiĊĦera --- src/mono/browser/runtime/interp-pgo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts index 8e2b42af64499a..fad17a1b8f7597 100644 --- a/src/mono/browser/runtime/interp-pgo.ts +++ b/src/mono/browser/runtime/interp-pgo.ts @@ -11,7 +11,7 @@ export const tablePrefix = "https://dotnet.generated.invalid/interp_pgo"; export async function interp_pgo_save_data() { if (!loaderHelpers.is_runtime_running()) { - mono_log_info("Skipped saveing interp_pgo table (already exited)"); + mono_log_info("Skipped saving interp_pgo table (already exited)"); return; } const cacheKey = await getCacheKey(tablePrefix); From d02549042a04e8092547f144e3303aa920b982d8 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 29 Jan 2024 12:53:04 +0100 Subject: [PATCH 24/24] feedback --- ...me.InteropServices.JavaScript.Tests.csproj | 2 ++ .../JavaScript/WebWorkerTest.Http.cs | 5 +++-- .../JavaScript/WebWorkerTest.cs | 19 +++++++++++++++++++ .../JavaScript/WebWorkerTestHelper.cs | 9 ++++++++- .../InteropServices/JavaScript/test.json | 3 +++ src/mono/browser/runtime/globals.ts | 2 +- src/mono/browser/runtime/invoke-cs.ts | 4 ++-- src/mono/browser/runtime/loader/exit.ts | 4 ++-- src/mono/browser/runtime/loader/globals.ts | 16 +++++++++------- src/mono/browser/runtime/marshal-to-cs.ts | 4 ++-- .../browser/runtime/pthreads/browser/index.ts | 1 + src/mono/browser/runtime/run.ts | 17 +++++++++++++++++ src/mono/browser/runtime/startup.ts | 16 ++++------------ src/mono/browser/runtime/types/internal.ts | 8 ++++---- 14 files changed, 77 insertions(+), 33 deletions(-) create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/test.json diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj index 8a8f3e6f584dbf..a4e88845ec0808 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj @@ -43,7 +43,9 @@ + + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.Http.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.Http.cs index a5472c689fb939..8939cca759b19d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.Http.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.Http.cs @@ -18,7 +18,7 @@ public class WebWorkerHttpTest : WebWorkerTestBase public async Task HttpClient_ContentInSameThread(Executor executor) { using var cts = CreateTestCaseTimeoutSource(); - var uri = WebWorkerTestHelper.GetOriginUrl() + "/_framework/blazor.boot.json"; + var uri = WebWorkerTestHelper.GetOriginUrl() + "/test.json"; await executor.Execute(async () => { @@ -26,7 +26,8 @@ await executor.Execute(async () => using var response = await client.GetAsync(uri); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); - Assert.StartsWith("{", body); + Assert.Contains("hello", body); + Assert.Contains("world", body); }, cts.Token); } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs index 54acd3d7e92a2d..63dc4460754fa7 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs @@ -263,6 +263,25 @@ public async Task Executor_Propagates(Executor executor) Assert.Equal("Test", ex.Message); } + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task Executor_Propagates_After_Delay(Executor executor) + { + using var cts = CreateTestCaseTimeoutSource(); + bool hit = false; + var failedTask = executor.Execute(async () => + { + await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); + await WebWorkerTestHelper.JSDelay(10); + + hit = true; + throw new InvalidOperationException("Test"); + }, cts.Token); + + var ex = await Assert.ThrowsAsync(async () => await failedTask); + Assert.True(hit); + Assert.Equal("Test", ex.Message); + } + #endregion #region Console, Yield, Delay, Timer diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs index ad0191f9cf881f..24cafa2f743a53 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs @@ -330,7 +330,14 @@ public static Task RunOnNewThread(Func job, CancellationToken cancellation } catch (Exception ex) { - tcs.TrySetException(ex); + if(ex is AggregateException agg) + { + tcs.TrySetException(agg.InnerException); + } + else + { + tcs.TrySetException(ex); + } } finally { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/test.json b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/test.json new file mode 100644 index 00000000000000..9c3916c2a43ae9 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/test.json @@ -0,0 +1,3 @@ +{ + "hello":"world" +} \ No newline at end of file diff --git a/src/mono/browser/runtime/globals.ts b/src/mono/browser/runtime/globals.ts index 3ccef85d631b12..702bb3fc94d8c1 100644 --- a/src/mono/browser/runtime/globals.ts +++ b/src/mono/browser/runtime/globals.ts @@ -104,5 +104,5 @@ export function mono_assert(condition: unknown, messageFactory: string | (() => ? messageFactory() : messageFactory); const error = new Error(message); - runtimeHelpers.abort(error); + runtimeHelpers.nativeAbort(error); } diff --git a/src/mono/browser/runtime/invoke-cs.ts b/src/mono/browser/runtime/invoke-cs.ts index dccdbe5be31317..2bdf952464318c 100644 --- a/src/mono/browser/runtime/invoke-cs.ts +++ b/src/mono/browser/runtime/invoke-cs.ts @@ -354,7 +354,7 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM try { set_args_context(args); const fail = cwraps.mono_wasm_invoke_method_bound(method, args, fail_root.address); - if (fail) runtimeHelpers.abort("ERR24: Unexpected error: " + monoStringToString(fail_root)); + if (fail) runtimeHelpers.nativeAbort("ERR24: Unexpected error: " + monoStringToString(fail_root)); if (is_args_exception(args)) { const exc = get_arg(args, 0); throw marshal_exception_to_js(exc); @@ -370,7 +370,7 @@ export function invoke_method_raw(method: MonoMethod): void { const fail_root = mono_wasm_new_root(); try { const fail = cwraps.mono_wasm_invoke_method_raw(method, fail_root.address); - if (fail) runtimeHelpers.abort("ERR24: Unexpected error: " + monoStringToString(fail_root)); + if (fail) runtimeHelpers.nativeAbort("ERR24: Unexpected error: " + monoStringToString(fail_root)); } finally { fail_root.release(); diff --git a/src/mono/browser/runtime/loader/exit.ts b/src/mono/browser/runtime/loader/exit.ts index 3aaf8144a48e82..7cf42e05ec0903 100644 --- a/src/mono/browser/runtime/loader/exit.ts +++ b/src/mono/browser/runtime/loader/exit.ts @@ -126,10 +126,10 @@ export function mono_exit(exit_code: number, reason?: any): void { } function set_exit_code_and_quit_now(exit_code: number, reason?: any): void { - if (runtimeHelpers.runtimeReady && runtimeHelpers.mono_wasm_exit) { + if (runtimeHelpers.runtimeReady && runtimeHelpers.nativeExit) { runtimeHelpers.runtimeReady = false; try { - runtimeHelpers.mono_wasm_exit(exit_code); + runtimeHelpers.nativeExit(exit_code); } catch (err) { if (runtimeHelpers.ExitStatus && !(err instanceof runtimeHelpers.ExitStatus)) { diff --git a/src/mono/browser/runtime/loader/globals.ts b/src/mono/browser/runtime/loader/globals.ts index e5de83002c7a14..386624b119eb4d 100644 --- a/src/mono/browser/runtime/loader/globals.ts +++ b/src/mono/browser/runtime/loader/globals.ts @@ -72,14 +72,15 @@ export function setLoaderGlobals( Object.assign(globalObjects.module, { config: deep_merge_config(monoConfig, { environmentVariables: {} }), }); - Object.assign(runtimeHelpers, { + const rh: Partial = { mono_wasm_bindings_is_ready: false, javaScriptExports: {} as any, config: globalObjects.module.config, diagnosticTracing: false, - abort: (reason: any) => { throw reason; }, - }); - Object.assign(loaderHelpers, { + nativeAbort: (reason: any) => { throw reason; }, + nativeExit: (code: number) => { throw new Error("exit:" + code); } + }; + const lh: Partial = { gitHash, config: globalObjects.module.config, diagnosticTracing: false, @@ -124,8 +125,9 @@ export function setLoaderGlobals( // from wasm-feature-detect npm package exceptions, simd, - - } as Partial); + }; + Object.assign(runtimeHelpers, rh); + Object.assign(loaderHelpers, lh); } // this will abort the program if the condition is false @@ -137,5 +139,5 @@ export function mono_assert(condition: unknown, messageFactory: string | (() => ? messageFactory() : messageFactory); const error = new Error(message); - runtimeHelpers.abort(error); + runtimeHelpers.nativeAbort(error); } \ No newline at end of file diff --git a/src/mono/browser/runtime/marshal-to-cs.ts b/src/mono/browser/runtime/marshal-to-cs.ts index 7f454bab84ecfc..fa5e0a7d7056d2 100644 --- a/src/mono/browser/runtime/marshal-to-cs.ts +++ b/src/mono/browser/runtime/marshal-to-cs.ts @@ -356,7 +356,7 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs); } catch (ex) { - runtimeHelpers.abort(ex); + runtimeHelpers.nativeAbort(ex); } } @@ -376,7 +376,7 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined); } catch (ex) { - runtimeHelpers.abort(ex); + runtimeHelpers.nativeAbort(ex); } } diff --git a/src/mono/browser/runtime/pthreads/browser/index.ts b/src/mono/browser/runtime/pthreads/browser/index.ts index 5285cbe5f5af47..7e6a4f41f9402f 100644 --- a/src/mono/browser/runtime/pthreads/browser/index.ts +++ b/src/mono/browser/runtime/pthreads/browser/index.ts @@ -161,6 +161,7 @@ export async function instantiateWasmPThreadWorkerPool(): Promise { } } +// 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) { diff --git a/src/mono/browser/runtime/run.ts b/src/mono/browser/runtime/run.ts index 4fda22cc2e713a..8443fc647749d3 100644 --- a/src/mono/browser/runtime/run.ts +++ b/src/mono/browser/runtime/run.ts @@ -1,6 +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 MonoWasmThreads from "consts:monoWasmThreads"; + import { ENVIRONMENT_IS_NODE, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_wasm_wait_for_debugger } from "./debug"; import { mono_wasm_set_main_args } from "./startup"; @@ -8,6 +10,7 @@ import cwraps from "./cwraps"; import { mono_log_info } from "./logging"; import { assert_js_interop } from "./invoke-js"; import { assembly_load } from "./invoke-cs"; +import { cancelThreads } from "./pthreads/browser"; /** * Possible signatures are described here https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/main-command-line @@ -81,3 +84,17 @@ export function find_entry_point(assembly: string) { return method; } +export function nativeExit(code: number) { + if (MonoWasmThreads) { + cancelThreads(); + } + cwraps.mono_wasm_exit(code); +} + +export function nativeAbort(reason: any) { + loaderHelpers.exitReason = reason; + if (!loaderHelpers.is_exited()) { + cwraps.mono_wasm_abort(); + } + throw reason; +} \ No newline at end of file diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 58a496a1ed972a..825f93b033ea37 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -25,13 +25,14 @@ 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, instantiateWasmPThreadWorkerPool, cancelThreads } from "./pthreads/browser"; +import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from "./pthreads/browser"; import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents } from "./pthreads/worker"; import { mono_wasm_main_thread_ptr, mono_wasm_pthread_ptr } from "./pthreads/shared"; 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"; export async function configureRuntimeStartup(): Promise { await init_polyfills_async(); @@ -221,17 +222,8 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { await runtimeHelpers.afterPreRun.promise; mono_log_debug("onRuntimeInitialized"); - runtimeHelpers.mono_wasm_exit = !MonoWasmThreads ? cwraps.mono_wasm_exit : (code) => { - cancelThreads(); - cwraps.mono_wasm_exit(code); - }; - runtimeHelpers.abort = (reason: any) => { - loaderHelpers.exitReason = reason; - if (!loaderHelpers.is_exited()) { - cwraps.mono_wasm_abort(); - } - throw reason; - }; + runtimeHelpers.nativeExit = nativeExit; + runtimeHelpers.nativeAbort = nativeAbort; const mark = startMeasure(); // signal this stage, this will allow pending assets to allocate memory diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index c8b88b4bdad02d..c30acbb67e5e89 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -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 type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, RuntimeAPI } from "."; +import type { AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, RuntimeAPI, SingleAssetBehaviors } from "."; import type { PThreadLibrary } from "../pthreads/shared/emscripten-internals"; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; @@ -147,7 +147,7 @@ export type LoaderHelpers = { getPromiseController: (promise: ControllablePromise) => PromiseController, assertIsControllablePromise: (promise: Promise) => asserts promise is ControllablePromise, mono_download_assets: () => Promise, - resolve_single_asset_path: (behavior: AssetBehaviors) => AssetEntryInternal, + resolve_single_asset_path: (behavior: SingleAssetBehaviors) => AssetEntryInternal, setup_proxy_console: (id: string, console: Console, origin: string) => void mono_set_thread_name: (tid: string) => void fetch_like: (url: string, init?: RequestInit) => Promise; @@ -192,8 +192,8 @@ export type RuntimeHelpers = { waitForDebugger?: number; ExitStatus: ExitStatusError; quit: Function, - mono_wasm_exit?: (code: number) => void, - abort: (reason: any) => void, + nativeExit: (code: number) => void, + nativeAbort: (reason: any) => void, javaScriptExports: JavaScriptExports, storeMemorySnapshotPending: boolean, memorySnapshotCacheKey: string,