From 6ace13e8dcd47e9b88cf306b2c0b4b75fe0bd033 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 26 Oct 2021 17:34:00 -0400 Subject: [PATCH 1/2] Fix UnobservedTaskException from SemaphoreSlim.WaitAsync If a SemaphoreSlim.WaitAsync times out, it correctly returns false, but it also results in TaskScheduler.UnobservedTaskException being raised unexpectedly, due to internal use of a faulted task whose exception isn't observed. This fixes that by marking any such exceptions as having been observed. --- .../src/System/Threading/SemaphoreSlim.cs | 2 +- .../src/System/Threading/Tasks/Task.cs | 6 +++++ .../tests/SemaphoreSlimTests.cs | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs index bfa8ccd86802cd..246ed5b92f85a0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs @@ -745,7 +745,7 @@ private async Task WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int public ConfiguredNoThrowAwaiter(Task task) => _task = task; public ConfiguredNoThrowAwaiter GetAwaiter() => this; public bool IsCompleted => _task.IsCompleted; - public void GetResult() { } + public void GetResult() => _task.MarkExceptionsAsHandled(); public void UnsafeOnCompleted(Action continuation) => _task.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(continuation); public void OnCompleted(Action continuation) => _task.ConfigureAwait(false).GetAwaiter().OnCompleted(continuation); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index 458c914773cbcd..8a21b34ddb01c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -1863,6 +1863,12 @@ internal List GetExceptionDispatchInfos() return Volatile.Read(ref m_contingentProperties)?.m_exceptionsHolder?.GetCancellationExceptionDispatchInfo(); // may be null } + /// Marks any exceptions stored in the Task as having been handled. + internal void MarkExceptionsAsHandled() + { + Volatile.Read(ref m_contingentProperties)?.m_exceptionsHolder?.MarkAsHandled(calledFromFinalizer: false); + } + /// /// Throws an aggregate exception if the task contains exceptions. /// diff --git a/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs b/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs index 2682166b24c512..aebacdfd2e49cb 100644 --- a/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs +++ b/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Threading.Tests @@ -615,5 +617,28 @@ public static void TestConcurrentWaitAndWaitAsync(int syncWaiters, int asyncWait semaphore.Release(totalWaiters / 2); Task.WaitAll(tasks); } + + [Fact] + public void WaitAsync_Timeout_NoUnhandledException() + { + RemoteExecutor.Invoke(async () => + { + Exception error = null; + TaskScheduler.UnobservedTaskException += (s, e) => Volatile.Write(ref error, e.Exception); + + var sem = new SemaphoreSlim(0); + for (int i = 0; i < 2; ++i) + { + await sem.WaitAsync(1); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + if (Volatile.Read(ref error) is Exception e) + { + throw e; + } + }).Dispose(); + } } } From 11fcaa54f3fa2f0388bfebde60e1878b12361af4 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 27 Oct 2021 06:29:36 -0400 Subject: [PATCH 2/2] Fix wasm build --- src/libraries/System.Threading/tests/SemaphoreSlimTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs b/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs index aebacdfd2e49cb..7aabd01c39f1e2 100644 --- a/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs +++ b/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs @@ -618,7 +618,7 @@ public static void TestConcurrentWaitAndWaitAsync(int syncWaiters, int asyncWait Task.WaitAll(tasks); } - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void WaitAsync_Timeout_NoUnhandledException() { RemoteExecutor.Invoke(async () =>