Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[browser][mt] dynamic thread create #95702

Merged
merged 2 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<TestRuntime>true</TestRuntime>
<EnableDefaultItems>true</EnableDefaultItems>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
<_WasmPThreadPoolSize Condition="'$(MonoWasmBuildVariant)' == 'multithread'">100</_WasmPThreadPoolSize>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Runtime.InteropServices.JavaScript.WebWorker</Target>
<Target>T:System.Runtime.InteropServices.JavaScript.JSWebWorker</Target>
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
</Suppression>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
<WasmEnableJsInteropByValue Condition="'$(TargetPlatformIdentifier)' == 'browser' and '$(WasmEnableJsInteropByValue)' == '' and '$(FeatureWasmThreads)' == 'true'">true</WasmEnableJsInteropByValue>
<WasmEnableJsInteropByValue Condition="'$(TargetPlatformIdentifier)' == 'browser' and '$(WasmEnableJsInteropByValue)' == ''">false</WasmEnableJsInteropByValue>
<WasmEnableLegacyJsInterop Condition="'$(TargetPlatformIdentifier)' == 'browser' and '$(WasmEnableLegacyJsInterop)' == ''">true</WasmEnableLegacyJsInterop>
<DefineConstants Condition="'$(FeatureWasmThreads)' == 'true'" >$(DefineConstants);FEATURE_WASM_THREADS</DefineConstants>
<DefineConstants Condition="'$(WasmEnableLegacyJsInterop)' == 'false'" >$(DefineConstants);DISABLE_LEGACY_JS_INTEROP</DefineConstants>
<DefineConstants Condition="'$(WasmEnableJsInteropByValue)' == 'true'" >$(DefineConstants);ENABLE_JS_INTEROP_BY_VALUE</DefineConstants>
<DefineConstants Condition="'$(FeatureWasmThreads)' == 'true'">$(DefineConstants);FEATURE_WASM_THREADS</DefineConstants>
<DefineConstants Condition="'$(WasmEnableLegacyJsInterop)' == 'false'">$(DefineConstants);DISABLE_LEGACY_JS_INTEROP</DefineConstants>
<DefineConstants Condition="'$(WasmEnableJsInteropByValue)' == 'true'">$(DefineConstants);ENABLE_JS_INTEROP_BY_VALUE</DefineConstants>
<EmitCompilerGeneratedFiles Condition="'$(Configuration)' == 'Debug' and '$(TargetPlatformIdentifier)' == 'browser'">true</EmitCompilerGeneratedFiles>
</PropertyGroup>

Expand Down Expand Up @@ -81,7 +81,7 @@

<!-- only include threads support when FeatureWasmThreads is enabled -->
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'browser' and '$(FeatureWasmThreads)' == 'true'">
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorker.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSWebWorker.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSSynchronizationContext.cs" />
</ItemGroup>
<ItemGroup Condition="'$(MonoWasmBuildVariant)' == 'multithread'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ internal static unsafe partial class JavaScriptImports
public static partial JSObject GetDotnetInstance();
[JSImport("INTERNAL.dynamic_import")]
public static partial Task<JSObject> 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")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public static class WebWorker
public static class JSWebWorker
{
public static Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken)
// temporary, for easy reflection
internal static Task RunAsyncVoid(Func<Task> body, CancellationToken cancellationToken) => RunAsync(body, cancellationToken);
internal static Task<T> RunAsyncGeneric<T>(Func<Task<T>> body, CancellationToken cancellationToken) => RunAsync(body, cancellationToken);

public static Task<T> RunAsync<T>(Func<Task<T>> body)
{
return RunAsync(body, CancellationToken.None);
}

public static Task RunAsync(Func<Task> body)
{
return RunAsync(body, CancellationToken.None);
}

public static async Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken)
{
// TODO remove main thread condition later
if (Thread.CurrentThread.ManagedThreadId == 1) await JavaScriptImports.ThreadAvailable().ConfigureAwait(false);
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
return await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false);
}

public static async Task RunAsync(Func<Task> body, CancellationToken cancellationToken)
{
// TODO remove main thread condition later
if (Thread.CurrentThread.ManagedThreadId == 1) await JavaScriptImports.ThreadAvailable().ConfigureAwait(false);
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false);
}

private static Task<T> RunAsyncImpl<T>(Func<Task<T>> body, CancellationToken cancellationToken)
{
var parentContext = SynchronizationContext.Current ?? new SynchronizationContext();
var tcs = new TaskCompletionSource<T>();
Expand Down Expand Up @@ -53,7 +81,7 @@ public static Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancella
return tcs.Task;
}

public static Task RunAsyncVoid(Func<Task> body, CancellationToken cancellationToken)
private static Task RunAsyncImpl(Func<Task> body, CancellationToken cancellationToken)
{
var parentContext = SynchronizationContext.Current ?? new SynchronizationContext();
var tcs = new TaskCompletionSource();
Expand Down Expand Up @@ -90,44 +118,6 @@ public static Task RunAsyncVoid(Func<Task> 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)
Expand All @@ -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);
}
}

Expand All @@ -150,7 +140,7 @@ private static void PostWhenCancellation<T>(SynchronizationContext ctx, TaskComp
}
catch (Exception e)
{
Environment.FailFast("WebWorker.RunAsync failed", e);
Environment.FailFast("JSWebWorker.RunAsync failed", e);
}
}

Expand All @@ -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);
}
}

Expand All @@ -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);
}
}

Expand All @@ -201,7 +179,7 @@ private static void SendWhenException<T>(SynchronizationContext ctx, TaskComplet
}
catch (Exception e)
{
Environment.FailFast("WebWorker.RunAsync failed", e);
Environment.FailFast("JSWebWorker.RunAsync failed", e);
}
}

Expand All @@ -216,15 +194,15 @@ private static void SendWhenDone<T>(SynchronizationContext ctx, TaskCompletionSo
}
catch (Exception e)
{
Environment.FailFast("WebWorker.RunAsync failed", e);
Environment.FailFast("JSWebWorker.RunAsync failed", e);
}
}

internal static void PropagateCompletion<T>(TaskCompletionSource<T> tcs, Task<T> done)
{
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<TestRuntime>true</TestRuntime>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<_WasmPThreadPoolSize Condition="'$(MonoWasmBuildVariant)' == 'multithread'">64</_WasmPThreadPoolSize>
</PropertyGroup>
<ItemGroup>
<Compile Include="Helpers.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,70 +7,53 @@
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<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken)
public static Task RunAsync(Func<Task> 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<T>)genericRunAsyncMethod.Invoke(null, new object[] { body, cancellationToken });
return RunAsync(body, CancellationToken.None);
}

public static Task<T> RunAsync<T>(Func<Task<T>> 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<Task> body, CancellationToken cancellationToken)
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern", Justification = "work in progress")]
public static Task<T> RunAsync<T>(Func<Task<T>> 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<Task> body)
{
return RunAsync(body, CancellationToken.None);
var genericRunAsyncMethod = runAsyncMethod.MakeGenericMethod(typeof(T));
return (Task<T>)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<Task> 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 });
}
}
}
Loading
Loading