Avoid QueueUserWorkItem allocation in rare Channel race condition (#48313)
authorStephen Toub <stoub@microsoft.com>
Tue, 16 Feb 2021 02:02:55 +0000 (21:02 -0500)
committerGitHub <noreply@github.com>
Tue, 16 Feb 2021 02:02:55 +0000 (21:02 -0500)
src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.cs
src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netcoreapp.cs
src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard1.cs
src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard2.cs
src/libraries/System.Threading.Channels/tests/ChannelTestBase.cs

index b13548c..7e912df 100644 (file)
@@ -269,7 +269,14 @@ namespace System.Threading.Channels
                 // object is completed after the awaiter.IsCompleted but before the awaiter.OnCompleted.
                 if (_schedulingContext == null)
                 {
-                    QueueUserWorkItem(continuation, state);
+                    if (_executionContext == null)
+                    {
+                        UnsafeQueueUserWorkItem(continuation, state);
+                    }
+                    else
+                    {
+                        QueueUserWorkItem(continuation, state);
+                    }
                 }
                 else if (sc != null)
                 {
index 80059d8..85ed3d8 100644 (file)
@@ -10,6 +10,9 @@ namespace System.Threading.Channels
         private void UnsafeQueueSetCompletionAndInvokeContinuation() =>
             ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
 
+        private void UnsafeQueueUserWorkItem(Action<object?> action, object? state) =>
+            ThreadPool.UnsafeQueueUserWorkItem(action, state, preferLocal: false);
+
         private static void QueueUserWorkItem(Action<object?> action, object? state) =>
             ThreadPool.QueueUserWorkItem(action, state, preferLocal: false);
 
index 7637f35..40e4d62 100644 (file)
@@ -11,6 +11,9 @@ namespace System.Threading.Channels
             Task.Factory.StartNew(s => ((AsyncOperation<TResult>)s).SetCompletionAndInvokeContinuation(), this,
                 CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 
+        private void UnsafeQueueUserWorkItem(Action<object?> action, object? state) =>
+            QueueUserWorkItem(action, state);
+
         private static void QueueUserWorkItem(Action<object?> action, object? state) =>
             Task.Factory.StartNew(action, state,
                 CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
index 9c02138..99693d8 100644 (file)
@@ -10,6 +10,9 @@ namespace System.Threading.Channels
         private void UnsafeQueueSetCompletionAndInvokeContinuation() =>
             ThreadPool.UnsafeQueueUserWorkItem(static s => ((AsyncOperation<TResult>)s).SetCompletionAndInvokeContinuation(), this);
 
+        private void UnsafeQueueUserWorkItem(Action<object?> action, object? state) =>
+            QueueUserWorkItem(action, state);
+
         private static void QueueUserWorkItem(Action<object?> action, object? state) =>
             Task.Factory.StartNew(action, state,
                 CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
index fd2dbed..a345de4 100644 (file)
@@ -1171,9 +1171,10 @@ namespace System.Threading.Channels.Tests
                 Assert.True(vt.IsCompletedSuccessfully);
 
                 Assert.Equal(continueOnCapturedContext != false, schedulerWasFlowed);
-                if (completeBeforeOnCompleted) // OnCompleted will simply queue using a mechanism that happens to flow
+                if (completeBeforeOnCompleted)
                 {
-                    Assert.True(executionContextWasFlowed);
+                    // OnCompleted may or may not flow ExecutionContext here; it's not needed,
+                    // and we avoid it when it's easy, but it's also not wrong to do so.
                 }
                 else
                 {