From 195450160a9bc744d4c91f4f8c02b0523048018a Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 22 Mar 2017 02:09:22 -0400 Subject: [PATCH] Fix leak in Task.WaitAny(..., int) (dotnet/coreclr#10331) 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/mscorlib/src/System/Threading/Tasks/Task.cs | 4 ++++ .../src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/coreclr/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/coreclr/src/mscorlib/src/System/Threading/Tasks/Task.cs index b9f58c0..8e2e6a4 100644 --- a/src/coreclr/src/mscorlib/src/System/Threading/Tasks/Task.cs +++ b/src/coreclr/src/mscorlib/src/System/Threading/Tasks/Task.cs @@ -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. diff --git a/src/coreclr/src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs b/src/coreclr/src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs index 9f6f505..e193d0e 100644 --- a/src/coreclr/src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs +++ b/src/coreclr/src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs @@ -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; } + /// + /// Cleans up the operations performed by CommonCWAnyLogic in a case where + /// the created continuation task is being discarded. + /// + /// The task returned from CommonCWAnyLogic. + internal static void CommonCWAnyLogicCleanup(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); + } /// /// Creates a continuation Task -- 2.7.4