Fix UnobservedTaskException from SemaphoreSlim.WaitAsync (#60890)
authorStephen Toub <stoub@microsoft.com>
Fri, 5 Nov 2021 15:06:44 +0000 (11:06 -0400)
committerGitHub <noreply@github.com>
Fri, 5 Nov 2021 15:06:44 +0000 (11:06 -0400)
* 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.

* Fix wasm build

src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs
src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
src/libraries/System.Threading/tests/SemaphoreSlimTests.cs

index bfa8ccd..246ed5b 100644 (file)
@@ -745,7 +745,7 @@ namespace System.Threading
             public ConfiguredNoThrowAwaiter(Task<T> task) => _task = task;
             public ConfiguredNoThrowAwaiter<T> 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);
         }
index 7e8888f..fe0db4c 100644 (file)
@@ -1863,6 +1863,12 @@ namespace System.Threading.Tasks
             return Volatile.Read(ref m_contingentProperties)?.m_exceptionsHolder?.GetCancellationExceptionDispatchInfo(); // may be null
         }
 
+        /// <summary>Marks any exceptions stored in the Task as having been handled.</summary>
+        internal void MarkExceptionsAsHandled()
+        {
+            Volatile.Read(ref m_contingentProperties)?.m_exceptionsHolder?.MarkAsHandled(calledFromFinalizer: false);
+        }
+
         /// <summary>
         /// Throws an aggregate exception if the task contains exceptions.
         /// </summary>
index 2682166..7aabd01 100644 (file)
@@ -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 @@ namespace System.Threading.Tests
             semaphore.Release(totalWaiters / 2);
             Task.WaitAll(tasks);
         }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        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();
+        }
     }
 }