diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml
index 31d15946c50da..fc8d757233cd4 100644
--- a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml
+++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml
@@ -290,17 +290,18 @@ jobs:
# ff tests are unstable currently
shouldContinueOnError: true
- - template: /eng/pipelines/common/templates/wasm-debugger-tests.yml
- parameters:
- platforms:
- - Browser_wasm
- - Browser_wasm_win
- extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS)
- nameSuffix: DebuggerTests_MultiThreaded
- alwaysRun: ${{ parameters.isWasmOnlyBuild }}
- isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }}
- isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }}
- runOnlyOnWasmOnlyPipelines: true
+ # Active Issue https://github.com/dotnet/runtime/issues/98771
+ # - template: /eng/pipelines/common/templates/wasm-debugger-tests.yml
+ # parameters:
+ # platforms:
+ # - Browser_wasm
+ # - Browser_wasm_win
+ # extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS)
+ # nameSuffix: DebuggerTests_MultiThreaded
+ # alwaysRun: ${{ parameters.isWasmOnlyBuild }}
+ # isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }}
+ # isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }}
+ # runOnlyOnWasmOnlyPipelines: true
# Disable for now
#- template: /eng/pipelines/coreclr/perf-wasm-jobs.yml
diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets
index d27fa412d490b..06f07c898eabc 100644
--- a/eng/testing/tests.browser.targets
+++ b/eng/testing/tests.browser.targets
@@ -89,6 +89,7 @@
<_XUnitBackgroundExec Condition="'$(_XUnitBackgroundExec)' == '' and '$(WasmEnableThreads)' == 'true'">true
$(WasmTestAppArgs) -backgroundExec
+ $(WasmXHarnessMonoArgs) --setenv=IsWasmBackgroundExec=true
<_AppArgs Condition="'$(WasmTestAppArgs)' != ''">$(_AppArgs) $(WasmTestAppArgs)
$(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true
diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
index f66afe40b4661..53e3fc148a46e 100644
--- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
+++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
@@ -42,7 +42,7 @@ internal static unsafe partial class Runtime
#if FEATURE_WASM_MANAGED_THREADS
[MethodImpl(MethodImplOptions.InternalCall)]
- public static extern void InstallWebWorkerInterop(nint proxyContextGCHandle);
+ public static extern void InstallWebWorkerInterop(nint proxyContextGCHandle, void* beforeSyncJSImport, void* afterSyncJSImport);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void UninstallWebWorkerInterop();
diff --git a/src/libraries/Common/tests/System/TimeProviderTests.cs b/src/libraries/Common/tests/System/TimeProviderTests.cs
index 428c5b13fecc5..7a0cb33eb74d7 100644
--- a/src/libraries/Common/tests/System/TimeProviderTests.cs
+++ b/src/libraries/Common/tests/System/TimeProviderTests.cs
@@ -214,7 +214,7 @@ private static void CancelAfter(TimeProvider provider, CancellationTokenSource c
}
#endif // NETFRAMEWORK
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[MemberData(nameof(TimersProvidersListData))]
public static void CancellationTokenSourceWithTimer(TimeProvider provider)
{
diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
index 054a868c25f85..510b4b8e32180 100644
--- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
+++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
@@ -135,6 +135,9 @@ public static int SlowRuntimeTimeoutModifier
public static bool IsThreadingSupported => (!IsWasi && !IsBrowser) || IsWasmThreadingSupported;
public static bool IsWasmThreadingSupported => IsBrowser && IsEnvironmentVariableTrue("IsBrowserThreadingSupported");
public static bool IsNotWasmThreadingSupported => !IsWasmThreadingSupported;
+ public static bool IsWasmBackgroundExec => IsBrowser && IsEnvironmentVariableTrue("IsWasmBackgroundExec");
+ public static bool IsWasmBackgroundExecOrSingleThread => IsWasmBackgroundExec || IsNotWasmThreadingSupported;
+ public static bool IsThreadingSupportedOrBrowserBackgroundExec => IsWasmBackgroundExec || !IsBrowser;
public static bool IsBinaryFormatterSupported => IsNotMobile && !IsNativeAot;
public static bool IsStartingProcessesSupported => !IsiOS && !IstvOS;
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs
index 919dee6475909..74f85296af952 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs
@@ -792,7 +792,7 @@ public void CreateCallSite_EnumberableCachedAtLowestLevel(ServiceDescriptor[] de
Assert.Equal(typeof(IEnumerable), callSite.Cache.Key.ServiceIdentifier.ServiceType);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
public void CallSitesAreUniquePerServiceTypeAndSlot()
{
// Connected graph
@@ -828,7 +828,7 @@ public void CallSitesAreUniquePerServiceTypeAndSlot()
}
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
public void CallSitesAreUniquePerServiceTypeAndSlotWithOpenGenericInGraph()
{
// Connected graph
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs
index 3e08a16db282e..f668cee41efc3 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs
@@ -371,7 +371,7 @@ public void GetService_DisposeOnSameThread_Throws()
});
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
public void GetAsyncService_DisposeAsyncOnSameThread_ThrowsAndDoesNotHangAndDisposeAsyncGetsCalled()
{
// Arrange
@@ -398,7 +398,7 @@ public void GetAsyncService_DisposeAsyncOnSameThread_ThrowsAndDoesNotHangAndDisp
Assert.True(asyncDisposableResource.DisposeAsyncCalled);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
public void GetService_DisposeOnSameThread_ThrowsAndDoesNotHangAndDisposeGetsCalled()
{
// Arrange
diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs
index 3982ae2984686..f123b65bb58bc 100644
--- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs
+++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs
@@ -37,7 +37,7 @@ public void BuildWebHostPattern_CanFindServiceProvider()
Assert.IsAssignableFrom(factory(Array.Empty()));
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BuildWebHostInvalidSignature.Program))]
public void BuildWebHostPattern__Invalid_CantFindWebHost()
{
@@ -46,7 +46,7 @@ public void BuildWebHostPattern__Invalid_CantFindWebHost()
Assert.Null(factory);
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BuildWebHostInvalidSignature.Program))]
public void BuildWebHostPattern__Invalid_CantFindServiceProvider()
{
@@ -55,7 +55,7 @@ public void BuildWebHostPattern__Invalid_CantFindServiceProvider()
Assert.NotNull(factory);
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateWebHostBuilderPatternTestSite.Program))]
public void CreateWebHostBuilderPattern_CanFindWebHostBuilder()
{
@@ -65,7 +65,7 @@ public void CreateWebHostBuilderPattern_CanFindWebHostBuilder()
Assert.IsAssignableFrom(factory(Array.Empty()));
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateWebHostBuilderPatternTestSite.Program))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(IWebHost))]
public void CreateWebHostBuilderPattern_CanFindServiceProvider()
@@ -76,7 +76,7 @@ public void CreateWebHostBuilderPattern_CanFindServiceProvider()
Assert.IsAssignableFrom(factory(Array.Empty()));
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateWebHostBuilderInvalidSignature.Program))]
public void CreateWebHostBuilderPattern__Invalid_CantFindWebHostBuilder()
{
@@ -85,7 +85,7 @@ public void CreateWebHostBuilderPattern__Invalid_CantFindWebHostBuilder()
Assert.Null(factory);
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateWebHostBuilderInvalidSignature.Program))]
public void CreateWebHostBuilderPattern__InvalidReturnType_CanFindServiceProvider()
{
@@ -95,7 +95,7 @@ public void CreateWebHostBuilderPattern__InvalidReturnType_CanFindServiceProvide
Assert.Null(factory(Array.Empty()));
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateHostBuilderPatternTestSite.Program))]
public void CreateHostBuilderPattern_CanFindHostBuilder()
{
@@ -105,7 +105,7 @@ public void CreateHostBuilderPattern_CanFindHostBuilder()
Assert.IsAssignableFrom(factory(Array.Empty()));
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateHostBuilderPatternTestSite.Program))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Host))]
public void CreateHostBuilderPattern_CanFindServiceProvider()
@@ -116,7 +116,7 @@ public void CreateHostBuilderPattern_CanFindServiceProvider()
Assert.IsAssignableFrom(factory(Array.Empty()));
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateHostBuilderInvalidSignature.Program))]
public void CreateHostBuilderPattern__Invalid_CantFindHostBuilder()
{
@@ -125,7 +125,7 @@ public void CreateHostBuilderPattern__Invalid_CantFindHostBuilder()
Assert.Null(factory);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateHostBuilderInvalidSignature.Program))]
public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider()
{
@@ -135,7 +135,7 @@ public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider()
Assert.Throws(() => factory(Array.Empty()));
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))]
public void NoSpecialEntryPointPattern()
{
@@ -145,7 +145,7 @@ public void NoSpecialEntryPointPattern()
Assert.IsAssignableFrom(factory(Array.Empty()));
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))]
public void NoSpecialEntryPointPatternHostBuilderConfigureHostBuilderCallbackIsCalled()
{
@@ -163,7 +163,7 @@ void ConfigureHostBuilder(object hostBuilder)
Assert.True(called);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))]
public void NoSpecialEntryPointPatternBuildsThenThrowsCallsEntryPointCompletedCallback()
{
@@ -183,7 +183,7 @@ void EntryPointCompleted(Exception? exception)
Assert.Null(entryPointException);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternBuildsThenThrows.Program))]
public void NoSpecialEntryPointPatternBuildsThenThrowsCallsEntryPointCompletedCallbackWithException()
{
@@ -203,7 +203,7 @@ void EntryPointCompleted(Exception? exception)
Assert.NotNull(entryPointException);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternThrows.Program))]
public void NoSpecialEntryPointPatternThrows()
{
@@ -213,7 +213,7 @@ public void NoSpecialEntryPointPatternThrows()
Assert.Throws(() => factory(Array.Empty()));
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternExits.Program))]
public void NoSpecialEntryPointPatternExits()
{
@@ -223,7 +223,7 @@ public void NoSpecialEntryPointPatternExits()
Assert.Throws(() => factory(Array.Empty()));
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternHangs.Program))]
public void NoSpecialEntryPointPatternHangs()
{
@@ -233,7 +233,7 @@ public void NoSpecialEntryPointPatternHangs()
Assert.Throws(() => factory(Array.Empty()));
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternMainNoArgs.Program))]
public void NoSpecialEntryPointPatternMainNoArgs()
{
@@ -243,7 +243,7 @@ public void NoSpecialEntryPointPatternMainNoArgs()
Assert.IsAssignableFrom(factory(Array.Empty()));
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "Program", "TopLevelStatements")]
public void TopLevelStatements()
{
@@ -254,7 +254,7 @@ public void TopLevelStatements()
Assert.IsAssignableFrom(factory(Array.Empty()));
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "Program", "TopLevelStatementsTestsTimeout")]
public void TopLevelStatementsTestsTimeout()
{
@@ -265,7 +265,7 @@ public void TopLevelStatementsTestsTimeout()
Assert.Throws(() => factory(Array.Empty()));
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "Program", "ApplicationNameSetFromArgument")]
public void ApplicationNameSetFromArgument()
{
@@ -277,7 +277,7 @@ public void ApplicationNameSetFromArgument()
Assert.Contains("ApplicationNameSetFromArgument", configuration["applicationName"]);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))]
public void NoSpecialEntryPointPatternCanRunInParallel()
{
diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs
index c72b17e728848..81bb589fcfb31 100644
--- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs
+++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs
@@ -1203,7 +1203,7 @@ public async Task AddHttpClient_MessageHandler_Scope_TransientDependency()
}
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported), nameof(PlatformDetection.IsReflectionEmitSupported))]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec), nameof(PlatformDetection.IsReflectionEmitSupported))]
public void AddHttpClient_GetAwaiterAndResult_InSingleThreadedSynchronizationContext_ShouldNotHangs()
{
// Arrange
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 e083c09a41f6f..b423f9b585bed 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
@@ -13,7 +13,7 @@ namespace System.Runtime.InteropServices.JavaScript
{
// this maps to src\mono\browser\runtime\managed-exports.ts
// the public methods are protected from trimming by DynamicDependency on JSFunctionBinding.BindJSFunction
- // TODO: all the calls here should be running on deputy or TP in MT, not in UI thread
+ // TODO: change all of these to [UnmanagedCallersOnly] and drop the reflection in mono_wasm_invoke_jsexport
internal static unsafe partial class JavaScriptExports
{
// the marshaled signature is: Task? CallEntrypoint(char* assemblyNamePtr, string[] args)
@@ -240,15 +240,21 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)
// 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: GCHandle InstallMainSynchronizationContext(nint jsNativeTID)
+ // the marshaled signature is: GCHandle InstallMainSynchronizationContext(nint jsNativeTID, JSThreadBlockingMode jsThreadBlockingMode, JSThreadInteropMode jsThreadInteropMode, MainThreadingMode mainThreadingMode)
public static void InstallMainSynchronizationContext(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_res = ref arguments_buffer[1];// initialized and set by caller
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
+ ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];// initialized and set by caller
+ ref JSMarshalerArgument arg_3 = ref arguments_buffer[4];// initialized and set by caller
+ ref JSMarshalerArgument arg_4 = ref arguments_buffer[5];// initialized and set by caller
try
{
+ JSProxyContext.ThreadBlockingMode = (JSHostImplementation.JSThreadBlockingMode)arg_2.slot.Int32Value;
+ JSProxyContext.ThreadInteropMode = (JSHostImplementation.JSThreadInteropMode)arg_3.slot.Int32Value;
+ JSProxyContext.MainThreadingMode = (JSHostImplementation.MainThreadingMode)arg_4.slot.Int32Value;
var jsSynchronizationContext = JSSynchronizationContext.InstallWebWorkerInterop(true, CancellationToken.None);
jsSynchronizationContext.ProxyContext.JSNativeTID = arg_1.slot.IntPtrValue;
arg_res.slot.GCHandle = jsSynchronizationContext.ProxyContext.ContextHandle;
@@ -259,6 +265,42 @@ public static void InstallMainSynchronizationContext(JSMarshalerArgument* argume
}
}
+#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
+ [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
+#pragma warning restore CS3016
+ // TODO ideally this would be public API callable from generated C# code for JSExport
+ public static void BeforeSyncJSExport(JSMarshalerArgument* arguments_buffer)
+ {
+ ref JSMarshalerArgument arg_exc = ref arguments_buffer[0];
+ try
+ {
+ var ctx = arg_exc.AssertCurrentThreadContext();
+ ctx.IsPendingSynchronousCall = true;
+ }
+ catch (Exception ex)
+ {
+ Environment.FailFast($"BeforeSyncJSExport: Unexpected synchronous failure (ManagedThreadId {Environment.CurrentManagedThreadId}): " + ex);
+ }
+ }
+
+#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
+ [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
+#pragma warning restore CS3016
+ // TODO ideally this would be public API callable from generated C# code for JSExport
+ public static void AfterSyncJSExport(JSMarshalerArgument* arguments_buffer)
+ {
+ ref JSMarshalerArgument arg_exc = ref arguments_buffer[0];
+ try
+ {
+ var ctx = arg_exc.AssertCurrentThreadContext();
+ ctx.IsPendingSynchronousCall = false;
+ }
+ catch (Exception ex)
+ {
+ Environment.FailFast($"AfterSyncJSExport: Unexpected synchronous failure (ManagedThreadId {Environment.CurrentManagedThreadId}): " + ex);
+ }
+ }
+
#endif
// the marshaled signature is: Task BindAssemblyExports(string assemblyName)
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs
index 974b0ce7e3eb7..4f14a575fb1c0 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs
@@ -206,6 +206,9 @@ internal static unsafe void InvokeJSFunction(JSObject jsFunction, Span arguments)
{
+#if FEATURE_WASM_MANAGED_THREADS
+ if (JSProxyContext.ThreadInteropMode == JSHostImplementation.JSThreadInteropMode.NoSyncJSInterop)
+ {
+ throw new PlatformNotSupportedException("Cannot call synchronous JS functions.");
+ }
+ else if (jsFunction.ProxyContext.IsPendingSynchronousCall)
+ {
+ throw new PlatformNotSupportedException("Cannot call synchronous JS function from inside a synchronous call to a C# method.");
+ }
+#endif
+
var functionHandle = (int)jsFunction.JSHandle;
fixed (JSMarshalerArgument* ptr = arguments)
{
@@ -245,6 +259,16 @@ internal static unsafe void InvokeJSFunctionCurrent(JSObject jsFunction, Span arguments)
{
+#if FEATURE_WASM_MANAGED_THREADS
+ if (JSProxyContext.ThreadInteropMode == JSHostImplementation.JSThreadInteropMode.NoSyncJSInterop)
+ {
+ throw new PlatformNotSupportedException("Cannot call synchronous JS functions.");
+ }
+ else if (jsFunction.ProxyContext.IsPendingSynchronousCall)
+ {
+ throw new PlatformNotSupportedException("Cannot call synchronous JS function from inside a synchronous call to a C# method.");
+ }
+#endif
var args = (nint)Unsafe.AsPointer(ref arguments[0]);
var functionHandle = jsFunction.JSHandle;
@@ -269,10 +293,13 @@ internal static unsafe void DispatchJSFunctionSync(JSObject jsFunction, Span arguments)
{
+ ref JSMarshalerArgument exc = ref arguments[0];
+ ref JSMarshalerArgument res = ref arguments[1];
#if FEATURE_WASM_MANAGED_THREADS
var targetContext = JSProxyContext.SealJSImportCapturing();
- arguments[0].slot.ContextHandle = targetContext.ContextHandle;
- arguments[1].slot.ContextHandle = targetContext.ContextHandle;
+ exc.slot.CallerNativeTID = targetContext.NativeTID;
+ exc.slot.ContextHandle = targetContext.ContextHandle;
+ res.slot.ContextHandle = targetContext.ContextHandle;
#else
var targetContext = JSProxyContext.MainThreadContext;
#endif
@@ -281,9 +308,22 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span
{
// pre-allocate the result handle and Task
var holder = targetContext.CreatePromiseHolder();
- arguments[1].slot.Type = MarshalerType.TaskPreCreated;
- arguments[1].slot.GCHandle = holder.GCHandle;
+ res.slot.Type = MarshalerType.TaskPreCreated;
+ res.slot.GCHandle = holder.GCHandle;
}
+#if FEATURE_WASM_MANAGED_THREADS
+ else
+ {
+ if (JSProxyContext.ThreadInteropMode == JSHostImplementation.JSThreadInteropMode.NoSyncJSInterop)
+ {
+ throw new PlatformNotSupportedException("Cannot call synchronous JS functions.");
+ }
+ else if (targetContext.IsPendingSynchronousCall)
+ {
+ throw new PlatformNotSupportedException("Cannot call synchronous JS function from inside a synchronous call to a C# method.");
+ }
+ }
+#endif
if (signature.IsDiscardNoWait)
{
@@ -360,6 +400,8 @@ internal static unsafe void DispatchJSImportSyncSend(JSFunctionBinding signature
var args = (nint)Unsafe.AsPointer(ref arguments[0]);
var sig = (nint)signature.Header;
+ ref JSMarshalerArgument exc = ref arguments[0];
+
// we already know that we are not on the right thread
// this will be blocking until resolved by that thread
// we don't have to disable ThrowOnBlockingWaitOnJSInteropThread, because this is lock in native code
@@ -368,10 +410,9 @@ internal static unsafe void DispatchJSImportSyncSend(JSFunctionBinding signature
// see also https://github.com/dotnet/runtime/issues/76958#issuecomment-1921418290
Interop.Runtime.InvokeJSImportSyncSend(targetContext.JSNativeTID, sig, args);
- ref JSMarshalerArgument exceptionArg = ref arguments[0];
- if (exceptionArg.slot.Type != MarshalerType.None)
+ if (exc.slot.Type != MarshalerType.None)
{
- JSHostImplementation.ThrowException(ref exceptionArg);
+ JSHostImplementation.ThrowException(ref exc);
}
}
@@ -421,17 +462,19 @@ internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, s
#endif
internal static unsafe void ResolveOrRejectPromise(JSProxyContext targetContext, Span arguments)
{
+ ref JSMarshalerArgument exc = ref arguments[0];
#if FEATURE_WASM_MANAGED_THREADS
+ exc.slot.CallerNativeTID = targetContext.NativeTID;
+
if (targetContext.IsCurrentThread())
#endif
{
fixed (JSMarshalerArgument* ptr = arguments)
{
Interop.Runtime.ResolveOrRejectPromise((nint)ptr);
- ref JSMarshalerArgument exceptionArg = ref arguments[0];
- if (exceptionArg.slot.Type != MarshalerType.None)
+ if (exc.slot.Type != MarshalerType.None)
{
- JSHostImplementation.ThrowException(ref exceptionArg);
+ JSHostImplementation.ThrowException(ref exc);
}
}
}
@@ -439,7 +482,6 @@ internal static unsafe void ResolveOrRejectPromise(JSProxyContext targetContext,
else
{
// meaning JS side needs to dispose it
- ref JSMarshalerArgument exc = ref arguments[0];
exc.slot.ReceiverShouldFree = true;
// this copy is freed in mono_wasm_resolve_or_reject_promise
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs
index 2aa59d1814d45..c949901a803cf 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs
@@ -46,5 +46,38 @@ public struct IntPtrAndHandle
[FieldOffset(0)]
internal RuntimeTypeHandle typeHandle;
}
+
+ // keep in sync with types\internal.ts
+ public enum MainThreadingMode : int
+ {
+ // Running the managed main thread on UI thread.
+ // Managed GC and similar scenarios could be blocking the UI.
+ // Easy to deadlock. Not recommended for production.
+ UIThread = 0,
+ // Running the managed main thread on dedicated WebWorker. Marshaling all JavaScript calls to and from the main thread.
+ DeputyThread = 1,
+ }
+
+ // keep in sync with types\internal.ts
+ public enum JSThreadBlockingMode : int
+ {
+ // throw PlatformNotSupportedException if blocking .Wait is called on threads with JS interop, like JSWebWorker and Main thread.
+ // Avoids deadlocks (typically with pending JS promises on the same thread) by throwing exceptions.
+ NoBlockingWait = 0,
+ // allow .Wait on all threads.
+ // Could cause deadlocks with blocking .Wait on a pending JS Task/Promise on the same thread or similar Task/Promise chain.
+ AllowBlockingWait = 100,
+ }
+
+ // keep in sync with types\internal.ts
+ public enum JSThreadInteropMode : int
+ {
+ // throw PlatformNotSupportedException if synchronous JSImport/JSExport is called on threads with JS interop, like JSWebWorker and Main thread.
+ // calling synchronous JSImport on thread pool or new threads is allowed.
+ NoSyncJSInterop = 0,
+ // allow non-re-entrant synchronous blocking calls to and from JS on JSWebWorker on threads with JS interop, like JSWebWorker and Main thread.
+ // calling synchronous JSImport on thread pool or new threads is allowed.
+ SimpleSynchronousJSInterop = 1,
+ }
}
}
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs
index 9402aa5e8b80e..afd0325800dc4 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs
@@ -65,6 +65,9 @@ internal struct JSMarshalerArgumentImpl
[FieldOffset(20)]
internal bool ReceiverShouldFree;
+
+ [FieldOffset(24)]
+ internal IntPtr CallerNativeTID;
#endif
}
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs
index 7a86f173d11e1..7b481d24658a0 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs
@@ -41,12 +41,17 @@ private JSProxyContext()
public bool IsMainThread;
public JSSynchronizationContext SynchronizationContext;
+ public static MainThreadingMode MainThreadingMode = MainThreadingMode.DeputyThread;
+ public static JSThreadBlockingMode ThreadBlockingMode = JSThreadBlockingMode.NoBlockingWait;
+ public static JSThreadInteropMode ThreadInteropMode = JSThreadInteropMode.SimpleSynchronousJSInterop;
+ public bool IsPendingSynchronousCall;
+
#if !DEBUG
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public bool IsCurrentThread()
{
- return ManagedTID == Environment.CurrentManagedThreadId;
+ return ManagedTID == Environment.CurrentManagedThreadId && (!IsMainThread || MainThreadingMode == MainThreadingMode.UIThread);
}
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "thread_id")]
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs
index b9c151ac79d08..ed7fffbae25de 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs
@@ -44,28 +44,16 @@ public WorkItem(SendOrPostCallback callback, object? data, ManualResetEventSlim?
}
// this need to be called from JSWebWorker or UI thread
- public static JSSynchronizationContext InstallWebWorkerInterop(bool isMainThread, CancellationToken cancellationToken)
+ public static unsafe JSSynchronizationContext InstallWebWorkerInterop(bool isMainThread, CancellationToken cancellationToken)
{
var ctx = new JSSynchronizationContext(isMainThread, cancellationToken);
ctx.previousSynchronizationContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(ctx);
- // FIXME: make this configurable
- // we could have 3 different modes of this
- // 1) throwing on UI + JSWebWorker
- // 2) throwing only on UI - small risk, more convenient.
- // 3) not throwing at all - quite risky
- // deadlock scenarios are:
- // - .Wait for more than 5000ms and deadlock the GC suspend
- // - .Wait on the Task from HTTP client, on the same thread as the HTTP client needs to resolve the Task/Promise. This could be also be a chain of promises.
- // - try to create new pthread when UI thread is blocked and we run out of posix/emscripten pool of loaded workers.
- // Things which lead to it are
- // - Task.Wait, Signal.Wait etc
- // - Monitor.Enter etc, if the lock is held by another thread for long time
- // - synchronous [JSExport] into managed code, which would block
- // - synchronous [JSImport] to another thread, which would block
- // see also https://github.com/dotnet/runtime/issues/76958#issuecomment-1921418290
- Thread.ThrowOnBlockingWaitOnJSInteropThread = true;
+ if (JSProxyContext.ThreadBlockingMode == JSHostImplementation.JSThreadBlockingMode.NoBlockingWait)
+ {
+ Thread.ThrowOnBlockingWaitOnJSInteropThread = true;
+ }
var proxyContext = ctx.ProxyContext;
JSProxyContext.CurrentThreadContext = proxyContext;
@@ -77,7 +65,9 @@ public static JSSynchronizationContext InstallWebWorkerInterop(bool isMainThread
ctx.AwaitNewData();
- Interop.Runtime.InstallWebWorkerInterop(proxyContext.ContextHandle);
+ Interop.Runtime.InstallWebWorkerInterop(proxyContext.ContextHandle,
+ (delegate* unmanaged[Cdecl])&JavaScriptExports.BeforeSyncJSExport,
+ (delegate* unmanaged[Cdecl])&JavaScriptExports.AfterSyncJSExport);
return ctx;
}
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/BackgroundExec/System.Runtime.InteropServices.JavaScript.BackgroundExec.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/BackgroundExec/System.Runtime.InteropServices.JavaScript.BackgroundExec.Tests.csproj
new file mode 100644
index 0000000000000..506fcc01f008a
--- /dev/null
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/BackgroundExec/System.Runtime.InteropServices.JavaScript.BackgroundExec.Tests.csproj
@@ -0,0 +1,7 @@
+
+
+ <_XUnitBackgroundExec Condition="'$(_XUnitBackgroundExec)' == ''">true
+ System.Runtime.InteropServices.JavaScript.Tests
+
+
+
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs
index bcc2c85e9e785..8fe5e467eca06 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs
@@ -43,7 +43,7 @@ public async Task JsExportInt32DiscardNoWait(int value)
}
}
- //TODO [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsWasmBackgroundExecOrSingleThread))]
public class JSExportTest : JSInteropTestBase, IAsyncLifetime
{
[Theory]
@@ -383,7 +383,7 @@ public async Task JsExportTaskOfInt(int value)
//GC.Collect();
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsExportCallback_FunctionIntInt()
{
int called = -1;
@@ -399,7 +399,7 @@ public void JsExportCallback_FunctionIntInt()
Assert.Equal(42, called);
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsExportCallback_FunctionIntIntThrow()
{
int called = -1;
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs
index 96b89ffa9e4f4..89ec5cbb08978 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs
@@ -113,7 +113,7 @@ public unsafe void OutOfRange()
Assert.Contains("Overflow: value 9007199254740991 is out of -2147483648 2147483647 range", ex.Message);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWasmBackgroundExecOrSingleThread))]
public unsafe void OptimizedPaths()
{
JavaScriptTestHelper.optimizedReached = 0;
@@ -922,7 +922,7 @@ public async Task JsImportSleep()
await JavaScriptTestHelper.sleep(100);
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // slow
public async Task JsImportTaskTypes()
{
for (int i = 0; i < 100; i++)
@@ -1117,7 +1117,7 @@ public async Task JsImportTaskAwait()
#region Action
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportCallback_EchoAction()
{
bool called = false;
@@ -1132,7 +1132,6 @@ public void JsImportCallback_EchoAction()
Assert.True(called);
}
- /* TODO deputy
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWasmThreadingSupported))]
public void JsImportCallback_EchoActionThrows_MT()
{
@@ -1148,7 +1147,6 @@ public void JsImportCallback_EchoActionThrows_MT()
Assert.Throws(()=>actual());
Assert.False(called);
}
- */
[Fact]
public async Task JsImportCallback_Async()
@@ -1166,7 +1164,7 @@ public async Task JsImportCallback_Async()
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [Fact]
[OuterLoop]
public async Task JsImportCallback_EchoActionMany()
{
@@ -1187,7 +1185,7 @@ public async Task JsImportCallback_EchoActionMany()
}
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportCallback_Action()
{
bool called = false;
@@ -1198,7 +1196,7 @@ public void JsImportCallback_Action()
Assert.True(called);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportEcho_ActionAction()
{
bool called = false;
@@ -1211,7 +1209,7 @@ public void JsImportEcho_ActionAction()
Assert.True(called);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportEcho_ActionIntActionInt()
{
int calledA = -1;
@@ -1224,7 +1222,7 @@ public void JsImportEcho_ActionIntActionInt()
Assert.Equal(42, calledA);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportCallback_ActionInt()
{
int called = -1;
@@ -1235,7 +1233,7 @@ public void JsImportCallback_ActionInt()
Assert.Equal(42, called);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportCallback_FunctionIntInt()
{
int called = -1;
@@ -1248,7 +1246,7 @@ public void JsImportCallback_FunctionIntInt()
Assert.Equal(42, res);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportBackCallback_FunctionIntInt()
{
int called = -1;
@@ -1263,7 +1261,7 @@ public void JsImportBackCallback_FunctionIntInt()
Assert.Equal(84, called);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportBackCallback_FunctionIntIntIntInt()
{
int calledA = -1;
@@ -1282,7 +1280,7 @@ public void JsImportBackCallback_FunctionIntIntIntInt()
Assert.Equal(84, calledB);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportCallback_ActionIntInt()
{
int calledA = -1;
@@ -1296,7 +1294,7 @@ public void JsImportCallback_ActionIntInt()
Assert.Equal(43, calledB);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportCallback_ActionLongLong()
{
long calledA = -1;
@@ -1310,7 +1308,7 @@ public void JsImportCallback_ActionLongLong()
Assert.Equal(43, calledB);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportCallback_ActionIntLong()
{
int calledA = -1;
@@ -1324,7 +1322,7 @@ public void JsImportCallback_ActionIntLong()
Assert.Equal(43, calledB);
}
- [Fact] //TODO [Fact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] // this test doesn't make sense with deputy
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsImportCallback_ActionIntThrow()
{
int called = -1;
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 514741a6b8753..f0a4047924eaa 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
@@ -343,7 +343,7 @@ await executor.Execute(async () =>
var jsTid = WebWorkerTestHelper.GetTid();
var csTid = WebWorkerTestHelper.NativeThreadId;
- if (executor.Type == ExecutorType.Main || executor.Type == ExecutorType.JSWebWorker)
+ if (executor.Type == ExecutorType.JSWebWorker)
{
Assert.Equal(jsTid, csTid);
}
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 96312c7aba35c..35cdd8ff1858b 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
@@ -325,10 +325,6 @@ public static Task RunOnThreadPool(Func job, CancellationToken cancellatio
public static Task RunOnNewThread(Func job, CancellationToken cancellationToken)
{
- if( Environment.CurrentManagedThreadId == 1)
- {
- throw new Exception("This unit test should be executed with -backgroundExec otherwise it's prone to consume all threads too quickly");
- }
TaskCompletionSource tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var thread = new Thread(() =>
{
diff --git a/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/UnitTests.cs b/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/UnitTests.cs
index cc704c68fac13..d58a8838d90a9 100644
--- a/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/UnitTests.cs
+++ b/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/UnitTests.cs
@@ -272,7 +272,7 @@ public static void RentingReturningThenRentingABufferShouldNotAllocate()
Assert.Equal(id, bt.GetHashCode());
}
- [Theory]
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupportedOrBrowserBackgroundExec))]
[MemberData(nameof(BytePoolInstances))]
public static void CanRentManySizedBuffers(ArrayPool pool)
{
diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj
index 42afa2ece4fba..4ab8fa568e3f3 100644
--- a/src/libraries/tests.proj
+++ b/src/libraries/tests.proj
@@ -397,13 +397,15 @@
-
-
+
+
+
+
diff --git a/src/mono/browser/runtime/corebindings.c b/src/mono/browser/runtime/corebindings.c
index abaa7cab7b6f6..1b0c0a809f63e 100644
--- a/src/mono/browser/runtime/corebindings.c
+++ b/src/mono/browser/runtime/corebindings.c
@@ -42,6 +42,7 @@ void mono_wasm_resolve_or_reject_promise_post (pthread_t target_tid, void *args)
void mono_wasm_cancel_promise_post (pthread_t target_tid, int task_holder_gc_handle);
extern void mono_wasm_install_js_worker_interop (int context_gc_handle);
+void mono_wasm_install_js_worker_interop_wrapper (int context_gc_handle, void* beforeSyncJSImport, void* afterSyncJSImport);
extern void mono_wasm_uninstall_js_worker_interop ();
extern void mono_wasm_invoke_jsimport (void* signature, void* args);
void mono_wasm_invoke_jsimport_async_post (pthread_t target_tid, void* signature, void* args);
@@ -77,7 +78,7 @@ void bindings_initialize_internals (void)
#ifndef DISABLE_THREADS
mono_add_internal_call ("Interop/Runtime::ReleaseCSOwnedObjectPost", mono_wasm_release_cs_owned_object_post);
mono_add_internal_call ("Interop/Runtime::ResolveOrRejectPromisePost", mono_wasm_resolve_or_reject_promise_post);
- mono_add_internal_call ("Interop/Runtime::InstallWebWorkerInterop", mono_wasm_install_js_worker_interop);
+ mono_add_internal_call ("Interop/Runtime::InstallWebWorkerInterop", mono_wasm_install_js_worker_interop_wrapper);
mono_add_internal_call ("Interop/Runtime::UninstallWebWorkerInterop", mono_wasm_uninstall_js_worker_interop);
mono_add_internal_call ("Interop/Runtime::InvokeJSImportSync", mono_wasm_invoke_jsimport);
mono_add_internal_call ("Interop/Runtime::InvokeJSImportSyncSend", mono_wasm_invoke_jsimport_sync_send);
@@ -253,6 +254,16 @@ void mono_wasm_get_assembly_export (char *assembly_name, char *namespace, char *
#ifndef DISABLE_THREADS
+void* before_sync_js_import;
+void* after_sync_js_import;
+
+void mono_wasm_install_js_worker_interop_wrapper (int context_gc_handle, void* beforeSyncJSImport, void* afterSyncJSImport)
+{
+ before_sync_js_import = beforeSyncJSImport;
+ after_sync_js_import = afterSyncJSImport;
+ mono_wasm_install_js_worker_interop (context_gc_handle);
+}
+
// async
void mono_wasm_release_cs_owned_object_post (pthread_t target_tid, int js_handle)
{
diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts
index dbb0babce8231..23c0330d6eced 100644
--- a/src/mono/browser/runtime/cwraps.ts
+++ b/src/mono/browser/runtime/cwraps.ts
@@ -27,6 +27,8 @@ const threading_cwraps: SigLine[] = WasmEnableThreads ? [
[false, "mono_wasm_init_finalizer_thread", null, []],
[false, "mono_wasm_invoke_jsexport_async_post", "void", ["number", "number", "number"]],
[false, "mono_wasm_invoke_jsexport_sync_send", "void", ["number", "number", "number"]],
+ [true, "mono_wasm_create_deputy_thread", "number", []],
+ [true, "mono_wasm_register_ui_thread", "void", []],
] : [];
// when the method is assigned/cached at usage, instead of being invoked directly from cwraps, it can't be marked lazy, because it would be re-bound on each call
@@ -144,6 +146,8 @@ export interface t_ThreadingCwraps {
mono_wasm_init_finalizer_thread(): void;
mono_wasm_invoke_jsexport_async_post(targetTID: PThreadPtr, method: MonoMethod, args: VoidPtr): void;
mono_wasm_invoke_jsexport_sync_send(targetTID: PThreadPtr, method: MonoMethod, args: VoidPtr): void;
+ mono_wasm_create_deputy_thread(): PThreadPtr;
+ mono_wasm_register_ui_thread(): void;
}
export interface t_ProfilerCwraps {
diff --git a/src/mono/browser/runtime/driver.c b/src/mono/browser/runtime/driver.c
index c5135a7e44b34..d8a3da3f100ac 100644
--- a/src/mono/browser/runtime/driver.c
+++ b/src/mono/browser/runtime/driver.c
@@ -258,6 +258,7 @@ mono_wasm_invoke_jsexport (MonoMethod *method, void* args)
extern void mono_threads_wasm_async_run_in_target_thread_vii (void* target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2);
extern void mono_threads_wasm_sync_run_in_target_thread_vii (void* target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2);
+// this is running on the target thread
static void
mono_wasm_invoke_jsexport_async_post_cb (MonoMethod *method, void* args)
{
@@ -274,11 +275,25 @@ mono_wasm_invoke_jsexport_async_post (void* target_thread, MonoMethod *method, v
mono_threads_wasm_async_run_in_target_thread_vii(target_thread, (void (*)(gpointer, gpointer))mono_wasm_invoke_jsexport_async_post_cb, method, args);
}
+
+typedef void (*js_interop_event)(void* args);
+extern js_interop_event before_sync_js_import;
+extern js_interop_event after_sync_js_import;
+
+// this is running on the target thread
+static void
+mono_wasm_invoke_jsexport_sync_send_cb (MonoMethod *method, void* args)
+{
+ before_sync_js_import (args);
+ mono_wasm_invoke_jsexport (method, args);
+ after_sync_js_import (args);
+}
+
// sync
EMSCRIPTEN_KEEPALIVE void
mono_wasm_invoke_jsexport_sync_send (void* target_thread, MonoMethod *method, void* args /*JSMarshalerArguments*/)
{
- mono_threads_wasm_sync_run_in_target_thread_vii(target_thread, (void (*)(gpointer, gpointer))mono_wasm_invoke_jsexport, method, args);
+ mono_threads_wasm_sync_run_in_target_thread_vii(target_thread, (void (*)(gpointer, gpointer))mono_wasm_invoke_jsexport_sync_send_cb, method, args);
}
#endif /* DISABLE_THREADS */
diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts
index 6ab2b430ab048..3e65378483df3 100644
--- a/src/mono/browser/runtime/exports-binding.ts
+++ b/src/mono/browser/runtime/exports-binding.ts
@@ -10,7 +10,6 @@ import { mono_interp_tier_prepare_jiterpreter, mono_jiterp_free_method_data_js }
import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry } from "./jiterpreter-interp-entry";
import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue } from "./jiterpreter-jit-call";
import { mono_wasm_resolve_or_reject_promise } from "./marshal-to-js";
-import { mono_wasm_eventloop_has_unsettled_interop_promises } from "./pthreads";
import { mono_wasm_schedule_timer, schedule_background_exec } from "./scheduling";
import { mono_wasm_asm_loaded } from "./startup";
import { mono_wasm_diagnostic_server_on_server_thread_created } from "./diagnostics/server_pthread";
@@ -27,8 +26,12 @@ import { mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year } fro
import { mono_wasm_browser_entropy } from "./crypto";
import { mono_wasm_cancel_promise } from "./cancelable-promise";
-import { mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_unregistered, mono_wasm_pthread_on_pthread_registered, mono_wasm_pthread_set_name } from "./pthreads";
-import { mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop } from "./pthreads";
+import {
+ mono_wasm_eventloop_has_unsettled_interop_promises, mono_wasm_start_deputy_thread_async,
+ mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_unregistered,
+ mono_wasm_pthread_on_pthread_registered, mono_wasm_pthread_set_name, mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop
+} from "./pthreads";
+
// the JS methods would be visible to EMCC linker and become imports of the WASM module
@@ -38,6 +41,7 @@ export const mono_wasm_threads_imports = !WasmEnableThreads ? [] : [
mono_wasm_pthread_on_pthread_attached,
mono_wasm_pthread_on_pthread_unregistered,
mono_wasm_pthread_set_name,
+ mono_wasm_start_deputy_thread_async,
// threads.c
mono_wasm_eventloop_has_unsettled_interop_promises,
diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts
index 79ea1e29ab5df..e7ec6198fbf51 100644
--- a/src/mono/browser/runtime/interp-pgo.ts
+++ b/src/mono/browser/runtime/interp-pgo.ts
@@ -207,6 +207,9 @@ export async function getCacheKey(prefix: string): Promise {
delete inputs.enableDownloadRetry;
delete inputs.extensions;
delete inputs.runtimeId;
+ delete inputs.mainThreadingMode;
+ delete inputs.jsThreadBlockingMode;
+ delete inputs.jsThreadInteropMode;
inputs.GitHash = loaderHelpers.gitHash;
inputs.ProductVersion = ProductVersion;
diff --git a/src/mono/browser/runtime/invoke-js.ts b/src/mono/browser/runtime/invoke-js.ts
index d4c0459a0f1a3..b2a352cf5e94e 100644
--- a/src/mono/browser/runtime/invoke-js.ts
+++ b/src/mono/browser/runtime/invoke-js.ts
@@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads";
import BuildConfiguration from "consts:configuration";
import { marshal_exception_to_cs, bind_arg_marshal_to_cs } from "./marshal-to-cs";
-import { get_signature_argument_count, bound_js_function_symbol, get_sig, get_signature_version, get_signature_type, imported_js_function_symbol, get_signature_handle, get_signature_function_name, get_signature_module_name, is_receiver_should_free } from "./marshal";
+import { get_signature_argument_count, bound_js_function_symbol, get_sig, get_signature_version, get_signature_type, imported_js_function_symbol, get_signature_handle, get_signature_function_name, get_signature_module_name, is_receiver_should_free, get_caller_native_tid } from "./marshal";
import { setI32_unchecked, receiveWorkerHeapViews, forceThreadMemoryViewRefresh } from "./memory";
import { stringToMonoStringRoot } from "./strings";
import { MonoObject, MonoObjectRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal";
@@ -141,7 +141,8 @@ function bind_js_import(signature: JSFunctionSignature): Function {
const previous = runtimeHelpers.isPendingSynchronousCall;
try {
forceThreadMemoryViewRefresh();
- runtimeHelpers.isPendingSynchronousCall = true;
+ const caller_tid = get_caller_native_tid(args);
+ runtimeHelpers.isPendingSynchronousCall = runtimeHelpers.currentThreadTID === caller_tid;
bound_fn(args);
}
finally {
diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts
index 8aec355743721..e9ae9e1dd3e69 100644
--- a/src/mono/browser/runtime/loader/config.ts
+++ b/src/mono/browser/runtime/loader/config.ts
@@ -4,7 +4,7 @@
import BuildConfiguration from "consts:configuration";
import WasmEnableThreads from "consts:wasmEnableThreads";
-import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal";
+import { MainThreadingMode, type DotnetModuleInternal, type MonoConfigInternal, JSThreadBlockingMode, JSThreadInteropMode } from "../types/internal";
import type { DotnetModuleConfig, MonoConfig, ResourceGroups, ResourceList } from "../types";
import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals";
import { mono_log_error, mono_log_debug } from "./logging";
@@ -12,6 +12,7 @@ import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryI
import { mono_exit } from "./exit";
import { makeURLAbsoluteWithApplicationBase } from "./polyfills";
import { appendUniqueQuery } from "./assets";
+import { mono_log_warn } from "./logging";
export function deep_merge_config(target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal {
// no need to merge the same object
@@ -188,14 +189,46 @@ export function normalizeConfig() {
}
// ActiveIssue https://github.com/dotnet/runtime/issues/75602
- if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolInitialSize)) {
- config.pthreadPoolInitialSize = 7;
- }
- if (WasmEnableThreads && !Number.isInteger(config.pthreadPoolUnusedSize)) {
- config.pthreadPoolUnusedSize = 3;
- }
- if (WasmEnableThreads && !Number.isInteger(config.finalizerThreadStartDelayMs)) {
- config.finalizerThreadStartDelayMs = 200;
+ if (WasmEnableThreads) {
+
+ if (!Number.isInteger(config.pthreadPoolInitialSize)) {
+ config.pthreadPoolInitialSize = 7;
+ }
+ if (!Number.isInteger(config.pthreadPoolUnusedSize)) {
+ config.pthreadPoolUnusedSize = 3;
+ }
+ if (!Number.isInteger(config.finalizerThreadStartDelayMs)) {
+ config.finalizerThreadStartDelayMs = 200;
+ }
+ if (config.mainThreadingMode == undefined) {
+ config.mainThreadingMode = MainThreadingMode.DeputyThread;
+ }
+ if (config.jsThreadBlockingMode == undefined) {
+ config.jsThreadBlockingMode = JSThreadBlockingMode.NoBlockingWait;
+ }
+ if (config.jsThreadInteropMode == undefined) {
+ config.jsThreadInteropMode = JSThreadInteropMode.SimpleSynchronousJSInterop;
+ }
+ let validModes = false;
+ if (config.mainThreadingMode == MainThreadingMode.DeputyThread
+ && config.jsThreadBlockingMode == JSThreadBlockingMode.NoBlockingWait
+ && config.jsThreadInteropMode == JSThreadInteropMode.SimpleSynchronousJSInterop
+ ) {
+ validModes = true;
+ }
+ else if (config.mainThreadingMode == MainThreadingMode.DeputyThread
+ && config.jsThreadBlockingMode == JSThreadBlockingMode.AllowBlockingWait
+ && config.jsThreadInteropMode == JSThreadInteropMode.SimpleSynchronousJSInterop
+ ) {
+ validModes = true;
+ }
+ if (!validModes) {
+ mono_log_warn("Unsupported threading configuration", {
+ mainThreadingMode: config.mainThreadingMode,
+ jsThreadBlockingMode: config.jsThreadBlockingMode,
+ jsThreadInteropMode: config.jsThreadInteropMode
+ });
+ }
}
// this is how long the Mono GC will try to wait for all threads to be suspended before it gives up and aborts the process
diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts
index 62fee7656e282..88081b4e4319a 100644
--- a/src/mono/browser/runtime/managed-exports.ts
+++ b/src/mono/browser/runtime/managed-exports.ts
@@ -3,17 +3,18 @@
import WasmEnableThreads from "consts:wasmEnableThreads";
-import { GCHandle, GCHandleNull, JSMarshalerArguments, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal";
-import cwraps from "./cwraps";
+import { GCHandle, GCHandleNull, JSMarshalerArguments, JSThreadInteropMode, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal";
+import cwraps, { threads_c_functions as twraps } from "./cwraps";
import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals";
-import { JavaScriptMarshalerArgSize, alloc_stack_frame, get_arg, get_arg_gc_handle, is_args_exception, set_arg_intptr, set_arg_type, set_gc_handle } from "./marshal";
+import { JavaScriptMarshalerArgSize, alloc_stack_frame, get_arg, get_arg_gc_handle, is_args_exception, set_arg_i32, set_arg_intptr, set_arg_type, set_gc_handle, set_receiver_should_free } from "./marshal";
import { marshal_array_to_cs, marshal_array_to_cs_impl, marshal_bool_to_cs, marshal_exception_to_cs, marshal_intptr_to_cs, marshal_string_to_cs } from "./marshal-to-cs";
import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js, marshal_exception_to_js } from "./marshal-to-js";
import { do_not_force_dispose, is_gcv_handle } from "./gc-handles";
import { assert_c_interop, assert_js_interop } from "./invoke-js";
import { mono_wasm_main_thread_ptr } from "./pthreads";
-import { _zero_region } from "./memory";
+import { _zero_region, copyBytes } from "./memory";
import { stringToUTF8Ptr } from "./strings";
+import { mono_log_debug } from "./logging";
const managedExports: ManagedExports = {} as any;
@@ -164,6 +165,14 @@ export function complete_task(holder_gc_handle: GCHandle, isCanceling: boolean,
// the marshaled signature is: TRes? CallDelegate(GCHandle callback, T1? arg1, T2? arg2, T3? arg3)
export function call_delegate(callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) {
loaderHelpers.assert_runtime_running();
+ if (WasmEnableThreads) {
+ if (runtimeHelpers.config.jsThreadInteropMode == JSThreadInteropMode.NoSyncJSInterop) {
+ throw new Error("Cannot call synchronous C# methods.");
+ }
+ else if (runtimeHelpers.isPendingSynchronousCall) {
+ throw new Error("Cannot call synchronous C# method from inside a synchronous call to a JS method.");
+ }
+ }
const sp = Module.stackSave();
try {
const size = 6;
@@ -218,21 +227,26 @@ export function get_managed_stack_trace(exception_gc_handle: GCHandle) {
}
}
-// GCHandle InstallMainSynchronizationContext(nint jsNativeTID)
-export function install_main_synchronization_context(): GCHandle {
+// GCHandle InstallMainSynchronizationContext(nint jsNativeTID, JSThreadBlockingMode jsThreadBlockingMode, JSThreadInteropMode jsThreadInteropMode, MainThreadingMode mainThreadingMode)
+export function install_main_synchronization_context(jsThreadBlockingMode: number, jsThreadInteropMode: number, mainThreadingMode: number): GCHandle {
if (!WasmEnableThreads) return GCHandleNull;
assert_c_interop();
- const sp = Module.stackSave();
try {
// this block is like alloc_stack_frame() but without set_args_context()
- const bytes = JavaScriptMarshalerArgSize * 3;
+ const bytes = JavaScriptMarshalerArgSize * 6;
const args = Module.stackAlloc(bytes) as any;
_zero_region(args, bytes);
const res = get_arg(args, 1);
const arg1 = get_arg(args, 2);
+ const arg2 = get_arg(args, 3);
+ const arg3 = get_arg(args, 4);
+ const arg4 = get_arg(args, 5);
set_arg_intptr(arg1, mono_wasm_main_thread_ptr() as any);
+ set_arg_i32(arg2, jsThreadBlockingMode);
+ set_arg_i32(arg3, jsThreadInteropMode);
+ set_arg_i32(arg4, mainThreadingMode);
// this block is like invoke_sync_jsexport() but without assert_js_interop()
cwraps.mono_wasm_invoke_jsexport(managedExports.InstallMainSynchronizationContext!, args);
@@ -241,8 +255,9 @@ export function install_main_synchronization_context(): GCHandle {
throw marshal_exception_to_js(exc);
}
return get_arg_gc_handle(res) as any;
- } finally {
- Module.stackRestore(sp);
+ } catch (e) {
+ mono_log_debug("install_main_synchronization_context failed", e);
+ throw e;
}
}
@@ -255,31 +270,33 @@ export function invoke_async_jsexport(method: MonoMethod, args: JSMarshalerArgum
throw marshal_exception_to_js(exc);
}
} else {
- throw new Error("Should be unreachable until we implement deputy." + size);
- /*
set_receiver_should_free(args);
const bytes = JavaScriptMarshalerArgSize * size;
const cpy = Module._malloc(bytes) as any;
copyBytes(args as any, cpy, bytes);
twraps.mono_wasm_invoke_jsexport_async_post(runtimeHelpers.managedThreadTID, method, cpy);
- */
}
}
export function invoke_sync_jsexport(method: MonoMethod, args: JSMarshalerArguments): void {
assert_js_interop();
- if (!WasmEnableThreads || runtimeHelpers.isManagedRunningOnCurrentThread) {
+ if (!WasmEnableThreads) {
cwraps.mono_wasm_invoke_jsexport(method, args as any);
} else {
- throw new Error("Should be unreachable until we implement deputy.");
- /*
- if (!runtimeHelpers.isManagedRunningOnCurrentThread && runtimeHelpers.isPendingSynchronousCall) {
+ if (runtimeHelpers.config.jsThreadInteropMode == JSThreadInteropMode.NoSyncJSInterop) {
+ throw new Error("Cannot call synchronous C# methods.");
+ }
+ else if (runtimeHelpers.isPendingSynchronousCall) {
throw new Error("Cannot call synchronous C# method from inside a synchronous call to a JS method.");
}
- // this is blocking too
- twraps.mono_wasm_invoke_jsexport_sync_send(runtimeHelpers.managedThreadTID, method, args as any);
- */
+ if (runtimeHelpers.isManagedRunningOnCurrentThread) {
+ cwraps.mono_wasm_invoke_jsexport(method, args as any);
+ } else {
+ // this is blocking too
+ twraps.mono_wasm_invoke_jsexport_sync_send(runtimeHelpers.managedThreadTID, method, args as any);
+ }
}
+
if (is_args_exception(args)) {
const exc = get_arg(args, 0);
throw marshal_exception_to_js(exc);
diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts
index b6f825c4f5738..35e001c8665e0 100644
--- a/src/mono/browser/runtime/marshal.ts
+++ b/src/mono/browser/runtime/marshal.ts
@@ -7,7 +7,7 @@ import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"
import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region, getB32, setB32, forceThreadMemoryViewRefresh } from "./memory";
import { mono_wasm_new_external_root } from "./roots";
-import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType } from "./types/internal";
+import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType, PThreadPtr, PThreadPtrNull } from "./types/internal";
import { TypedArray, VoidPtr } from "./types/emscripten";
import { utf16ToString } from "./strings";
import { get_managed_stack_trace } from "./managed-exports";
@@ -38,6 +38,7 @@ const enum JSMarshalerArgumentOffsets {
ElementType = 13,
ContextHandle = 16,
ReceiverShouldFree = 20,
+ CallerNativeTID = 24,
}
export const JSMarshalerTypeSize = 32;
// keep in sync with JSFunctionBinding.JSBindingType
@@ -90,6 +91,12 @@ export function is_receiver_should_free(args: JSMarshalerArguments): boolean {
return getB32(args + JSMarshalerArgumentOffsets.ReceiverShouldFree);
}
+export function get_caller_native_tid(args: JSMarshalerArguments): PThreadPtr {
+ if (!WasmEnableThreads) return PThreadPtrNull;
+ mono_assert(args, "Null args");
+ return getI32(args + JSMarshalerArgumentOffsets.CallerNativeTID) as any;
+}
+
export function set_receiver_should_free(args: JSMarshalerArguments): void {
mono_assert(args, "Null args");
setB32(args + JSMarshalerArgumentOffsets.ReceiverShouldFree, true);
diff --git a/src/mono/browser/runtime/multi-threading.md b/src/mono/browser/runtime/multi-threading.md
new file mode 100644
index 0000000000000..4e308852e5034
--- /dev/null
+++ b/src/mono/browser/runtime/multi-threading.md
@@ -0,0 +1,45 @@
+# Multi-threading with JavaScript interop
+
+## Meaningful configurations are:
+
+ * Single-threaded mode as you know it since .Net 6
+ - default, safe, tested, supported
+ - from .Net 8 it could be easily started also as a web worker, but you need your own messaging between main and worker
+ * `MainThreadingMode.DeputyThread` + `JSThreadBlockingMode.NoBlockingWait` + `JSThreadInteropMode.SimpleSynchronousJSInterop`
+ + **default threading**, safe, tested, supported
+ + blocking `.Wait` is allowed on thread pool and new threads
+ - blocking `.Wait` throws `PlatformNotSupportedException` on `JSWebWorker` and main thread
+ - DOM events like `onClick` need to be asynchronous, if the handler needs use synchronous `[JSImport]`
+ - synchronous calls to `[JSImport]`/`[JSExport]` can't synchronously call back
+
+ * `MainThreadingMode.DeputyThread` + `JSThreadBlockingMode.AllowBlockingWait` + `JSThreadInteropMode.SimpleSynchronousJSInterop`
+ + pragmatic for legacy codebase, which contains blocking code and can't be fully executed on thread pool or new threads
+ - ** could cause deadlocks !!!**
+ - Use your own judgment before you opt in.
+ - blocking .Wait is allowed on all threads!
+ - blocking .Wait on pending JS `Task`/`Promise` (like HTTP/WS requests) could cause deadlocks!
+ - reason is that blocked thread can't process the browser event loop
+ - so it can't resolve the promises
+ - even when it's longer `Promise`/`Task` chain
+ - DOM events like `onClick` need to be asynchronous, if the handler needs use synchronous `[JSImport]`
+ - synchronous calls to `[JSImport]`/`[JSExport]` can't synchronously call back
+
+## Unsupported combinations are:
+ * `MainThreadingMode.DeputyThread` + `JSThreadBlockingMode.NoBlockingWait` + `JSThreadInteropMode.NoSyncJSInterop`
+ + very safe
+ - HTTP/WS requests are not possible because it currently uses synchronous JS interop
+ - Blazor doesn't work because it currently uses synchronous JS interop
+ * `MainThreadingMode.UIThread`
+ - not recommended, not tested, not supported!
+ - can deadlock on creating new threads
+ - can deadlock on blocking `.Wait` for a pending JS `Promise`/`Task`, including HTTP/WS requests
+ - .Wait is spin-waiting - it blocks debugger, network, UI rendering, ...
+ + JS interop to UI is faster, synchronous and re-entrant
+
+### There could be more JSThreadInteropModes:
+ - allow re-entrant synchronous JS interop on `JSWebWorker`.
+ - This is possible because managed code is running on same thread as JS.
+ - But it's nuanced to debug it, when things go wrong.
+ - allow re-entrant synchronous JS interop also on deputy thread.
+ - This is not possible for deputy, because it would deadlock on call back to different thread.
+ - The thread receiving the callback is still blocked waiting for the first synchronous call to finish.
diff --git a/src/mono/browser/runtime/pthreads/deputy-thread.ts b/src/mono/browser/runtime/pthreads/deputy-thread.ts
new file mode 100644
index 0000000000000..1020aaf98620d
--- /dev/null
+++ b/src/mono/browser/runtime/pthreads/deputy-thread.ts
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import WasmEnableThreads from "consts:wasmEnableThreads";
+import BuildConfiguration from "consts:configuration";
+
+import { mono_log_error, mono_log_info } from "../logging";
+import { monoThreadInfo, postMessageToMain, update_thread_info } from "./shared";
+import { Module, loaderHelpers, runtimeHelpers } from "../globals";
+import { start_runtime } from "../startup";
+import { WorkerToMainMessageType } from "../types/internal";
+
+export function mono_wasm_start_deputy_thread_async() {
+ if (!WasmEnableThreads) return;
+
+ if (BuildConfiguration === "Debug" && globalThis.setInterval) globalThis.setInterval(() => {
+ mono_log_info("Deputy thread is alive!");
+ }, 3000);
+
+ try {
+ monoThreadInfo.isDeputy = true;
+ monoThreadInfo.threadName = "Managed Main Deputy";
+ update_thread_info();
+ postMessageToMain({
+ monoCmd: WorkerToMainMessageType.deputyCreated,
+ info: monoThreadInfo,
+ });
+ Module.runtimeKeepalivePush();
+ Module.safeSetTimeout(async () => {
+ try {
+
+ await start_runtime();
+
+ postMessageToMain({
+ monoCmd: WorkerToMainMessageType.deputyStarted,
+ info: monoThreadInfo,
+ deputyProxyGCHandle: runtimeHelpers.proxyGCHandle,
+ });
+ }
+ catch (err) {
+ postMessageToMain({
+ monoCmd: WorkerToMainMessageType.deputyFailed,
+ info: monoThreadInfo,
+ error: "mono_wasm_start_deputy_thread_async() failed" + err,
+ });
+ mono_log_error("mono_wasm_start_deputy_thread_async() failed", err);
+ loaderHelpers.mono_exit(1, err);
+ throw err;
+ }
+ }, 0);
+ }
+ catch (err) {
+ mono_log_error("mono_wasm_start_deputy_thread_async() failed", err);
+ loaderHelpers.mono_exit(1, err);
+ throw err;
+ }
+
+ // same as emscripten_exit_with_live_runtime()
+ throw "unwind";
+}
\ No newline at end of file
diff --git a/src/mono/browser/runtime/pthreads/index.ts b/src/mono/browser/runtime/pthreads/index.ts
index 74d9a4be7561b..a97f0f0f06082 100644
--- a/src/mono/browser/runtime/pthreads/index.ts
+++ b/src/mono/browser/runtime/pthreads/index.ts
@@ -16,3 +16,5 @@ export {
mono_wasm_pthread_on_pthread_registered, mono_wasm_pthread_set_name, currentWorkerThreadEvents,
dotnetPthreadCreated, initWorkerThreadEvents, replaceEmscriptenTLSInit, pthread_self
} from "./worker-thread";
+
+export { mono_wasm_start_deputy_thread_async } from "./deputy-thread";
diff --git a/src/mono/browser/runtime/pthreads/shared.ts b/src/mono/browser/runtime/pthreads/shared.ts
index eda95db4c6226..5eb76b7f8fc37 100644
--- a/src/mono/browser/runtime/pthreads/shared.ts
+++ b/src/mono/browser/runtime/pthreads/shared.ts
@@ -38,7 +38,7 @@ export function mono_wasm_install_js_worker_interop(context_gc_handle: GCHandle)
mono_assert(!runtimeHelpers.proxyGCHandle, "JS interop should not be already installed on this worker.");
runtimeHelpers.proxyGCHandle = context_gc_handle;
if (ENVIRONMENT_IS_PTHREAD) {
- runtimeHelpers.managedThreadTID = mono_wasm_pthread_ptr();
+ runtimeHelpers.managedThreadTID = runtimeHelpers.currentThreadTID;
runtimeHelpers.isManagedRunningOnCurrentThread = true;
}
Module.runtimeKeepalivePush();
@@ -70,14 +70,15 @@ export function update_thread_info(): void {
if (!WasmEnableThreads) return;
const threadType = !monoThreadInfo.isRegistered ? "emsc"
: monoThreadInfo.isUI ? "-UI-"
- : monoThreadInfo.isTimer ? "timr"
- : monoThreadInfo.isLongRunning ? "long"
- : monoThreadInfo.isThreadPoolGate ? "gate"
- : monoThreadInfo.isDebugger ? "dbgr"
- : monoThreadInfo.isThreadPoolWorker ? "pool"
- : monoThreadInfo.isExternalEventLoop ? "jsww"
- : monoThreadInfo.isBackground ? "back"
- : "norm";
+ : monoThreadInfo.isDeputy ? "dpty"
+ : monoThreadInfo.isTimer ? "timr"
+ : monoThreadInfo.isLongRunning ? "long"
+ : monoThreadInfo.isThreadPoolGate ? "gate"
+ : monoThreadInfo.isDebugger ? "dbgr"
+ : monoThreadInfo.isThreadPoolWorker ? "pool"
+ : monoThreadInfo.isExternalEventLoop ? "jsww"
+ : monoThreadInfo.isBackground ? "back"
+ : "norm";
const hexPtr = (monoThreadInfo.pthreadId as any).toString(16).padStart(8, "0");
const hexPrefix = monoThreadInfo.isRegistered ? "0x" : "--";
monoThreadInfo.threadPrefix = `${hexPrefix}${hexPtr}-${threadType}`;
@@ -124,6 +125,8 @@ export interface MonoWorkerToMainMessage {
monoCmd: WorkerToMainMessageType;
info: PThreadInfo;
port?: MessagePort;
+ error?: string;
+ deputyProxyGCHandle?: GCHandle;
}
/// Identification of the current thread executing on a worker
diff --git a/src/mono/browser/runtime/pthreads/ui-thread.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts
index d145af3c50a6c..c7fb54a66e48c 100644
--- a/src/mono/browser/runtime/pthreads/ui-thread.ts
+++ b/src/mono/browser/runtime/pthreads/ui-thread.ts
@@ -95,12 +95,20 @@ function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent):
worker.thread = thread;
worker.info.isRunning = true;
resolveThreadPromises(pthreadId, thread);
+ worker.info = Object.assign(worker.info!, message.info, {});
+ break;
+ case WorkerToMainMessageType.deputyStarted:
+ runtimeHelpers.afterMonoStarted.promise_control.resolve(message.deputyProxyGCHandle);
+ break;
+ case WorkerToMainMessageType.deputyFailed:
+ runtimeHelpers.afterMonoStarted.promise_control.reject(new Error(message.error));
break;
case WorkerToMainMessageType.monoRegistered:
case WorkerToMainMessageType.monoAttached:
case WorkerToMainMessageType.enabledInterop:
case WorkerToMainMessageType.monoUnRegistered:
case WorkerToMainMessageType.updateInfo:
+ case WorkerToMainMessageType.deputyCreated:
// just worker.info updates above
break;
default:
diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts
index 8d98635b55637..41ce07e37d3c4 100644
--- a/src/mono/browser/runtime/startup.ts
+++ b/src/mono/browser/runtime/startup.ts
@@ -3,9 +3,9 @@
import WasmEnableThreads from "consts:wasmEnableThreads";
-import { DotnetModuleInternal, CharPtrNull } from "./types/internal";
+import { DotnetModuleInternal, CharPtrNull, MainThreadingMode } from "./types/internal";
import { ENVIRONMENT_IS_NODE, exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert, ENVIRONMENT_IS_WORKER } from "./globals";
-import cwraps, { init_c_exports } from "./cwraps";
+import cwraps, { init_c_exports, threads_c_functions as tcwraps } from "./cwraps";
import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug";
import { toBase64StringImpl } from "./base64";
import { mono_wasm_init_aot_profiler, mono_wasm_init_browser_profiler } from "./profiler";
@@ -270,11 +270,25 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) {
Module.runtimeKeepalivePush();
- // load mono runtime and apply environment settings (if necessary)
- await start_runtime();
+ if (WasmEnableThreads && runtimeHelpers.config.mainThreadingMode == MainThreadingMode.DeputyThread) {
+ // this will create thread and call start_runtime() on it
+ runtimeHelpers.monoThreadInfo = monoThreadInfo;
+ runtimeHelpers.isManagedRunningOnCurrentThread = false;
+ update_thread_info();
+ runtimeHelpers.managedThreadTID = tcwraps.mono_wasm_create_deputy_thread();
+ runtimeHelpers.proxyGCHandle = await runtimeHelpers.afterMonoStarted.promise;
- if (!ENVIRONMENT_IS_WORKER) {
- Module.runtimeKeepalivePush();
+ // TODO make UI thread not managed
+ tcwraps.mono_wasm_register_ui_thread();
+ monoThreadInfo.isAttached = true;
+ monoThreadInfo.isRegistered = true;
+
+ runtimeHelpers.runtimeReady = true;
+ update_thread_info();
+ bindings_init();
+ } else {
+ // load mono runtime and apply environment settings (if necessary)
+ await start_runtime();
}
if (ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER) {
@@ -517,7 +531,10 @@ export async function start_runtime() {
monoThreadInfo.isRegistered = true;
runtimeHelpers.currentThreadTID = monoThreadInfo.pthreadId = runtimeHelpers.managedThreadTID = mono_wasm_pthread_ptr();
update_thread_info();
- runtimeHelpers.proxyGCHandle = install_main_synchronization_context();
+ runtimeHelpers.proxyGCHandle = install_main_synchronization_context(
+ runtimeHelpers.config.jsThreadBlockingMode!,
+ runtimeHelpers.config.jsThreadInteropMode!,
+ runtimeHelpers.config.mainThreadingMode!);
runtimeHelpers.isManagedRunningOnCurrentThread = true;
// start finalizer thread, lazy
diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts
index e92c24faa03b2..4c9fa7babc62f 100644
--- a/src/mono/browser/runtime/types/internal.ts
+++ b/src/mono/browser/runtime/types/internal.ts
@@ -95,6 +95,10 @@ export type MonoConfigInternal = MonoConfig & {
resourcesHash?: string,
GitHash?: string,
ProductVersion?: string,
+
+ mainThreadingMode?: MainThreadingMode,
+ jsThreadBlockingMode?: JSThreadBlockingMode,
+ jsThreadInteropMode?: JSThreadInteropMode,
};
export type RunArguments = {
@@ -455,6 +459,7 @@ export type passEmscriptenInternalsType = (internals: EmscriptenInternals, emscr
export type setGlobalObjectsType = (globalObjects: GlobalObjects) => void;
export type initializeExportsType = (globalObjects: GlobalObjects) => RuntimeAPI;
export type initializeReplacementsType = (replacements: EmscriptenReplacements) => void;
+export type afterInitializeType = (module: EmscriptenModuleInternal) => void;
export type configureEmscriptenStartupType = (module: DotnetModuleInternal) => void;
export type configureRuntimeStartupType = () => Promise;
export type configureWorkerStartupType = (module: DotnetModuleInternal) => Promise
@@ -489,6 +494,9 @@ export const enum WorkerToMainMessageType {
enabledInterop = "notify_enabled_interop",
monoUnRegistered = "monoUnRegistered",
pthreadCreated = "pthreadCreated",
+ deputyCreated = "createdDeputy",
+ deputyFailed = "deputyFailed",
+ deputyStarted = "monoStarted",
preload = "preload",
}
@@ -518,6 +526,7 @@ export interface PThreadInfo {
isRegistered?: boolean,
isRunning?: boolean,
isAttached?: boolean,
+ isDeputy?: boolean,
isExternalEventLoop?: boolean,
isUI?: boolean;
isBackground?: boolean,
@@ -557,3 +566,33 @@ export interface MonoThreadMessage {
// A particular kind of message. For example, "started", "stopped", "stopped_with_error", etc.
cmd: string;
}
+
+// keep in sync with JSHostImplementation.Types.cs
+export const enum MainThreadingMode {
+ // Running the managed main thread on UI thread.
+ // Managed GC and similar scenarios could be blocking the UI.
+ // Easy to deadlock. Not recommended for production.
+ UIThread = 0,
+ // Running the managed main thread on dedicated WebWorker. Marshaling all JavaScript calls to and from the main thread.
+ DeputyThread = 1,
+}
+
+// keep in sync with JSHostImplementation.Types.cs
+export const enum JSThreadBlockingMode {
+ // throw PlatformNotSupportedException if blocking .Wait is called on threads with JS interop, like JSWebWorker and Main thread.
+ // Avoids deadlocks (typically with pending JS promises on the same thread) by throwing exceptions.
+ NoBlockingWait = 0,
+ // allow .Wait on all threads.
+ // Could cause deadlocks with blocking .Wait on a pending JS Task/Promise on the same thread or similar Task/Promise chain.
+ AllowBlockingWait = 100,
+}
+
+// keep in sync with JSHostImplementation.Types.cs
+export const enum JSThreadInteropMode {
+ // throw PlatformNotSupportedException if synchronous JSImport/JSExport is called on threads with JS interop, like JSWebWorker and Main thread.
+ // calling synchronous JSImport on thread pool or new threads is allowed.
+ NoSyncJSInterop = 0,
+ // allow non-re-entrant synchronous blocking calls to and from JS on JSWebWorker on threads with JS interop, like JSWebWorker and Main thread.
+ // calling synchronous JSImport on thread pool or new threads is allowed.
+ SimpleSynchronousJSInterop = 1,
+}
\ No newline at end of file
diff --git a/src/mono/mono/component/diagnostics_server.c b/src/mono/mono/component/diagnostics_server.c
index 4bea3d722625c..02179f785eb35 100644
--- a/src/mono/mono/component/diagnostics_server.c
+++ b/src/mono/mono/component/diagnostics_server.c
@@ -250,6 +250,7 @@ queue_push_sync (WasmIpcStreamQueue *q, const uint8_t *buf, uint32_t buf_size, u
gboolean is_browser_thread = FALSE;
while (mono_atomic_load_i32 (&q->buf_full) != 0) {
if (G_UNLIKELY (!is_browser_thread_inited)) {
+ // FIXME for deputy
is_browser_thread = mono_threads_wasm_is_ui_thread ();
is_browser_thread_inited = TRUE;
}
diff --git a/src/mono/mono/utils/mono-threads-wasm.c b/src/mono/mono/utils/mono-threads-wasm.c
index 67d3e7a1c6a79..1746c7213e96d 100644
--- a/src/mono/mono/utils/mono-threads-wasm.c
+++ b/src/mono/mono/utils/mono-threads-wasm.c
@@ -458,7 +458,7 @@ mono_threads_platform_is_main_thread (void)
#ifdef DISABLE_THREADS
return TRUE;
#else
- return emscripten_is_main_runtime_thread ();
+ return mono_threads_wasm_is_deputy_thread ();
#endif
}
@@ -541,6 +541,54 @@ mono_threads_wasm_on_thread_registered (void)
}
#ifndef DISABLE_THREADS
+extern void mono_wasm_start_deputy_thread_async (void);
+extern void mono_wasm_trace_logger (const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data);
+static pthread_t deputy_thread_tid;
+
+gboolean
+mono_threads_wasm_is_deputy_thread (void)
+{
+ return pthread_self () == deputy_thread_tid;
+}
+
+MonoNativeThreadId
+mono_threads_wasm_deputy_thread_tid (void)
+{
+ return (MonoNativeThreadId) deputy_thread_tid;
+}
+
+// this is running in deputy thread
+static gsize
+deputy_thread_fn (void* unused_arg G_GNUC_UNUSED)
+{
+ deputy_thread_tid = pthread_self ();
+
+ // this will throw JS "unwind"
+ mono_wasm_start_deputy_thread_async();
+
+ return 0;// never reached
+}
+
+EMSCRIPTEN_KEEPALIVE MonoNativeThreadId
+mono_wasm_create_deputy_thread (void)
+{
+ pthread_create (&deputy_thread_tid, NULL, (void *(*)(void *)) deputy_thread_fn, NULL);
+ return deputy_thread_tid;
+}
+
+// TODO ideally we should not need to have UI thread registered as managed
+EMSCRIPTEN_KEEPALIVE void
+mono_wasm_register_ui_thread (void)
+{
+ MonoThread *thread = mono_thread_internal_attach (mono_get_root_domain ());
+ mono_thread_set_state (thread, ThreadState_Background);
+ mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE);
+
+ MonoThreadInfo *info = mono_thread_info_current_unchecked ();
+ g_assert (info);
+ info->runtime_thread = TRUE;
+ MONO_ENTER_GC_SAFE_UNBALANCED;
+}
void
mono_threads_wasm_async_run_in_target_thread (pthread_t target_thread, void (*func) (void))
diff --git a/src/mono/mono/utils/mono-threads-wasm.h b/src/mono/mono/utils/mono-threads-wasm.h
index 13d0b6cb76a6c..13668709357db 100644
--- a/src/mono/mono/utils/mono-threads-wasm.h
+++ b/src/mono/mono/utils/mono-threads-wasm.h
@@ -28,6 +28,18 @@ mono_threads_wasm_ui_thread_tid (void);
#ifndef DISABLE_THREADS
+gboolean
+mono_threads_wasm_is_deputy_thread (void);
+
+MonoNativeThreadId
+mono_threads_wasm_deputy_thread_tid (void);
+
+MonoNativeThreadId
+mono_wasm_create_deputy_thread (void);
+
+void
+mono_wasm_register_ui_thread (void);
+
void
mono_threads_wasm_async_run_in_target_thread (pthread_t target_thread, void (*func) (void));