Fix leak in Task.WaitAny(..., int) (dotnet/coreclr#10331)
authorStephen Toub <stoub@microsoft.com>
Wed, 22 Mar 2017 06:09:22 +0000 (02:09 -0400)
committerJan Kotas <jkotas@microsoft.com>
Wed, 22 Mar 2017 06:09:22 +0000 (23:09 -0700)
WaitAny is effectively built on top of WhenAny, creating a continuation from the supplied tasks and then blocking on that continuation.  When a timeout is provided, it blocks with that timeout.  But if it doesn't complete within the timeout, it ends up leaking the continuations it created into the constituent tasks.  The fix is simply to force the returned continuation to complete, such that its continuation logic does all of the appropriate cleanup.

Commit migrated from https://github.com/dotnet/coreclr/commit/d6e9a08a70dab11ddc845fb0ccf36aa436e9ebec

src/coreclr/src/mscorlib/src/System/Threading/Tasks/Task.cs
src/coreclr/src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs

index b9f58c0..8e2e6a4 100644 (file)
@@ -5029,6 +5029,10 @@ namespace System.Threading.Tasks
                     signaledTaskIndex = Array.IndexOf(tasks, firstCompleted.Result);
                     Debug.Assert(signaledTaskIndex >= 0);
                 }
+                else
+                {
+                    TaskFactory.CommonCWAnyLogicCleanup(firstCompleted);
+                }
             }
 
             // We need to prevent the tasks array from being GC'ed until we come out of the wait.
index 9f6f505..e193d0e 100644 (file)
@@ -2372,7 +2372,8 @@ namespace System.Threading.Tasks
         {
             Contract.Requires(tasks != null);
 
-            // Create a promise task to be returned to the user
+            // Create a promise task to be returned to the user.
+            // (If this logic ever changes, also update CommonCWAnyLogicCleanup.)
             var promise = new CompleteOnInvokePromise(tasks);
 
             // At the completion of any of the tasks, complete the promise.
@@ -2420,6 +2421,17 @@ namespace System.Threading.Tasks
             return promise;
         }
 
+        /// <summary>
+        /// Cleans up the operations performed by CommonCWAnyLogic in a case where
+        /// the created continuation task is being discarded.
+        /// </summary>
+        /// <param name="continuation">The task returned from CommonCWAnyLogic.</param>
+        internal static void CommonCWAnyLogicCleanup(Task<Task> continuation)
+        {
+            // Force cleanup of the promise (e.g. removing continuations from each
+            // constituent task), by completing the promise with any value.
+            ((CompleteOnInvokePromise)continuation).Invoke(null);
+        }
 
         /// <summary>
         /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see>