Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[browser][MT] JSSynchronizationContext_Send_Post_Items_Cancellation #97832

Merged
merged 1 commit into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
{
Expand All @@ -120,9 +123,6 @@ private void ThreadMain()
self.PropagateCompletionAndDispose(Task.FromCanceled<T>(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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
Expand Down
Loading