From 7d950cbf13b455d1adccc9193bdba705df785f30 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Wed, 28 Jan 2026 11:20:27 -0800 Subject: [PATCH 1/2] e --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 68 +++++++++++++++++-- .../src/System/Threading/Tasks/Task.cs | 45 +++++++++++- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index e77eb6409cce51..427ed676fa334e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -12,6 +12,7 @@ using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; +using System.Collections.Generic; using System.Threading.Tasks.Sources; #if NATIVEAOT @@ -146,6 +147,22 @@ public ref byte GetResultStorageOrNull() } } + internal class ContinuationEqualityComparer : IEqualityComparer + { + internal static readonly ContinuationEqualityComparer Instance = new ContinuationEqualityComparer(); + + public bool Equals(Continuation? x, Continuation? y) + { + return ReferenceEquals(x, y); + } + + public unsafe int GetHashCode([DisallowNull] Continuation obj) + { + object o = (object)obj; + return RuntimeHelpers.GetHashCode(o); + } + } + [StructLayout(LayoutKind.Explicit)] internal unsafe ref struct AsyncDispatcherInfo { @@ -161,6 +178,12 @@ internal unsafe ref struct AsyncDispatcherInfo [FieldOffset(4)] #endif public Continuation? NextContinuation; +#if TARGET_64BIT + [FieldOffset(16)] +#else + [FieldOffset(8)] +#endif + public Task Task; // Information about current task dispatching, to be used for async // stackwalking. @@ -237,6 +260,7 @@ private static unsafe Continuation AllocContinuation(Continuation prevContinuati Continuation newContinuation = (Continuation)RuntimeTypeHandle.InternalAllocNoChecks(contMT); #endif prevContinuation.Next = newContinuation; + Task.SetRuntimeAsyncContinuationTicks(newContinuation, Environment.TickCount64); return newContinuation; } @@ -247,6 +271,7 @@ private static unsafe Continuation AllocContinuationMethod(Continuation prevCont Continuation newContinuation = (Continuation)RuntimeTypeHandle.InternalAllocNoChecks(contMT); Unsafe.As(ref Unsafe.Add(ref RuntimeHelpers.GetRawData(newContinuation), keepAliveOffset)) = loaderAllocator; prevContinuation.Next = newContinuation; + Task.SetRuntimeAsyncContinuationTicks(newContinuation, Environment.TickCount64); return newContinuation; } @@ -260,6 +285,7 @@ private static unsafe Continuation AllocContinuationClass(Continuation prevConti { Unsafe.As(ref Unsafe.Add(ref RuntimeHelpers.GetRawData(newContinuation), keepAliveOffset)) = GCHandle.FromIntPtr(loaderAllocatorHandle).Target; } + Task.SetRuntimeAsyncContinuationTicks(newContinuation, Environment.TickCount64); return newContinuation; } #endif @@ -336,6 +362,7 @@ private void SetContinuationState(Continuation value) { Debug.Assert(m_stateObject == null); m_stateObject = value; + Task.SetRuntimeAsyncContinuationTicks(value, Environment.TickCount64); } internal void HandleSuspended() @@ -445,6 +472,12 @@ private unsafe void DispatchContinuations() asyncDispatcherInfo.Next = AsyncDispatcherInfo.t_current; asyncDispatcherInfo.NextContinuation = MoveContinuationState(); AsyncDispatcherInfo.t_current = &asyncDispatcherInfo; + asyncDispatcherInfo.Task = this; + + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceSynchronousWorkBegin(this.Id, CausalitySynchronousWork.Execution); + } while (true) { @@ -456,15 +489,20 @@ private unsafe void DispatchContinuations() asyncDispatcherInfo.NextContinuation = nextContinuation; ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); + long tickCount = Task.GetRuntimeAsyncContinuationTicks(curContinuation, out long tickCountVal) ? tickCountVal : Environment.TickCount64; + Task.UpdateRuntimeAsyncTaskTicks(this, tickCount); Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); + Task.RemoveRuntimeAsyncContinuationTicks(curContinuation); + if (newContinuation != null) { + Task.SetRuntimeAsyncContinuationTicks(newContinuation, tickCount); newContinuation.Next = nextContinuation; HandleSuspended(); contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; - return; + break; } } catch (Exception ex) @@ -486,7 +524,7 @@ private unsafe void DispatchContinuations() ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); } - return; + break; } handlerContinuation.SetException(ex); @@ -495,6 +533,12 @@ private unsafe void DispatchContinuations() if (asyncDispatcherInfo.NextContinuation == null) { + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceOperationEnd(this.Id, AsyncCausalityStatus.Completed); + Task.RemoveFromActiveTasks(this); + Task.RemoveRuntimeAsyncTaskTicks(this); + } bool successfullySet = TrySetResult(m_result); contexts.Pop(); @@ -506,16 +550,20 @@ private unsafe void DispatchContinuations() ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); } - return; + break; } if (QueueContinuationFollowUpActionIfNecessary(asyncDispatcherInfo.NextContinuation)) { contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; - return; + break; } } + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } } private ref byte GetResultStorage() => ref Unsafe.As(ref m_result); @@ -614,6 +662,12 @@ private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuatio private static Task FinalizeTaskReturningThunk() { RuntimeAsyncTask result = new(); + if (Task.s_asyncDebuggingEnabled) + Task.AddToActiveTasks(result); + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceOperationBegin(result.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); + } result.HandleSuspended(); return result; } @@ -621,6 +675,12 @@ private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuatio private static Task FinalizeTaskReturningThunk() { RuntimeAsyncTask result = new(); + if (Task.s_asyncDebuggingEnabled) + Task.AddToActiveTasks(result); + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceOperationBegin(result.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); + } result.HandleSuspended(); return result; } 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 1e64b0c17a51e2..0921f3e7eb5515 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 @@ -123,7 +123,6 @@ public class Task : IAsyncResult, IDisposable // The delegate to invoke for a delegate-backed Task. // This field also may be used by async state machines to cache an Action. internal Delegate? m_action; - private protected object? m_stateObject; // A state object that can be optionally supplied, passed to action. internal TaskScheduler? m_taskScheduler; // The task scheduler this task runs under. @@ -180,6 +179,12 @@ internal enum TaskStateFlags // These methods are a way to access the dictionary both from this class and for other classes that also // activate dummy tasks. Specifically the AsyncTaskMethodBuilder and AsyncTaskMethodBuilder<> + + // Dictionary that relates Tasks to the tick count of the inflight task for debugging purposes + internal static System.Collections.Concurrent.ConcurrentDictionary? s_runtimeAsyncTaskTicks; + + // Dictionary that relates Continuations to their creation tick count for debugging purposes + internal static System.Collections.Concurrent.ConcurrentDictionary? s_runtimeAsyncContinuationTicks; internal static bool AddToActiveTasks(Task task) { Debug.Assert(task != null, "Null Task objects can't be added to the ActiveTasks collection"); @@ -211,6 +216,44 @@ internal static void RemoveFromActiveTasks(Task task) } } + internal static void SetRuntimeAsyncContinuationTicks(Continuation continuation, long tickCount) + { + if (s_asyncDebuggingEnabled) + { + s_runtimeAsyncContinuationTicks ??= new Collections.Concurrent.ConcurrentDictionary(ContinuationEqualityComparer.Instance); + s_runtimeAsyncContinuationTicks.TryAdd(continuation, tickCount); + } + } + + internal static bool GetRuntimeAsyncContinuationTicks(Continuation continuation, out long tickCount) + { + if (s_asyncDebuggingEnabled && s_runtimeAsyncContinuationTicks != null && s_runtimeAsyncContinuationTicks.TryGetValue(continuation, out tickCount)) + { + return true; + } + tickCount = 0; + return false; + } + + internal static void RemoveRuntimeAsyncContinuationTicks(Continuation continuation) + { + s_runtimeAsyncContinuationTicks?.Remove(continuation, out _); + } + + internal static void UpdateRuntimeAsyncTaskTicks(Task task, long inflightTickCount) + { + if (s_asyncDebuggingEnabled) + { + s_runtimeAsyncTaskTicks ??= []; + s_runtimeAsyncTaskTicks[task.Id] = inflightTickCount; + } + } + + internal static void RemoveRuntimeAsyncTaskTicks(Task task) + { + s_runtimeAsyncTaskTicks?.Remove(task.Id, out _); + } + // We moved a number of Task properties into this class. The idea is that in most cases, these properties never // need to be accessed during the life cycle of a Task, so we don't want to instantiate them every time. Once // one of these properties needs to be written, we will instantiate a ContingentProperties object and set From 788fb654a71f579f93a9027017ad6fa0e8bd0017 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 29 Jan 2026 10:37:25 -0800 Subject: [PATCH 2/2] TBD: remove unneeded updates --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 2 +- .../src/System/Threading/Tasks/Task.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 427ed676fa334e..46abe32bf85146 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -497,7 +497,7 @@ private unsafe void DispatchContinuations() if (newContinuation != null) { - Task.SetRuntimeAsyncContinuationTicks(newContinuation, tickCount); + Task.UpdateRuntimeAsyncContinuationTicks(newContinuation, tickCount); newContinuation.Next = nextContinuation; HandleSuspended(); contexts.Pop(); 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 0921f3e7eb5515..5c6af31a918a0d 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 @@ -182,9 +182,10 @@ internal enum TaskStateFlags // Dictionary that relates Tasks to the tick count of the inflight task for debugging purposes internal static System.Collections.Concurrent.ConcurrentDictionary? s_runtimeAsyncTaskTicks; - +#if !MONO // Dictionary that relates Continuations to their creation tick count for debugging purposes internal static System.Collections.Concurrent.ConcurrentDictionary? s_runtimeAsyncContinuationTicks; +#endif internal static bool AddToActiveTasks(Task task) { Debug.Assert(task != null, "Null Task objects can't be added to the ActiveTasks collection"); @@ -216,6 +217,7 @@ internal static void RemoveFromActiveTasks(Task task) } } +#if !MONO internal static void SetRuntimeAsyncContinuationTicks(Continuation continuation, long tickCount) { if (s_asyncDebuggingEnabled) @@ -235,6 +237,15 @@ internal static bool GetRuntimeAsyncContinuationTicks(Continuation continuation, return false; } + internal static void UpdateRuntimeAsyncContinuationTicks(Continuation continuation, long tickCount) + { + if (s_asyncDebuggingEnabled) + { + s_runtimeAsyncContinuationTicks ??= new Collections.Concurrent.ConcurrentDictionary(ContinuationEqualityComparer.Instance); + s_runtimeAsyncContinuationTicks[continuation] = tickCount; + } + } + internal static void RemoveRuntimeAsyncContinuationTicks(Continuation continuation) { s_runtimeAsyncContinuationTicks?.Remove(continuation, out _); @@ -253,6 +264,7 @@ internal static void RemoveRuntimeAsyncTaskTicks(Task task) { s_runtimeAsyncTaskTicks?.Remove(task.Id, out _); } +#endif // We moved a number of Task properties into this class. The idea is that in most cases, these properties never // need to be accessed during the life cycle of a Task, so we don't want to instantiate them every time. Once