From f6a96fb85f187cc749843ac1cc8186431498df18 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 13 Dec 2023 08:55:46 +0100 Subject: [PATCH] [browser][mt] dynamic thread create (#95702) --- ...ft.Extensions.Logging.Console.Tests.csproj | 1 - .../CompatibilitySuppressions.WasmThreads.xml | 2 +- ....Runtime.InteropServices.JavaScript.csproj | 8 +- .../JavaScript/Interop/JavaScriptExports.cs | 3 +- .../Interop/JavaScriptImports.Generated.cs | 4 + .../{WebWorker.cs => JSWebWorker.cs} | 98 +++++++------------ .../System.Threading.Tasks.Tests.csproj | 1 - .../{WebWorker.cs => JSWebWorker.cs} | 57 ++++------- .../wasm/browser-threads-minimal/Program.cs | 15 +-- .../tests/debugger-test/debugger-test.csproj | 2 - src/mono/wasm/runtime/es6/dotnet.es6.lib.js | 23 ++--- src/mono/wasm/runtime/exports-internal.ts | 2 + src/mono/wasm/runtime/loader/config.ts | 2 +- src/mono/wasm/runtime/polyfills.ts | 6 +- .../wasm/runtime/pthreads/browser/index.ts | 48 +++++++-- .../pthreads/shared/emscripten-internals.ts | 20 ++-- .../shared/emscripten-replacements.ts | 89 +++++++++++++---- .../wasm/runtime/pthreads/shared/index.ts | 55 ++++++++--- .../wasm/runtime/pthreads/worker/index.ts | 10 +- src/mono/wasm/runtime/types/internal.ts | 8 +- src/mono/wasm/wasm.proj | 2 +- 21 files changed, 261 insertions(+), 195 deletions(-) rename src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/{WebWorker.cs => JSWebWorker.cs} (73%) rename src/mono/sample/wasm/browser-threads-minimal/{WebWorker.cs => JSWebWorker.cs} (57%) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj index b2801823f13f5..c38ae483cf00d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj @@ -5,7 +5,6 @@ true true true - <_WasmPThreadPoolSize Condition="'$(MonoWasmBuildVariant)' == 'multithread'">100 diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/CompatibilitySuppressions.WasmThreads.xml b/src/libraries/System.Runtime.InteropServices.JavaScript/src/CompatibilitySuppressions.WasmThreads.xml index e6ca924ab4d70..dfc469c8a165e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/CompatibilitySuppressions.WasmThreads.xml +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/CompatibilitySuppressions.WasmThreads.xml @@ -2,7 +2,7 @@ CP0001 - T:System.Runtime.InteropServices.JavaScript.WebWorker + T:System.Runtime.InteropServices.JavaScript.JSWebWorker ref/net9.0/System.Runtime.InteropServices.JavaScript.dll runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj index c8425dfe89f4a..0f4f0fd6d4184 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -15,9 +15,9 @@ true false true - $(DefineConstants);FEATURE_WASM_THREADS - $(DefineConstants);DISABLE_LEGACY_JS_INTEROP - $(DefineConstants);ENABLE_JS_INTEROP_BY_VALUE + $(DefineConstants);FEATURE_WASM_THREADS + $(DefineConstants);DISABLE_LEGACY_JS_INTEROP + $(DefineConstants);ENABLE_JS_INTEROP_BY_VALUE true @@ -81,7 +81,7 @@ - + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 747ebdbcb7d10..1233115fab072 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -272,9 +272,10 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer) #if FEATURE_WASM_THREADS + // this is here temporarily, until JSWebWorker becomes public API + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")] // the marshaled signature is: // void InstallSynchronizationContext() - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.WebWorker", "System.Runtime.InteropServices.JavaScript")] public static void InstallSynchronizationContext (JSMarshalerArgument* arguments_buffer) { ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() try diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs index 589ea75add2fb..77716ab72c0c8 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs @@ -44,6 +44,10 @@ internal static unsafe partial class JavaScriptImports public static partial JSObject GetDotnetInstance(); [JSImport("INTERNAL.dynamic_import")] public static partial Task DynamicImport(string moduleName, string moduleUrl); +#if FEATURE_WASM_THREADS + [JSImport("INTERNAL.thread_available")] + public static partial Task ThreadAvailable(); +#endif #if DEBUG [JSImport("globalThis.console.log")] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/WebWorker.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs similarity index 73% rename from src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/WebWorker.cs rename to src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs index c9ad70ab9a4e9..7b2cf935bacb4 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/WebWorker.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs @@ -14,9 +14,37 @@ namespace System.Runtime.InteropServices.JavaScript /// This is draft for possible public API of browser thread (web worker) dedicated to JS interop workloads. /// The method names are unique to make it easy to call them via reflection for now. All of them should be just `RunAsync` probably. /// - public static class WebWorker + public static class JSWebWorker { - public static Task RunAsync(Func> body, CancellationToken cancellationToken) + // temporary, for easy reflection + internal static Task RunAsyncVoid(Func body, CancellationToken cancellationToken) => RunAsync(body, cancellationToken); + internal static Task RunAsyncGeneric(Func> body, CancellationToken cancellationToken) => RunAsync(body, cancellationToken); + + public static Task RunAsync(Func> body) + { + return RunAsync(body, CancellationToken.None); + } + + public static Task RunAsync(Func body) + { + return RunAsync(body, CancellationToken.None); + } + + public static async Task RunAsync(Func> body, CancellationToken cancellationToken) + { + // TODO remove main thread condition later + if (Thread.CurrentThread.ManagedThreadId == 1) await JavaScriptImports.ThreadAvailable().ConfigureAwait(false); + return await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false); + } + + public static async Task RunAsync(Func body, CancellationToken cancellationToken) + { + // TODO remove main thread condition later + if (Thread.CurrentThread.ManagedThreadId == 1) await JavaScriptImports.ThreadAvailable().ConfigureAwait(false); + await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false); + } + + private static Task RunAsyncImpl(Func> body, CancellationToken cancellationToken) { var parentContext = SynchronizationContext.Current ?? new SynchronizationContext(); var tcs = new TaskCompletionSource(); @@ -53,7 +81,7 @@ public static Task RunAsync(Func> body, CancellationToken cancella return tcs.Task; } - public static Task RunAsyncVoid(Func body, CancellationToken cancellationToken) + private static Task RunAsyncImpl(Func body, CancellationToken cancellationToken) { var parentContext = SynchronizationContext.Current ?? new SynchronizationContext(); var tcs = new TaskCompletionSource(); @@ -90,44 +118,6 @@ public static Task RunAsyncVoid(Func body, CancellationToken cancellationT return tcs.Task; } - public static Task Run(Action body, CancellationToken cancellationToken) - { - var parentContext = SynchronizationContext.Current ?? new SynchronizationContext(); - var tcs = new TaskCompletionSource(); - var capturedContext = SynchronizationContext.Current; - var t = new Thread(() => - { - try - { - if (cancellationToken.IsCancellationRequested) - { - PostWhenCancellation(parentContext, tcs); - return; - } - - JSHostImplementation.InstallWebWorkerInterop(false); - try - { - body(); - SendWhenDone(parentContext, tcs); - } - catch (Exception ex) - { - SendWhenException(parentContext, tcs, ex); - } - JSHostImplementation.UninstallWebWorkerInterop(); - } - catch (Exception ex) - { - SendWhenException(parentContext, tcs, ex); - } - - }); - JSHostImplementation.SetHasExternalEventLoop(t); - t.Start(); - return tcs.Task; - } - #region posting result to the original thread when handling exception private static void PostWhenCancellation(SynchronizationContext ctx, TaskCompletionSource tcs) @@ -138,7 +128,7 @@ private static void PostWhenCancellation(SynchronizationContext ctx, TaskComplet } catch (Exception e) { - Environment.FailFast("WebWorker.RunAsync failed", e); + Environment.FailFast("JSWebWorker.RunAsync failed", e); } } @@ -150,7 +140,7 @@ private static void PostWhenCancellation(SynchronizationContext ctx, TaskComp } catch (Exception e) { - Environment.FailFast("WebWorker.RunAsync failed", e); + Environment.FailFast("JSWebWorker.RunAsync failed", e); } } @@ -165,19 +155,7 @@ private static void SendWhenDone(SynchronizationContext ctx, TaskCompletionSourc } catch (Exception e) { - Environment.FailFast("WebWorker.RunAsync failed", e); - } - } - - private static void SendWhenDone(SynchronizationContext ctx, TaskCompletionSource tcs) - { - try - { - ctx.Send((_) => tcs.SetResult(), null); - } - catch (Exception e) - { - Environment.FailFast("WebWorker.RunAsync failed", e); + Environment.FailFast("JSWebWorker.RunAsync failed", e); } } @@ -189,7 +167,7 @@ private static void SendWhenException(SynchronizationContext ctx, TaskCompletion } catch (Exception e) { - Environment.FailFast("WebWorker.RunAsync failed", e); + Environment.FailFast("JSWebWorker.RunAsync failed", e); } } @@ -201,7 +179,7 @@ private static void SendWhenException(SynchronizationContext ctx, TaskComplet } catch (Exception e) { - Environment.FailFast("WebWorker.RunAsync failed", e); + Environment.FailFast("JSWebWorker.RunAsync failed", e); } } @@ -216,7 +194,7 @@ private static void SendWhenDone(SynchronizationContext ctx, TaskCompletionSo } catch (Exception e) { - Environment.FailFast("WebWorker.RunAsync failed", e); + Environment.FailFast("JSWebWorker.RunAsync failed", e); } } @@ -224,7 +202,7 @@ internal static void PropagateCompletion(TaskCompletionSource tcs, Task { if (done.IsFaulted) { - if(done.Exception is AggregateException ag && ag.InnerException!=null) + if (done.Exception is AggregateException ag && ag.InnerException != null) { tcs.SetException(ag.InnerException); } diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Threading.Tasks.Tests.csproj b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Threading.Tasks.Tests.csproj index dfd17dfa6e6d4..eb1b13766e64f 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Threading.Tasks.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Threading.Tasks.Tests.csproj @@ -3,7 +3,6 @@ true true $(NetCoreAppCurrent) - <_WasmPThreadPoolSize Condition="'$(MonoWasmBuildVariant)' == 'multithread'">64 diff --git a/src/mono/sample/wasm/browser-threads-minimal/WebWorker.cs b/src/mono/sample/wasm/browser-threads-minimal/JSWebWorker.cs similarity index 57% rename from src/mono/sample/wasm/browser-threads-minimal/WebWorker.cs rename to src/mono/sample/wasm/browser-threads-minimal/JSWebWorker.cs index b1036898f2466..c197cc266ebae 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/WebWorker.cs +++ b/src/mono/sample/wasm/browser-threads-minimal/JSWebWorker.cs @@ -7,29 +7,17 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -namespace System.Runtime.InteropServices.JavaScript +namespace Sample { // this is just temporary thin wrapper to expose future public API - public partial class WebWorker + public partial class JSWebWorker { private static MethodInfo runAsyncMethod; private static MethodInfo runAsyncVoidMethod; - private static MethodInfo runMethod; - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.WebWorker", "System.Runtime.InteropServices.JavaScript")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "work in progress")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "work in progress")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern", Justification = "work in progress")] - public static Task RunAsync(Func> body, CancellationToken cancellationToken) + public static Task RunAsync(Func body) { - if(runAsyncMethod == null) - { - var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.WebWorker"); - runAsyncMethod = webWorker.GetMethod("RunAsync", BindingFlags.Public|BindingFlags.Static); - } - - var genericRunAsyncMethod = runAsyncMethod.MakeGenericMethod(typeof(T)); - return (Task)genericRunAsyncMethod.Invoke(null, new object[] { body, cancellationToken }); + return RunAsync(body, CancellationToken.None); } public static Task RunAsync(Func> body) @@ -37,40 +25,35 @@ public static Task RunAsync(Func> body) return RunAsync(body, CancellationToken.None); } - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.WebWorker", "System.Runtime.InteropServices.JavaScript")] + + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "work in progress")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "work in progress")] - public static Task RunAsync(Func body, CancellationToken cancellationToken) + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern", Justification = "work in progress")] + public static Task RunAsync(Func> body, CancellationToken cancellationToken) { - if(runAsyncVoidMethod == null) + if(runAsyncMethod == null) { - var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.WebWorker"); - runAsyncVoidMethod = webWorker.GetMethod("RunAsyncVoid", BindingFlags.Public|BindingFlags.Static); + var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker"); + runAsyncMethod = webWorker.GetMethod("RunAsyncGeneric", BindingFlags.NonPublic|BindingFlags.Static); } - return (Task)runAsyncVoidMethod.Invoke(null, new object[] { body, cancellationToken }); - } - public static Task RunAsync(Func body) - { - return RunAsync(body, CancellationToken.None); + var genericRunAsyncMethod = runAsyncMethod.MakeGenericMethod(typeof(T)); + return (Task)genericRunAsyncMethod.Invoke(null, new object[] { body, cancellationToken }); } - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.WebWorker", "System.Runtime.InteropServices.JavaScript")] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "work in progress")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "work in progress")] - public static Task RunAsync(Action body, CancellationToken cancellationToken) + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern", Justification = "work in progress")] + public static Task RunAsync(Func body, CancellationToken cancellationToken) { - if(runMethod == null) + if(runAsyncVoidMethod == null) { - var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.WebWorker"); - runMethod = webWorker.GetMethod("Run", BindingFlags.Public|BindingFlags.Static); + var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker"); + runAsyncVoidMethod = webWorker.GetMethod("RunAsyncVoid", BindingFlags.NonPublic|BindingFlags.Static); } - return (Task)runMethod.Invoke(null, new object[] { body, cancellationToken }); - } - - public static Task RunAsync(Action body) - { - return RunAsync(body, CancellationToken.None); + return (Task)runAsyncVoidMethod.Invoke(null, new object[] { body, cancellationToken }); } } } \ No newline at end of file diff --git a/src/mono/sample/wasm/browser-threads-minimal/Program.cs b/src/mono/sample/wasm/browser-threads-minimal/Program.cs index 61cf37708ff83..11e4d01ff6141 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/Program.cs +++ b/src/mono/sample/wasm/browser-threads-minimal/Program.cs @@ -111,11 +111,12 @@ await Task.Run(() => internal static Task TestHelloWebWorker() { Console.WriteLine($"smoke: TestHelloWebWorker 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - Task t = WebWorker.RunAsync(() => + Task t = JSWebWorker.RunAsync(() => { Console.WriteLine($"smoke: TestHelloWebWorker 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); GlobalThisConsoleLog($"smoke: TestHelloWebWorker 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); Console.WriteLine($"smoke: TestHelloWebWorker 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); + return Task.CompletedTask; }); Console.WriteLine($"smoke: TestHelloWebWorker 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); return t.ContinueWith(Gogo); @@ -154,7 +155,7 @@ public static async Task TestCanStartThread() internal static void StartTimerFromWorker() { Console.WriteLine("smoke: StartTimerFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime()); - WebWorker.RunAsync(async () => + JSWebWorker.RunAsync(async () => { while (!_timerDone) { @@ -169,7 +170,7 @@ internal static void StartTimerFromWorker() internal static void StartAllocatorFromWorker() { Console.WriteLine("smoke: StartAllocatorFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime()); - WebWorker.RunAsync(async () => + JSWebWorker.RunAsync(async () => { while (!_timerDone) { @@ -196,7 +197,7 @@ internal static void StopTimerFromWorker() [JSExport] public static async Task TestCallSetTimeoutOnWorker() { - await WebWorker.RunAsync(() => TimeOutThenComplete()); + await JSWebWorker.RunAsync(() => TimeOutThenComplete()); Console.WriteLine($"XYZ: Main Thread caught task tid:{Thread.CurrentThread.ManagedThreadId}"); } @@ -220,7 +221,7 @@ public static Task HttpClientMain(string url) [JSExport] public static Task HttpClientWorker(string url) { - return WebWorker.RunAsync(() => + return JSWebWorker.RunAsync(() => { return HttpClientGet("HttpClientWorker", url); }); @@ -270,7 +271,7 @@ public static Task WsClientMain(string url) public static async Task FetchBackground(string url) { Console.WriteLine($"smoke: FetchBackground 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - var t = WebWorker.RunAsync(async () => + var t = JSWebWorker.RunAsync(async () => { var ctx = SynchronizationContext.Current; @@ -310,7 +311,7 @@ public static async Task TestTLS() { Console.WriteLine($"smoke {meaning}: TestTLS 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); meaning = 40; - await WebWorker.RunAsync(async () => + await JSWebWorker.RunAsync(async () => { Console.WriteLine($"smoke {meaning}: TestTLS 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); meaning = 41; diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index 5aa5bb7b55c73..6a39c840002c8 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -9,8 +9,6 @@ library true true - - <_WasmPThreadPoolSize Condition="'$(MonoWasmBuildVariant)' == 'multithread'">10 diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 03e43305bf417..75438c50424bb 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -17,22 +17,20 @@ var methodIndexByName = undefined; var gitHash = undefined; function setup(linkerSetup) { - const pthreadReplacements = {}; + // USE_PTHREADS is emscripten's define symbol, which is passed to acorn optimizer, so we could use it here + #if USE_PTHREADS + const modulePThread = PThread; + #else + const modulePThread = {}; + const ENVIRONMENT_IS_PTHREAD = false; + #endif const dotnet_replacements = { fetch: globalThis.fetch, ENVIRONMENT_IS_WORKER, require, - pthreadReplacements, + modulePThread, scriptDirectory, }; - // USE_PTHREADS is emscripten's define symbol, which is passed to acorn optimizer, so we could use it here - #if USE_PTHREADS - pthreadReplacements.loadWasmModuleToWorker = PThread.loadWasmModuleToWorker; - pthreadReplacements.threadInitTLS = PThread.threadInitTLS; - pthreadReplacements.allocateUnusedWorker = PThread.allocateUnusedWorker; - #else - const ENVIRONMENT_IS_PTHREAD = false; - #endif ENVIRONMENT_IS_WORKER = dotnet_replacements.ENVIRONMENT_IS_WORKER; Module.__dotnet_runtime.initializeReplacements(dotnet_replacements); @@ -40,11 +38,6 @@ function setup(linkerSetup) { fetch = dotnet_replacements.fetch; require = dotnet_replacements.require; _scriptDir = __dirname = scriptDirectory = dotnet_replacements.scriptDirectory; - #if USE_PTHREADS - PThread.loadWasmModuleToWorker = pthreadReplacements.loadWasmModuleToWorker; - PThread.threadInitTLS = pthreadReplacements.threadInitTLS; - PThread.allocateUnusedWorker = pthreadReplacements.allocateUnusedWorker; - #endif Module.__dotnet_runtime.passEmscriptenInternals({ isPThread: ENVIRONMENT_IS_PTHREAD, quit_, ExitStatus, diff --git a/src/mono/wasm/runtime/exports-internal.ts b/src/mono/wasm/runtime/exports-internal.ts index 13c7ec160d1b0..f81a7b95a5b07 100644 --- a/src/mono/wasm/runtime/exports-internal.ts +++ b/src/mono/wasm/runtime/exports-internal.ts @@ -20,6 +20,7 @@ import { forceDisposeProxies } from "./gc-handles"; import { mono_wasm_get_func_id_to_name_mappings } from "./logging"; import { MonoObject, MonoObjectNull } from "./types/internal"; import { monoStringToStringUnsafe } from "./strings"; +import { thread_available } from "./pthreads/browser"; export function export_internal(): any { return { @@ -56,6 +57,7 @@ export function export_internal(): any { get_global_this, get_dotnet_instance: () => exportedRuntimeAPI, dynamic_import, + thread_available, // BrowserWebSocket mono_wasm_cancel_promise, diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 59ce4a5f0f0f9..d62a20b503942 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -190,7 +190,7 @@ export function normalizeConfig() { if (MonoWasmThreads && !Number.isInteger(config.pthreadPoolSize)) { // ActiveIssue https://github.com/dotnet/runtime/issues/91538 - config.pthreadPoolSize = 40; + config.pthreadPoolSize = 5; } // Default values (when WasmDebugLevel is not set) diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts index ef87e1e90c5fd..0a2a075cd7bc6 100644 --- a/src/mono/wasm/runtime/polyfills.ts +++ b/src/mono/wasm/runtime/polyfills.ts @@ -33,10 +33,8 @@ export function initializeReplacements(replacements: EmscriptenReplacements): vo replacements.ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_WORKER; // threads - if (MonoWasmThreads) { - if (replacements.pthreadReplacements) { - replaceEmscriptenPThreadLibrary(replacements.pthreadReplacements); - } + if (MonoWasmThreads && replacements.modulePThread) { + replaceEmscriptenPThreadLibrary(replacements.modulePThread); } } diff --git a/src/mono/wasm/runtime/pthreads/browser/index.ts b/src/mono/wasm/runtime/pthreads/browser/index.ts index 89c84baf78836..4b0f3153db44a 100644 --- a/src/mono/wasm/runtime/pthreads/browser/index.ts +++ b/src/mono/wasm/runtime/pthreads/browser/index.ts @@ -1,12 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { isMonoWorkerMessageChannelCreated, monoSymbol, makeMonoThreadMessageApplyMonoConfig, isMonoWorkerMessagePreload, MonoWorkerMessage } from "../shared"; +import MonoWasmThreads from "consts:monoWasmThreads"; + +import { monoSymbol, makeMonoThreadMessageApplyMonoConfig, isMonoWorkerMessagePreload, MonoWorkerMessage, isMonoWorkerMessageEnabledInterop, isMonoWorkerMessageChannelCreated } from "../shared"; import { pthreadPtr } from "../shared/types"; import { MonoThreadMessage } from "../shared"; -import Internals from "../shared/emscripten-internals"; +import { Internals, PThreadWorker } from "../shared/emscripten-internals"; import { createPromiseController, runtimeHelpers } from "../../globals"; -import { PromiseController } from "../../types/internal"; +import { PromiseAndController, PromiseController } from "../../types/internal"; import { mono_log_debug } from "../../logging"; const threads: Map = new Map(); @@ -29,6 +31,8 @@ const threadPromises: Map[]> = new Map(); /// wait until the thread with the given id has set up a message port to the runtime export function waitForThread(pthreadPtr: pthreadPtr): Promise { + if (!MonoWasmThreads) return null as any; + if (threads.has(pthreadPtr)) { return Promise.resolve(threads.get(pthreadPtr)!); } @@ -43,6 +47,7 @@ export function waitForThread(pthreadPtr: pthreadPtr): Promise { } function resolvePromises(pthreadPtr: pthreadPtr, thread: Thread): void { + if (!MonoWasmThreads) return; const arr = threadPromises.get(pthreadPtr); if (arr !== undefined) { arr.forEach((controller) => controller.resolve(thread)); @@ -51,6 +56,7 @@ function resolvePromises(pthreadPtr: pthreadPtr, thread: Thread): void { } function addThread(pthreadPtr: pthreadPtr, worker: Worker, port: MessagePort): Thread { + if (!MonoWasmThreads) return null as any; const thread = new ThreadImpl(pthreadPtr, worker, port); threads.set(pthreadPtr, thread); return thread; @@ -62,6 +68,7 @@ function removeThread(pthreadPtr: pthreadPtr): void { /// Given a thread id, return the thread object with the worker where the thread is running, and a message port. export function getThread(pthreadPtr: pthreadPtr): Thread | undefined { + if (!MonoWasmThreads) return null as any; const thread = threads.get(pthreadPtr); if (thread === undefined) { return undefined; @@ -85,7 +92,8 @@ function monoDedicatedChannelMessageFromWorkerToMain(event: MessageEvent>): void { +function monoWorkerMessageHandler(worker: Worker, ev: MessageEvent): void { + if (!MonoWasmThreads) return; /// N.B. important to ignore messages we don't recognize - Emscripten uses the message event to send internal messages const data = ev.data; if (isMonoWorkerMessagePreload(data)) { @@ -93,7 +101,7 @@ function monoWorkerMessageHandler(worker: Worker, ev: MessageEvent | undefined; + /// Called by Emscripten internals on the browser thread when a new pthread worker is created and added to the pthread worker pool. /// At this point the worker doesn't have any pthread assigned to it, yet. -export function afterLoadWasmModuleToWorker(worker: Worker): void { +export function onWorkerLoadInitiated(worker: Worker, loaded: Promise): void { + if (!MonoWasmThreads) return; worker.addEventListener("message", (ev) => monoWorkerMessageHandler(worker, ev)); - mono_log_debug("afterLoadWasmModuleToWorker added message event handler", worker); + if (pendingWorkerLoad == undefined) { + pendingWorkerLoad = createPromiseController(); + } + loaded.then(() => { + if (pendingWorkerLoad != undefined) { + pendingWorkerLoad.promise_control.resolve(); + pendingWorkerLoad = undefined; + } + }); +} + +export function thread_available(): Promise { + if (!MonoWasmThreads) return null as any; + if (pendingWorkerLoad == undefined) { + return Promise.resolve(); + } + return pendingWorkerLoad.promise; } /// We call on the main thread this during startup to pre-allocate a pool of pthread workers. /// At this point asset resolution needs to be working (ie we loaded MonoConfig). /// This is used instead of the Emscripten PThread.initMainThread because we call it later. export function preAllocatePThreadWorkerPool(pthreadPoolSize: number): void { + if (!MonoWasmThreads) return; for (let i = 0; i < pthreadPoolSize; i++) { Internals.allocateUnusedWorker(); } @@ -125,6 +158,7 @@ export function preAllocatePThreadWorkerPool(pthreadPoolSize: number): void { /// This is used instead of the Emscripten "receiveInstance" in "createWasm" because that code is /// conditioned on a non-zero PTHREAD_POOL_SIZE (but we set it to 0 to avoid early worker allocation). export async function instantiateWasmPThreadWorkerPool(): Promise { + if (!MonoWasmThreads) return null as any; // this is largely copied from emscripten's "receiveInstance" in "createWasm" in "src/preamble.js" const workers = Internals.getUnusedWorkerPool(); if (workers.length > 0) { diff --git a/src/mono/wasm/runtime/pthreads/shared/emscripten-internals.ts b/src/mono/wasm/runtime/pthreads/shared/emscripten-internals.ts index 0b71fcecb988b..a3b7c87772906 100644 --- a/src/mono/wasm/runtime/pthreads/shared/emscripten-internals.ts +++ b/src/mono/wasm/runtime/pthreads/shared/emscripten-internals.ts @@ -3,6 +3,7 @@ import { Module } from "../../globals"; import { pthreadPtr } from "./types"; +import MonoWasmThreads from "consts:monoWasmThreads"; /** @module emscripten-internals accessors to the functions in the emscripten PThreads library, including * the low-level representations of {@linkcode pthreadPtr} thread info structs, etc. @@ -12,11 +13,14 @@ import { pthreadPtr } from "./types"; */ // This is what we know about the Emscripten PThread library -interface PThreadLibrary { - unusedWorkers: Worker[]; +export interface PThreadLibrary { + unusedWorkers: PThreadWorker[]; pthreads: PThreadInfoMap; allocateUnusedWorker: () => void; loadWasmModuleToWorker: (worker: Worker) => Promise; + threadInitTLS: () => void, + getNewWorker: () => PThreadWorker, + returnWorkerToPool: (worker: PThreadWorker) => void, } interface EmscriptenPThreadInfo { @@ -24,8 +28,10 @@ interface EmscriptenPThreadInfo { } /// N.B. emscripten deletes the `pthread` property from the worker when it is not actively running a pthread -interface PThreadWorker extends Worker { +export interface PThreadWorker extends Worker { pthread: EmscriptenPThreadInfo; + loaded: boolean; + interopInstalled: boolean; } interface PThreadObject { @@ -42,13 +48,12 @@ function isRunningPThreadWorker(w: Worker): w is PThreadWorker { } /// These utility functions dig into Emscripten internals -const Internals = { +export const Internals = !MonoWasmThreads ? null as any : { get modulePThread(): PThreadLibrary { return (Module).PThread as PThreadLibrary; }, getWorker: (pthreadPtr: pthreadPtr): PThreadWorker | undefined => { - // see https://github.com/emscripten-core/emscripten/pull/16239 - return Internals.modulePThread.pthreads[pthreadPtr]?.worker; + return Internals.modulePThread.pthreads[pthreadPtr]; }, getThreadId: (worker: Worker): pthreadPtr | undefined => { /// See library_pthread.js in Emscripten. @@ -71,6 +76,3 @@ const Internals = { return Internals.modulePThread.loadWasmModuleToWorker(worker); } }; - - -export default Internals; diff --git a/src/mono/wasm/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/wasm/runtime/pthreads/shared/emscripten-replacements.ts index 9b5b9fa0b388b..8fec3b10faf8a 100644 --- a/src/mono/wasm/runtime/pthreads/shared/emscripten-replacements.ts +++ b/src/mono/wasm/runtime/pthreads/shared/emscripten-replacements.ts @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. import MonoWasmThreads from "consts:monoWasmThreads"; -import { afterLoadWasmModuleToWorker } from "../browser"; + +import { onWorkerLoadInitiated } from "../browser"; import { afterThreadInitTLS } from "../worker"; -import Internals from "./emscripten-internals"; +import { Internals, PThreadLibrary, PThreadWorker } from "./emscripten-internals"; import { loaderHelpers, mono_assert } from "../../globals"; -import { PThreadReplacements } from "../../types/internal"; import { mono_log_debug } from "../../logging"; /** @module emscripten-replacements Replacements for individual functions in the emscripten PThreads library. @@ -14,30 +14,77 @@ import { mono_log_debug } from "../../logging"; * {@linkcode file://./../../../emsdk/upstream/emscripten/src/library_pthread.js} */ -export function replaceEmscriptenPThreadLibrary(replacements: PThreadReplacements): void { - if (MonoWasmThreads) { - const originalLoadWasmModuleToWorker = replacements.loadWasmModuleToWorker; - replacements.loadWasmModuleToWorker = (worker: Worker): Promise => { - const p = originalLoadWasmModuleToWorker(worker); - afterLoadWasmModuleToWorker(worker); - return p; - }; - const originalThreadInitTLS = replacements.threadInitTLS; - replacements.threadInitTLS = (): void => { - originalThreadInitTLS(); - afterThreadInitTLS(); - }; - // const originalAllocateUnusedWorker = replacements.allocateUnusedWorker; - replacements.allocateUnusedWorker = replacementAllocateUnusedWorker; +export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): void { + if (!MonoWasmThreads) return; + + const originalLoadWasmModuleToWorker = modulePThread.loadWasmModuleToWorker; + const originalThreadInitTLS = modulePThread.threadInitTLS; + const originalReturnWorkerToPool = modulePThread.returnWorkerToPool; + + modulePThread.loadWasmModuleToWorker = (worker: Worker): Promise => { + const afterLoaded = originalLoadWasmModuleToWorker(worker); + onWorkerLoadInitiated(worker, afterLoaded); + return afterLoaded; + }; + modulePThread.threadInitTLS = (): void => { + originalThreadInitTLS(); + afterThreadInitTLS(); + }; + modulePThread.allocateUnusedWorker = allocateUnusedWorker; + modulePThread.getNewWorker = () => getNewWorker(modulePThread); + modulePThread.returnWorkerToPool = (worker: PThreadWorker) => { + // when JS interop is installed on JSWebWorker + // we can't reuse the worker, because user code could leave the worker JS globals in a dirty state + if (worker.interopInstalled) { + // we are on UI thread, invoke the handler directly to destroy the dirty worker + worker.onmessage!(new MessageEvent("message", { + data: { + "cmd": "killThread", + thread: worker.pthread + } + })); + } else { + originalReturnWorkerToPool(worker); + } + }; +} + +function getNewWorker(modulePThread: PThreadLibrary): PThreadWorker { + if (!MonoWasmThreads) return null as any; + + if (modulePThread.unusedWorkers.length == 0) { + const worker = allocateUnusedWorker(); + modulePThread.loadWasmModuleToWorker(worker); + return worker; } + + // keep them pre-allocated all the time, not just during startup + if (loaderHelpers.config.pthreadPoolSize && modulePThread.unusedWorkers.length <= loaderHelpers.config.pthreadPoolSize) { + const worker = allocateUnusedWorker(); + modulePThread.loadWasmModuleToWorker(worker); + } + + for (let i = 0; i < modulePThread.unusedWorkers.length; i++) { + const worker = modulePThread.unusedWorkers[i]; + if (worker.loaded) { + modulePThread.unusedWorkers.splice(i, 1); + return worker; + } + } + mono_log_debug("Failed to find loaded WebWorker, this may deadlock. Please increase the pthreadPoolSize."); + return modulePThread.unusedWorkers.pop()!; } /// We replace Module["PThreads"].allocateUnusedWorker with this version that knows about assets -function replacementAllocateUnusedWorker(): void { - mono_log_debug("replacementAllocateUnusedWorker"); +function allocateUnusedWorker(): PThreadWorker { + if (!MonoWasmThreads) return null as any; + const asset = loaderHelpers.resolve_single_asset_path("js-module-threads"); const uri = asset.resolvedUrl; mono_assert(uri !== undefined, "could not resolve the uri for the js-module-threads asset"); - const worker = new Worker(uri); + const worker = new Worker(uri) as PThreadWorker; Internals.getUnusedWorkerPool().push(worker); + worker.loaded = false; + worker.interopInstalled = false; + return worker; } diff --git a/src/mono/wasm/runtime/pthreads/shared/index.ts b/src/mono/wasm/runtime/pthreads/shared/index.ts index 64a90c8567d7c..af991aa7b3fe3 100644 --- a/src/mono/wasm/runtime/pthreads/shared/index.ts +++ b/src/mono/wasm/runtime/pthreads/shared/index.ts @@ -4,7 +4,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import BuildConfiguration from "consts:configuration"; -import { Module, mono_assert, runtimeHelpers } from "../../globals"; +import { ENVIRONMENT_IS_PTHREAD, Module, mono_assert, runtimeHelpers } from "../../globals"; import { MonoConfig } from "../../types"; import { pthreadPtr } from "./types"; import { mono_log_debug } from "../../logging"; @@ -33,6 +33,7 @@ export function getBrowserThreadID(): pthreadPtr { } const enum WorkerMonoCommandType { + enabledInterop = "notify_enabled_interop", channelCreated = "channel_created", preload = "preload", } @@ -79,30 +80,41 @@ export const monoSymbol = "__mono_message_please_dont_collide__"; //Symbol("mono /// Messages sent from the main thread using Worker.postMessage or from the worker using DedicatedWorkerGlobalScope.postMessage /// should use this interface. The message event is also used by emscripten internals (and possibly by 3rd party libraries targeting Emscripten). /// We should just use this to establish a dedicated MessagePort for Mono's uses. -export interface MonoWorkerMessage { +export interface MonoWorkerMessage { [monoSymbol]: { monoCmd: WorkerMonoCommandType; - port: TPort; + }; +} +export type MonoWorkerMessagePort = MonoWorkerMessage & { + [monoSymbol]: { + port: MessagePort; }; } /// The message sent early during pthread creation to set up a dedicated MessagePort for Mono between the main thread and the pthread. -export interface MonoWorkerMessageChannelCreated extends MonoWorkerMessage { +export interface MonoWorkerMessageChannelCreated extends MonoWorkerMessage { [monoSymbol]: { monoCmd: WorkerMonoCommandType.channelCreated; threadId: pthreadPtr; - port: TPort; + port: MessagePort; + }; +} + +export interface MonoWorkerMessageEnabledInterop extends MonoWorkerMessage { + [monoSymbol]: { + monoCmd: WorkerMonoCommandType.enabledInterop; + threadId: pthreadPtr; }; } -export interface MonoWorkerMessagePreload extends MonoWorkerMessage { +export interface MonoWorkerMessagePreload extends MonoWorkerMessagePort { [monoSymbol]: { monoCmd: WorkerMonoCommandType.preload; - port: TPort; + port: MessagePort; }; } -export function makeChannelCreatedMonoMessage(threadId: pthreadPtr, port: TPort): MonoWorkerMessageChannelCreated { +export function makeChannelCreatedMonoMessage(threadId: pthreadPtr, port: MessagePort): MonoWorkerMessageChannelCreated { return { [monoSymbol]: { monoCmd: WorkerMonoCommandType.channelCreated, @@ -111,12 +123,20 @@ export function makeChannelCreatedMonoMessage(threadId: pthreadPtr, port: } }; } +export function makeEnabledInteropMonoMessage(threadId: pthreadPtr): MonoWorkerMessageEnabledInterop { + return { + [monoSymbol]: { + monoCmd: WorkerMonoCommandType.enabledInterop, + threadId: threadId, + } + }; +} -export function isMonoWorkerMessage(message: unknown): message is MonoWorkerMessage { +export function isMonoWorkerMessage(message: unknown): message is MonoWorkerMessage { return message !== undefined && typeof message === "object" && message !== null && monoSymbol in message; } -export function isMonoWorkerMessageChannelCreated(message: MonoWorkerMessage): message is MonoWorkerMessageChannelCreated { +export function isMonoWorkerMessageChannelCreated(message: MonoWorkerMessage): message is MonoWorkerMessageChannelCreated { if (isMonoWorkerMessage(message)) { const monoMessage = message[monoSymbol]; if (monoMessage.monoCmd === WorkerMonoCommandType.channelCreated) { @@ -126,7 +146,17 @@ export function isMonoWorkerMessageChannelCreated(message: MonoWorkerMess return false; } -export function isMonoWorkerMessagePreload(message: MonoWorkerMessage): message is MonoWorkerMessagePreload { +export function isMonoWorkerMessageEnabledInterop(message: MonoWorkerMessage): message is MonoWorkerMessageEnabledInterop { + if (isMonoWorkerMessage(message)) { + const monoMessage = message[monoSymbol]; + if (monoMessage.monoCmd === WorkerMonoCommandType.enabledInterop) { + return true; + } + } + return false; +} + +export function isMonoWorkerMessagePreload(message: MonoWorkerMessage): message is MonoWorkerMessagePreload { if (isMonoWorkerMessage(message)) { const monoMessage = message[monoSymbol]; if (monoMessage.monoCmd === WorkerMonoCommandType.preload) { @@ -144,6 +174,9 @@ export function mono_wasm_install_js_worker_interop(): void { mono_log_debug("Installed JSSynchronizationContext"); } Module.runtimeKeepalivePush(); + if (ENVIRONMENT_IS_PTHREAD) { + self.postMessage(makeEnabledInteropMonoMessage(pthread_self.pthreadId), []); + } set_thread_info(pthread_self ? pthread_self.pthreadId : 0, true, true, true); } diff --git a/src/mono/wasm/runtime/pthreads/worker/index.ts b/src/mono/wasm/runtime/pthreads/worker/index.ts index 95641ac86a25f..b92b5bb8c3bbb 100644 --- a/src/mono/wasm/runtime/pthreads/worker/index.ts +++ b/src/mono/wasm/runtime/pthreads/worker/index.ts @@ -18,8 +18,7 @@ import { WorkerThreadEventTarget } from "./events"; import { postRunWorker, preRunWorker } from "../../startup"; -import { mono_log_debug } from "../../logging"; -import { mono_set_thread_id } from "../../logging"; +import { mono_log_debug, mono_set_thread_id } from "../../logging"; import { jiterpreter_allocate_tables } from "../../jiterpreter-support"; // re-export some of the events types @@ -71,7 +70,6 @@ function monoDedicatedChannelMessageFromMainToWorker(event: MessageEvent function setupChannelToMainThread(pthread_ptr: pthreadPtr): PThreadSelf { - mono_log_debug("creating a channel", pthread_ptr); const channel = new MessageChannel(); const workerPort = channel.port1; const mainPort = channel.port2; @@ -86,10 +84,10 @@ function setupChannelToMainThread(pthread_ptr: pthreadPtr): PThreadSelf { /// This is an implementation detail function. /// Called in the worker thread (not main thread) from mono when a pthread becomes attached to the mono runtime. export function mono_wasm_pthread_on_pthread_attached(pthread_id: number): void { + if (!MonoWasmThreads) return; const self = pthread_self; mono_assert(self !== null && self.pthreadId == pthread_id, "expected pthread_self to be set already when attaching"); mono_set_thread_id("0x" + pthread_id.toString(16)); - mono_log_debug("attaching pthread to mono runtime 0x" + pthread_id.toString(16)); preRunWorker(); set_thread_info(pthread_id, true, false, false); jiterpreter_allocate_tables(); @@ -98,7 +96,7 @@ export function mono_wasm_pthread_on_pthread_attached(pthread_id: number): void /// Called in the worker thread (not main thread) from mono when a pthread becomes detached from the mono runtime. export function mono_wasm_pthread_on_pthread_detached(pthread_id: number): void { - mono_log_debug("detaching pthread from mono runtime 0x" + pthread_id.toString(16)); + if (!MonoWasmThreads) return; postRunWorker(); set_thread_info(pthread_id, false, false, false); mono_set_thread_id(""); @@ -108,11 +106,11 @@ export function mono_wasm_pthread_on_pthread_detached(pthread_id: number): void /// 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; // don't do this callback for the main thread if (ENVIRONMENT_IS_PTHREAD) { const pthread_ptr = (Module)["_pthread_self"](); mono_assert(!is_nullish(pthread_ptr), "pthread_self() returned null"); - mono_log_debug("after thread init, pthread ptr 0x" + pthread_ptr.toString(16)); const self = setupChannelToMainThread(pthread_ptr); currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadCreated, self)); } diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 530e520d5186e..4c086350182a6 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { AssetBehaviors, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, RuntimeAPI } from "."; +import type { PThreadLibrary } from "../pthreads/shared/emscripten-internals"; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -306,18 +307,13 @@ export type GlobalObjects = { export type EmscriptenReplacements = { fetch: any, require: any, - pthreadReplacements: PThreadReplacements | undefined | null + modulePThread: PThreadLibrary | undefined | null scriptDirectory: string; ENVIRONMENT_IS_WORKER: boolean; } export interface ExitStatusError { new(status: number): any; } -export type PThreadReplacements = { - loadWasmModuleToWorker(worker: Worker): Promise, - threadInitTLS: () => void, - allocateUnusedWorker: () => void, -} /// Always throws. Used to handle unreachable switch branches when TypeScript refines the type of a variable /// to 'never' after you handle all the cases it knows about. diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index a92cfd1d0a6ab..78ee9bc3d739c 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -275,7 +275,7 @@ <_EmccCommonFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-s USE_PTHREADS=1" /> <_EmccLinkFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-Wno-pthreads-mem-growth" /> <_EmccLinkFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-s PTHREAD_POOL_SIZE=0" /> - <_EmccLinkFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-s PTHREAD_POOL_SIZE_STRICT=2" /> + <_EmccLinkFlags Condition="'$(MonoWasmThreads)' == 'true'" Include="-s PTHREAD_POOL_SIZE_STRICT=0" /> <_EmccLinkFlags Include="-s ALLOW_MEMORY_GROWTH=1" /> <_EmccLinkFlags Include="-s ALLOW_TABLE_GROWTH=1" />