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 a329bf54eea82..eaa5bc2be575d 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 @@ -226,11 +226,17 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) if (holder.CallbackReady != null) { var threadFlag = Monitor.ThrowOnBlockingWaitOnJSInteropThread; - Monitor.ThrowOnBlockingWaitOnJSInteropThread = false; -#pragma warning disable CA1416 // Validate platform compatibility - holder.CallbackReady?.Wait(); -#pragma warning restore CA1416 // Validate platform compatibility - Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag; + try + { + Monitor.ThrowOnBlockingWaitOnJSInteropThread = false; + #pragma warning disable CA1416 // Validate platform compatibility + holder.CallbackReady?.Wait(); + #pragma warning restore CA1416 // Validate platform compatibility + } + finally + { + Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag; + } } #endif var callback = holder.Callback!; 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 618be9bedf4e2..e2492d9163faf 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 @@ -90,12 +90,12 @@ internal void UninstallWebWorkerInterop() // this will runtimeKeepalivePop() // and later maybeExit() -> __emscripten_thread_exit() + // this will also call JSSynchronizationContext.Dispose() on this instance jsProxyContext.Dispose(); JSProxyContext.CurrentThreadContext = null; JSProxyContext.ExecutionContext = null; _isRunning = false; - Dispose(); } public JSSynchronizationContext(bool isMainThread, CancellationToken cancellationToken) @@ -217,9 +217,15 @@ public override void Send(SendOrPostCallback d, object? state) } var threadFlag = Monitor.ThrowOnBlockingWaitOnJSInteropThread; - Monitor.ThrowOnBlockingWaitOnJSInteropThread = false; - signal.Wait(); - Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag; + try + { + Monitor.ThrowOnBlockingWaitOnJSInteropThread = false; + signal.Wait(); + } + finally + { + Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag; + } if (_isCancellationRequested) { @@ -280,25 +286,23 @@ private void Pump() } } - private void Dispose(bool disposing) + + internal void Dispose() { if (!_isDisposed) { - if (disposing) + _isCancellationRequested = true; + Queue.Writer.TryComplete(); + while (Queue.Reader.TryRead(out var item)) { - Queue.Writer.TryComplete(); + // the Post is checking _isCancellationRequested after .Wait() + item.Signal?.Set(); } _isDisposed = true; _cancellationTokenRegistration.Dispose(); previousSynchronizationContext = null; } } - - internal void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs index 27fe38e3f1991..5721c67d5ea24 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs @@ -112,6 +112,9 @@ private void ThreadMain() return; } + // JSSynchronizationContext also registers to _cancellationToken + _jsSynchronizationContext = JSSynchronizationContext.InstallWebWorkerInterop(false, _cancellationToken); + // receive callback when the cancellation is requested _cancellationRegistration = _cancellationToken.Register(static (o) => { @@ -120,9 +123,6 @@ private void ThreadMain() self.PropagateCompletionAndDispose(Task.FromCanceled(self._cancellationToken)); }, this); - // JSSynchronizationContext also registers to _cancellationToken - _jsSynchronizationContext = JSSynchronizationContext.InstallWebWorkerInterop(false, _cancellationToken); - var childScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // This code is exiting thread ThreadMain() before all promises are resolved. 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 aa8a91e4cd7a6..5409d100e58e7 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 @@ -84,8 +84,18 @@ public async Task JSSynchronizationContext_Send_Post_Items_Cancellation() capturedSynchronizationContext = SynchronizationContext.Current; jswReady.SetResult(); - // blocking the worker, so that JSSynchronizationContext could enqueue next tasks - blocker.Wait(); + var threadFlag = Monitor.ThrowOnBlockingWaitOnJSInteropThread; + try + { + Monitor.ThrowOnBlockingWaitOnJSInteropThread = false; + + // blocking the worker, so that JSSynchronizationContext could enqueue next tasks + blocker.Wait(); + } + finally + { + Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag; + } return never.Task; }, cts.Token); @@ -98,35 +108,51 @@ public async Task JSSynchronizationContext_Send_Post_Items_Cancellation() var hitAfterPost = false; var hitAfterSend = false; - var canceledSend = Task.Run(async () => + var canceledSend = Task.Run(() => { - // this will be blocked until blocker.Set() sendReady.SetResult(); - await canceled.Task; - capturedSynchronizationContext.Send(_ => + // this will be blocked until blocker.Set() + try { - // then it should get canceled and not executed - shouldNotHitSend = true; - }, null); + capturedSynchronizationContext.Send(_ => + { + // then it should get canceled and not executed + shouldNotHitSend = true; + }, null); + } + catch (Exception ex) + { + return Task.FromException(ex); + } hitAfterSend = true; - return Task.CompletedTask; + return Task.FromException(new Exception("Should be unreachable")); }); var canceledPost = Task.Run(() => { - capturedSynchronizationContext.Post(_ => + try { - // then it should get canceled and not executed - shouldNotHitPost = true; - }, null); + capturedSynchronizationContext.Post(_ => + { + // then it should get canceled and not executed + shouldNotHitPost = true; + }, null); + } + catch (Exception ex) + { + Console.WriteLine("Unexpected exception " + ex); + postReady.SetException(ex); + return Task.FromException(ex); + } hitAfterPost = true; postReady.SetResult(); return Task.CompletedTask; }); // make sure that jobs got the chance to enqueue - await sendReady.Task; await postReady.Task; + await sendReady.Task; + await Task.Delay(200); // make sure that // this could should be delivered immediately cts.Cancel();