diff --git a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.cs b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.cs index 4294279eefc574..b16548c7b4c374 100644 --- a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.cs +++ b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.cs @@ -41,10 +41,13 @@ public static partial class Debug #if FEATURE_WASM_MANAGED_THREADS namespace System.Threading { - public partial class Monitor + public partial class Thread { [ThreadStatic] public static bool ThrowOnBlockingWaitOnJSInteropThread; + + public static void AssureBlockingPossible() { throw null; } + public static void ForceBlockingWait(Action action, object? state) { throw null; } } } #endif diff --git a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.txt b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.txt index b09aabbd2decc0..3b80cb0de6753b 100644 --- a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.txt +++ b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.txt @@ -5,4 +5,6 @@ T:System.Runtime.Serialization.DeserializationToken M:System.Runtime.Serialization.SerializationInfo.StartDeserialization T:System.Diagnostics.DebugProvider M:System.Diagnostics.Debug.SetProvider(System.Diagnostics.DebugProvider) -F:System.Threading.Monitor.ThrowOnBlockingWaitOnJSInteropThread +M:System.Threading.Thread.AssureBlockingPossible +F:System.Threading.Thread.ThrowOnBlockingWaitOnJSInteropThread +F:System.Threading.Thread.ForceBlockingWait diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 089418e78f4c36..b721414a669627 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4298,4 +4298,7 @@ This AssemblyBuilder instance doesn't support saving. Use AssemblyBuilder.DefinePersistedAssembly to create an AssemblyBuilder instance that supports saving. + + Blocking wait is not supported on the JS interop threads. + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs index e95d13cf2812f7..5340ecaeccc50a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs @@ -285,6 +285,9 @@ public bool Wait(int millisecondsTimeout) public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { CheckDispose(); +#if FEATURE_WASM_MANAGED_THREADS + Thread.AssureBlockingPossible(); +#endif if (millisecondsTimeout < -1) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index 0c1c65348fa501..1e400eec097bed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -671,5 +671,33 @@ public static int GetCurrentProcessorId() // a speed check will determine refresh rate of the cache and will report if caching is not advisable. // we will record that in a readonly static so that it could become a JIT constant and bypass caching entirely. private static readonly bool s_isProcessorNumberReallyFast = ProcessorIdCache.ProcessorNumberSpeedCheck(); + +#if FEATURE_WASM_MANAGED_THREADS + [ThreadStatic] + public static bool ThrowOnBlockingWaitOnJSInteropThread; + + public static void AssureBlockingPossible() + { + if (ThrowOnBlockingWaitOnJSInteropThread) + { + throw new PlatformNotSupportedException(SR.WasmThreads_BlockingWaitNotSupportedOnJSInterop); + } + } + + public static void ForceBlockingWait(Action action, object? state = null) + { + var flag = ThrowOnBlockingWaitOnJSInteropThread; + try + { + ThrowOnBlockingWaitOnJSInteropThread = false; + + action(state); + } + finally + { + ThrowOnBlockingWaitOnJSInteropThread = flag; + } + } +#endif } } 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 b547ffd66ad0f4..74a67b477648c9 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 @@ -233,18 +233,9 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) if (holder.CallbackReady != null) { - var threadFlag = Monitor.ThrowOnBlockingWaitOnJSInteropThread; - try - { - Monitor.ThrowOnBlockingWaitOnJSInteropThread = false; #pragma warning disable CA1416 // Validate platform compatibility - holder.CallbackReady?.Wait(); + Thread.ForceBlockingWait(static (callbackReady) => ((ManualResetEventSlim)callbackReady!).Wait(), holder.CallbackReady); #pragma warning restore CA1416 // Validate platform compatibility - } - finally - { - Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag; - } } lock (ctx) 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 4b2ce05e523db3..a1e6b6c93b093d 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 @@ -65,7 +65,7 @@ public static JSSynchronizationContext InstallWebWorkerInterop(bool isMainThread // - 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 - Monitor.ThrowOnBlockingWaitOnJSInteropThread = true; + Thread.ThrowOnBlockingWaitOnJSInteropThread = true; var proxyContext = ctx.ProxyContext; JSProxyContext.CurrentThreadContext = proxyContext; @@ -216,11 +216,8 @@ public override void Send(SendOrPostCallback d, object? state) d(state); return; } - // TODO, refactor into single assert method - if (Monitor.ThrowOnBlockingWaitOnJSInteropThread) - { - throw new PlatformNotSupportedException("Blocking wait is not supported on the JS interop threads."); - } + + Thread.AssureBlockingPossible(); using (var signal = new ManualResetEventSlim(false)) { 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 84d243f933535e..c88d32277da8bb 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 @@ -85,18 +85,8 @@ public async Task JSSynchronizationContext_Send_Post_Items_Cancellation() capturedSynchronizationContext = SynchronizationContext.Current; jswReady.SetResult(); - var threadFlag = Monitor.ThrowOnBlockingWaitOnJSInteropThread; - try - { - Monitor.ThrowOnBlockingWaitOnJSInteropThread = false; - - // blocking the worker, so that JSSynchronizationContext could enqueue next tasks - blocker.Wait(); - } - finally - { - Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag; - } + // blocking the worker, so that JSSynchronizationContext could enqueue next tasks + Thread.ForceBlockingWait(static (b) => ((ManualResetEventSlim)b).Wait(), blocker); return never.Task; }, cts.Token); @@ -453,9 +443,12 @@ public async Task WaitAssertsOnJSInteropThreads(Executor executor, NamedCall met await executor.Execute(Task () => { Exception? exception = null; - try { + try + { method.Call(cts.Token); - } catch (Exception ex) { + } + catch (Exception ex) + { exception = ex; } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs index 3d03feb01b6c28..87f88745377b02 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs @@ -158,6 +158,13 @@ public class NamedCall mr.Wait(cts.Token); } catch (OperationCanceledException) { /* ignore */ } }}, + new NamedCall { Name = "SemaphoreSlim.Wait", Call = delegate (CancellationToken ct) { + using var sem = new SemaphoreSlim(2); + var cts = new CancellationTokenSource(8); + try { + sem.Wait(cts.Token); + } catch (OperationCanceledException) { /* ignore */ } + }}, }; public static IEnumerable GetTargetThreadsAndBlockingCalls() diff --git a/src/libraries/System.Threading.Thread/src/CompatibilitySuppressions.Threading.xml b/src/libraries/System.Threading.Thread/src/CompatibilitySuppressions.Threading.xml index fb0dc5a8bf2ed6..5fc41e30ed4415 100644 --- a/src/libraries/System.Threading.Thread/src/CompatibilitySuppressions.Threading.xml +++ b/src/libraries/System.Threading.Thread/src/CompatibilitySuppressions.Threading.xml @@ -16,4 +16,16 @@ CP0014 M:System.Threading.Thread.UnsafeStart(System.Object):[T:System.Runtime.Versioning.UnsupportedOSPlatformAttribute] + + CP0002 + F:System.Threading.Thread.ThrowOnBlockingWaitOnJSInteropThread + + + CP0002 + M:System.Threading.Thread.AssureBlockingPossible + + + CP0002 + M:System.Threading.Thread.ForceBlockingWait(System.Action{System.Object},System.Object) + \ No newline at end of file diff --git a/src/libraries/System.Threading/src/CompatibilitySuppressions.Threading.xml b/src/libraries/System.Threading/src/CompatibilitySuppressions.Threading.xml index 33789a19820625..9c9a072a248df2 100644 --- a/src/libraries/System.Threading/src/CompatibilitySuppressions.Threading.xml +++ b/src/libraries/System.Threading/src/CompatibilitySuppressions.Threading.xml @@ -100,8 +100,4 @@ CP0014 M:System.Threading.Monitor.Wait(System.Object):[T:System.Runtime.Versioning.UnsupportedOSPlatformAttribute] - - CP0002 - F:System.Threading.Monitor.ThrowOnBlockingWaitOnJSInteropThread - diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs index 393c33d83d9f41..4633be109690a1 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs @@ -9,11 +9,6 @@ namespace System.Threading { public static partial class Monitor { -#if FEATURE_WASM_MANAGED_THREADS - [ThreadStatic] - public static bool ThrowOnBlockingWaitOnJSInteropThread; -#endif - [Intrinsic] [MethodImplAttribute(MethodImplOptions.InternalCall)] // Interpreter is missing this intrinsic public static void Enter(object obj) => Enter(obj); @@ -83,10 +78,7 @@ public static bool Wait(object obj, int millisecondsTimeout) { ArgumentNullException.ThrowIfNull(obj); #if FEATURE_WASM_MANAGED_THREADS - if (ThrowOnBlockingWaitOnJSInteropThread) - { - throw new PlatformNotSupportedException("blocking Wait is not supported on the JS interop threads."); - } + Thread.AssureBlockingPossible(); #endif return ObjWait(millisecondsTimeout, obj); }