From e6ba06a24ef439c94d1a2a53efc3951adc0036d1 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Wed, 25 Mar 2026 12:07:36 +0100 Subject: [PATCH 01/23] Add instrumentation probes to RuntimeAsyncTask. Add instrumentation probes to RuntimeAsyncTask DispatchContinuations loop. This is an extremely hot loop, in the centre of dispatching async continuations. https://github.com/dotnet/runtime/pull/123727 introduced a regression of ~7% when adding additional instrumentation for debugger/tpl into the loop. This commit uses generic value type specialization to setup an interface for the probes that JIT can use to create two versions of codegen for this hot method, most of the probes will be transformed to noop when profiling/debugging is disabled, introduce minimal overhead to critical hot code path. Dispatch loop checks on entry if instrumentation is enabled, if so it will switch to instrumented version of the function. It also checks on each completion of a continuation if instrumentation flags changed and if that is the case it will again switch to the instrumented version. In total it performs small set of instructions to "upgrade" the method on entry, and also a fast check against a static on each loop to support late attach scenarios. This change will make sure the dispatch continuations loop is protected from future performance regressions when more instrumentation gets added. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 681 +++++++++++++++--- .../src/System/Threading/Tasks/Task.cs | 120 ++- .../System/Threading/Tasks/TplEventSource.cs | 4 + 3 files changed, 669 insertions(+), 136 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 dc0fafd0a0e4cb..b0a120136142c1 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 @@ -1,14 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers.Binary; using System.Diagnostics; +using System.Diagnostics.Tracing; using System.Diagnostics.CodeAnalysis; -using System.Numerics; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Sources; @@ -317,6 +314,148 @@ private static void TransparentAwait(object o) AsyncSuspend(sentinelContinuation); } + internal static class RuntimeAsyncTaskInstrumentation + { +#if NATIVEAOT + internal static bool IsSupported { get; } = Debugger.IsSupported || EventSource.IsSupported; +#else + internal static bool IsSupported { get; } = true; +#endif + + internal enum Flags + { + Disabled = 0x0, + CreateAsyncContext = 0x1, + ResumeAsyncContext = 0x2, + SuspendAsyncContext = 0x4, + CompleteAsyncContext = 0x8, + UnwindAsyncException = 0x10, + ResumeAsyncMethod = 0x20, + CompleteAsyncMethod = 0x40, + AsyncProfiler = 0x10000, + Tpl = 0x20000, + Debugger = 0x40000 + } + + public static Flags ActiveFlags => _activeFlags; + + public static Flags UpdateAsyncProfilerFlags(Flags flags) + { + lock (_lock) + { + if (flags != Flags.Disabled) + { + flags |= Flags.AsyncProfiler; + } + + _asyncProfilerActiveFlags = flags; + _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; + + return _activeFlags; + } + } + + public static Flags UpdateTplFlags(EventSource tplEventSource) + { + Flags flags = Flags.Disabled; + + flags |= tplEventSource.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.AsyncCausalitySynchronousWork) ? + Flags.ResumeAsyncContext | + Flags.SuspendAsyncContext | + Flags.CompleteAsyncContext | + Flags.UnwindAsyncException : 0; + + flags |= tplEventSource.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.AsyncCausalityOperation) ? + Flags.CreateAsyncContext | + Flags.CompleteAsyncContext | + Flags.UnwindAsyncException : 0; + + lock (_lock) + { + if (flags != Flags.Disabled) + { + flags |= Flags.Tpl; + } + + _tplActiveFlags = flags; + _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; + + return _activeFlags; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Flags SyncAndGetActiveFlags() + { + Flags flags = _activeFlags; + if (IsEnabled.Debugger(flags) != Task.s_asyncDebuggingEnabled) + { + flags = SyncDebuggerFlagsSlow(flags); + } + return flags; + } + + private static Flags SyncDebuggerFlagsSlow(Flags flags) + { + if (IsEnabled.Debugger(flags) && !Task.s_asyncDebuggingEnabled) + { + return UpdateDebuggerFlags(Flags.Disabled); + } + else if (!IsEnabled.Debugger(flags) && Task.s_asyncDebuggingEnabled) + { + return UpdateDebuggerFlags(DebuggerFlags); + } + + return flags; + } + + private static Flags UpdateDebuggerFlags(Flags flags) + { + lock (_lock) + { + if (flags != Flags.Disabled) + { + flags |= Flags.Debugger; + } + + _debuggerActiveFlags = flags; + _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; + + return _activeFlags; + } + } + + public static class IsEnabled + { + public static bool CreateAsyncContext(Flags flags) => (Flags.CreateAsyncContext & flags) != 0; + public static bool ResumeAsyncContext(Flags flags) => (Flags.ResumeAsyncContext & flags) != 0; + public static bool SuspendAsyncContext(Flags flags) => (Flags.SuspendAsyncContext & flags) != 0; + public static bool CompleteAsyncContext(Flags flags) => (Flags.CompleteAsyncContext & flags) != 0; + public static bool UnwindAsyncException(Flags flags) => (Flags.UnwindAsyncException & flags) != 0; + public static bool ResumeAsyncMethod(Flags flags) => (Flags.ResumeAsyncMethod & flags) != 0; + public static bool CompleteAsyncMethod(Flags flags) => (Flags.CompleteAsyncMethod & flags) != 0; + public static bool AsyncProfiler(Flags flags) => (Flags.AsyncProfiler & flags) != 0; + public static bool Tpl(Flags flags) => (Flags.Tpl & flags) != 0; + public static bool Debugger(Flags flags) => (Flags.Debugger & flags) != 0; + public static bool DebuggerOrTpl(Flags flags) => ((Flags.Tpl | Flags.Debugger) & flags) != 0; + } + + private static Flags _activeFlags; + + private static Flags _asyncProfilerActiveFlags; + + private static Flags _tplActiveFlags; + + private static Flags _debuggerActiveFlags; + + private static readonly object _lock = new object(); + + private const Flags DebuggerFlags = + Flags.CreateAsyncContext | Flags.SuspendAsyncContext | + Flags.CompleteAsyncContext | Flags.UnwindAsyncException | + Flags.ResumeAsyncMethod | Flags.CompleteAsyncMethod; + } + // Represents execution of a chain of suspended and resuming runtime // async functions. private sealed class RuntimeAsyncTask : Task, ITaskCompletionAction @@ -327,18 +466,18 @@ public RuntimeAsyncTask() // Ensure that state object isn't published out for others to see. Debug.Assert((m_stateFlags & (int)InternalTaskOptions.PromiseTask) != 0, "Expected state flags to already be configured."); Debug.Assert(m_stateObject is null, "Expected to be able to use the state object field for Continuation."); - m_action = DispatchContinuations; + m_action = DispatchContinuations; m_stateFlags |= (int)InternalTaskOptions.HiddenState; } internal override void ExecuteFromThreadPool(Thread threadPoolThread) { - DispatchContinuations(); + DispatchContinuations(); } void ITaskCompletionAction.Invoke(Task completingTask) { - DispatchContinuations(); + DispatchContinuations(); } bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true; @@ -358,7 +497,7 @@ private void SetContinuationState(Continuation value) m_stateObject = value; } - internal void HandleSuspended() + internal bool HandleSuspended() { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; @@ -391,18 +530,6 @@ internal void HandleSuspended() SetContinuationState(headContinuation); - Continuation? nc = headContinuation; - if (Task.s_asyncDebuggingEnabled) - { - long timestamp = Stopwatch.GetTimestamp(); - while (nc != null) - { - // On suspension we set timestamp for all continuations that have not yet had it set. - Task.SetRuntimeAsyncContinuationTimestamp(nc, timestamp); - nc = nc.Next; - } - } - try { if (critNotifier != null) @@ -461,23 +588,15 @@ internal void HandleSuspended() Debug.Assert(notifier != null); notifier.OnCompleted(GetContinuationAction()); } + + return true; } catch (Exception ex) { - if (Task.s_asyncDebuggingEnabled) - { - Task.RemoveFromActiveTasks(this); - Task.RemoveRuntimeAsyncTaskTimestamp(this); - Continuation? nextCont = headContinuation; - while (nextCont != null) - { - Task.RemoveRuntimeAsyncContinuationTimestamp(nextCont); - nextCont = nextCont.Next; - } - } - Task.ThrowAsync(ex, targetContext: null); } + + return false; } #pragma warning disable CA1822 // Mark members as static @@ -487,23 +606,368 @@ public void NotifyDebuggerOfRuntimeAsyncState() } #pragma warning restore CA1822 + internal interface IRuntimeAsyncTaskInstrumentation + { + static abstract bool InstrumentEntryPoint { get; } + + static abstract bool InstrumentCheckPoint { get; } + + static abstract RuntimeAsyncTaskInstrumentation.Flags Flags { get; } + + static abstract void InitAsyncDispatcherInfo(RuntimeAsyncTask task, ref AsyncDispatcherInfo info); + + static abstract void CreateRuntimeAsyncContext(RuntimeAsyncTask task); + + static abstract void ResumeRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags); + + static abstract void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation); + + static abstract void SuspendRuntimeAsyncContext(RuntimeAsyncTask task, ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation); + + static abstract void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags); + + static abstract void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames); + + static abstract void UnwindRuntimeAsyncMethodHandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames); + + static abstract Continuation? ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, ref byte resultLoc); + + static abstract void CompleteRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation); + } + + internal struct DisableRuntimeAsyncTaskInstrumentation : IRuntimeAsyncTaskInstrumentation + { + public static bool InstrumentEntryPoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => RuntimeAsyncTaskInstrumentation.IsSupported + && RuntimeAsyncTaskInstrumentation.SyncAndGetActiveFlags() != RuntimeAsyncTaskInstrumentation.Flags.Disabled; + } + + public static bool InstrumentCheckPoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => RuntimeAsyncTaskInstrumentation.IsSupported + && RuntimeAsyncTaskInstrumentation.ActiveFlags != RuntimeAsyncTaskInstrumentation.Flags.Disabled; + } + + public static RuntimeAsyncTaskInstrumentation.Flags Flags => RuntimeAsyncTaskInstrumentation.Flags.Disabled; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitAsyncDispatcherInfo(RuntimeAsyncTask task, ref AsyncDispatcherInfo info) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CreateRuntimeAsyncContext(RuntimeAsyncTask task) { task.HandleSuspended(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ResumeRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SuspendRuntimeAsyncContext(RuntimeAsyncTask task, ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation) + { + task.HandleSuspended(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnwindRuntimeAsyncMethodHandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Continuation? ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, ref byte resultLoc) + { + unsafe + { + return curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CompleteRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation) { } + } + + internal struct EnableRuntimeAsyncTaskInstrumentation : IRuntimeAsyncTaskInstrumentation + { + public static bool InstrumentEntryPoint => false; + + public static bool InstrumentCheckPoint => false; + + public static RuntimeAsyncTaskInstrumentation.Flags Flags => RuntimeAsyncTaskInstrumentation.ActiveFlags; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitAsyncDispatcherInfo(RuntimeAsyncTask task, ref AsyncDispatcherInfo info) + { + info.CurrentTask = task; + } + + public static void CreateRuntimeAsyncContext(RuntimeAsyncTask task) + { + RuntimeAsyncTaskInstrumentation.Flags flags = Flags; + if (RuntimeAsyncTaskInstrumentation.IsEnabled.CreateAsyncContext(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + { + task.NotifyDebuggerOfRuntimeAsyncState(); + AddToActiveTasks(task); + } + + HandleSuspended(task, flags); + + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); + } + + return; + } + + task.HandleSuspended(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ResumeRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.ResumeAsyncContext(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags) && info.CurrentTask != null) + { + TplEventSource.Log.TraceSynchronousWorkBegin(info.CurrentTask.Id, CausalitySynchronousWork.Execution); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.SuspendAsyncContext(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags) && info.NextContinuation != null) + { + TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); + } + + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SuspendRuntimeAsyncContext(RuntimeAsyncTask task, ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.SuspendAsyncContext(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + { + ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); + } + + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + + HandleSuspended(task, flags, newContinuation); + return; + } + + task.HandleSuspended(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.CompleteAsyncContext(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.DebuggerOrTpl(flags)) + { + CompleteRuntimeAsyncContext(info.CurrentTask, flags); + } + } + } + + public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.UnwindAsyncException(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.DebuggerOrTpl(flags)) + { + UnwindRuntimeAsyncMethodUnhandledException(info.CurrentTask, flags, ex, curContinuation, unwindedFrames); + } + } + } + + public static void UnwindRuntimeAsyncMethodHandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.UnwindAsyncException(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + { + RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Continuation? ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, ref byte resultLoc) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.ResumeAsyncMethod(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags) && info.CurrentTask != null) + { + UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); + } + } + + unsafe + { + return curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CompleteRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.CompleteAsyncMethod(flags)) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + { + RemoveRuntimeAsyncContinuationTimestamp(curContinuation); + } + } + } + + private static void CompleteRuntimeAsyncContext(Task? task, RuntimeAsyncTaskInstrumentation.Flags flags) + { + if (task != null) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + { + RemoveRuntimeAsyncTask(task); + } + + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + } + + private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint _) + { + if (task != null) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + { + RemoveRuntimeAsyncTask(task, curContinuation); + } + + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void HandleSuspended(RuntimeAsyncTask task, RuntimeAsyncTaskInstrumentation.Flags flags) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + { + DebuggerHandleSuspended(task); + } + else + { + task.HandleSuspended(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void HandleSuspended(RuntimeAsyncTask task, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation newContinuation) + { + if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + { + DebuggerHandleSuspended(task, newContinuation); + } + else + { + task.HandleSuspended(); + } + } + + private static void DebuggerHandleSuspended(RuntimeAsyncTask task, Continuation newContinuation) + { + ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; + Continuation? nc = state.SentinelContinuation!.Next; + + if (nc != null) + { + TryAddRuntimeAsyncContinuationChainTimestamps(nc, newContinuation); + } + + if (!task.HandleSuspended()) + { + RemoveRuntimeAsyncTask(task); + } + } + + private static void DebuggerHandleSuspended(RuntimeAsyncTask task) + { + ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; + Continuation? nc = state.SentinelContinuation!.Next; + + if (nc != null) + { + TryAddRuntimeAsyncContinuationChainTimestamps(nc); + } + + if (!task.HandleSuspended()) + { + RemoveRuntimeAsyncTask(task); + } + } + } + + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.NoInlining)] + [StackTraceHidden] + private void InstrumentedDispatchContinuations() + { + DispatchContinuations(); + } + [StackTraceHidden] - private unsafe void DispatchContinuations() + private unsafe void DispatchContinuations() where TRuntimeAsyncTaskInstrumentation : struct, IRuntimeAsyncTaskInstrumentation { + if (TRuntimeAsyncTaskInstrumentation.InstrumentEntryPoint) + { + InstrumentedDispatchContinuations(); + return; + } + + RuntimeAsyncTaskInstrumentation.Flags flags = TRuntimeAsyncTaskInstrumentation.Flags; + ExecutionAndSyncBlockStore contexts = default; contexts.Push(); AsyncDispatcherInfo asyncDispatcherInfo; asyncDispatcherInfo.Next = AsyncDispatcherInfo.t_current; asyncDispatcherInfo.NextContinuation = MoveContinuationState(); - asyncDispatcherInfo.CurrentTask = this; AsyncDispatcherInfo.t_current = &asyncDispatcherInfo; - bool isTplEnabled = TplEventSource.Log.IsEnabled(); - if (isTplEnabled) - { - TplEventSource.Log.TraceSynchronousWorkBegin(this.Id, CausalitySynchronousWork.Execution); - } + TRuntimeAsyncTaskInstrumentation.InitAsyncDispatcherInfo(this, ref asyncDispatcherInfo); + + TRuntimeAsyncTaskInstrumentation.ResumeRuntimeAsyncContext(ref asyncDispatcherInfo, flags); while (true) { @@ -515,43 +979,29 @@ private unsafe void DispatchContinuations() asyncDispatcherInfo.NextContinuation = nextContinuation; ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); - long timestamp = 0; - if (Task.s_asyncDebuggingEnabled) - { - timestamp = Task.GetRuntimeAsyncContinuationTimestamp(curContinuation, out long timestampVal) ? timestampVal : Stopwatch.GetTimestamp(); - // we have dequeued curContinuation; update task tick info so that we can track its start time from a debugger - Task.UpdateRuntimeAsyncTaskTimestamp(this, timestamp); - } - Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); - if (Task.s_asyncDebuggingEnabled) - { - Task.RemoveRuntimeAsyncContinuationTimestamp(curContinuation); - } + Continuation? newContinuation = TRuntimeAsyncTaskInstrumentation.ResumeRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation, ref resultLoc); if (newContinuation != null) { - // we have a new Continuation that belongs to the same logical invocation as the previous; propagate debug info from previous continuation - if (Task.s_asyncDebuggingEnabled) - { - Task.SetRuntimeAsyncContinuationTimestamp(newContinuation, timestamp); - } newContinuation.Next = nextContinuation; - HandleSuspended(); + TRuntimeAsyncTaskInstrumentation.SuspendRuntimeAsyncContext(this, ref asyncDispatcherInfo, flags, curContinuation, newContinuation); + contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; - break; + return; } + + TRuntimeAsyncTaskInstrumentation.CompleteRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation); } catch (Exception ex) { - if (Task.s_asyncDebuggingEnabled) - { - Task.RemoveRuntimeAsyncContinuationTimestamp(curContinuation); - } - Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation, ex); + uint unwindedFrames = 1; // Count current frame. + Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation, ex, ref unwindedFrames); if (handlerContinuation == null) { + TRuntimeAsyncTaskInstrumentation.UnwindRuntimeAsyncMethodUnhandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); + // Tail of AsyncTaskMethodBuilderT.SetException bool successfullySet = ex is OperationCanceledException oce ? TrySetCanceled(oce.CancellationToken, oce) : @@ -561,40 +1011,24 @@ private unsafe void DispatchContinuations() AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; - if (isTplEnabled) - { - TplEventSource.Log.TraceOperationEnd(this.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); - } - - if (Task.s_asyncDebuggingEnabled) - { - Task.RemoveFromActiveTasks(this); - Task.RemoveRuntimeAsyncTaskTimestamp(this); - } - if (!successfullySet) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); } - break; + return; } + TRuntimeAsyncTaskInstrumentation.UnwindRuntimeAsyncMethodHandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); + handlerContinuation.SetException(ex); asyncDispatcherInfo.NextContinuation = handlerContinuation; } if (asyncDispatcherInfo.NextContinuation == null) { - if (isTplEnabled) - { - TplEventSource.Log.TraceOperationEnd(this.Id, AsyncCausalityStatus.Completed); - } - if (Task.s_asyncDebuggingEnabled) - { - Task.RemoveFromActiveTasks(this); - Task.RemoveRuntimeAsyncTaskTimestamp(this); - } + TRuntimeAsyncTaskInstrumentation.CompleteRuntimeAsyncContext(ref asyncDispatcherInfo, flags); + bool successfullySet = TrySetResult(m_result); contexts.Pop(); @@ -606,25 +1040,38 @@ private unsafe void DispatchContinuations() ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); } - break; + return; } if (QueueContinuationFollowUpActionIfNecessary(asyncDispatcherInfo.NextContinuation)) { + TRuntimeAsyncTaskInstrumentation.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); + contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; - break; + return; } - } - if (isTplEnabled) - { - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + + if (TRuntimeAsyncTaskInstrumentation.InstrumentCheckPoint) + { + SetContinuationState(asyncDispatcherInfo.NextContinuation); + + TRuntimeAsyncTaskInstrumentation.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); + + contexts.Pop(); + AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; + + InstrumentedDispatchContinuations(); + return; + } + + flags = TRuntimeAsyncTaskInstrumentation.Flags; } } private ref byte GetResultStorage() => ref Unsafe.As(ref m_result); - private static unsafe Continuation? UnwindToPossibleHandler(Continuation? continuation, Exception ex) + private static unsafe Continuation? UnwindToPossibleHandler(Continuation? continuation, Exception ex, ref uint unwindedFrames) { while (true) { @@ -639,11 +1086,9 @@ private unsafe void DispatchContinuations() } if (continuation == null || continuation.HasException()) return continuation; - if (Task.s_asyncDebuggingEnabled) - { - Task.RemoveRuntimeAsyncContinuationTimestamp(continuation); - } + continuation = continuation.Next; + unwindedFrames++; } } @@ -713,16 +1158,36 @@ private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuatio private static readonly SendOrPostCallback s_postCallback = static state => { Debug.Assert(state is RuntimeAsyncTask); - ((RuntimeAsyncTask)state).DispatchContinuations(); + ((RuntimeAsyncTask)state).DispatchContinuations(); }; private static readonly Action s_runContinuationAction = static state => { Debug.Assert(state is RuntimeAsyncTask); - ((RuntimeAsyncTask)state).DispatchContinuations(); + ((RuntimeAsyncTask)state).DispatchContinuations(); }; } + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.NoInlining)] + [StackTraceHidden] + private static void InstrumentedFinalizeRuntimeAsyncTask(RuntimeAsyncTask task) + { + RuntimeAsyncTask.EnableRuntimeAsyncTaskInstrumentation.CreateRuntimeAsyncContext(task); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void FinalizeRuntimeAsyncTask(RuntimeAsyncTask task) + { + if (RuntimeAsyncTask.DisableRuntimeAsyncTaskInstrumentation.InstrumentEntryPoint) + { + InstrumentedFinalizeRuntimeAsyncTask(task); + return; + } + + RuntimeAsyncTask.DisableRuntimeAsyncTaskInstrumentation.CreateRuntimeAsyncContext(task); + } + // Change return type to RuntimeAsyncTask -- no benefit since this is used for Task returning thunks only #pragma warning disable CA1859 // When a Task-returning thunk gets a continuation result @@ -730,32 +1195,14 @@ private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuatio private static Task FinalizeTaskReturningThunk() { RuntimeAsyncTask result = new(); - if (Task.s_asyncDebuggingEnabled) - { - result.NotifyDebuggerOfRuntimeAsyncState(); - Task.AddToActiveTasks(result); - } - if (TplEventSource.Log.IsEnabled()) - { - TplEventSource.Log.TraceOperationBegin(result.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); - } - result.HandleSuspended(); + FinalizeRuntimeAsyncTask(result!); return result; } private static Task FinalizeTaskReturningThunk() { RuntimeAsyncTask result = new(); - if (Task.s_asyncDebuggingEnabled) - { - result.NotifyDebuggerOfRuntimeAsyncState(); - Task.AddToActiveTasks(result); - } - if (TplEventSource.Log.IsEnabled()) - { - TplEventSource.Log.TraceOperationBegin(result.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); - } - result.HandleSuspended(); + FinalizeRuntimeAsyncTask(result!); 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 520c0642c4e1fd..7cfbc3c1d9a172 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 @@ -218,37 +218,70 @@ internal static void RemoveFromActiveTasks(Task task) } #if !MONO - internal static void SetRuntimeAsyncContinuationTimestamp(Continuation continuation, long timestamp) + private static Dictionary GetOrCreateRuntimeAsyncContinuationTimestamps() { - Dictionary continuationTimestamps = - Volatile.Read(ref s_runtimeAsyncContinuationTimestamps) ?? + return Volatile.Read(ref s_runtimeAsyncContinuationTimestamps) ?? Interlocked.CompareExchange(ref s_runtimeAsyncContinuationTimestamps, new Dictionary(ReferenceEqualityComparer.Instance), null) ?? - s_runtimeAsyncContinuationTimestamps; + s_runtimeAsyncContinuationTimestamps!; + } + + private static Dictionary GetOrCreateRuntimeAsyncTaskTimestamps() + { + return Volatile.Read(ref s_runtimeAsyncTaskTimestamps) ?? + Interlocked.CompareExchange(ref s_runtimeAsyncTaskTimestamps, new Dictionary(), null) ?? + s_runtimeAsyncTaskTimestamps; + } + internal static void ReplaceOrAddRuntimeAsyncContinuationTimestamp(Continuation curContinuation, Continuation newContinuation) + { + var continuationTimestamps = GetOrCreateRuntimeAsyncContinuationTimestamps(); lock (continuationTimestamps) { - continuationTimestamps.TryAdd(continuation, timestamp); + if (continuationTimestamps.TryGetValue(curContinuation, out long timestampVal)) + { + continuationTimestamps.Remove(curContinuation); + continuationTimestamps[newContinuation] = timestampVal; + } + else + { + continuationTimestamps[newContinuation] = Stopwatch.GetTimestamp(); + } } } - internal static bool GetRuntimeAsyncContinuationTimestamp(Continuation continuation, out long timestamp) + internal static void TryAddRuntimeAsyncContinuationChainTimestamps(Continuation continuationChain) { - Dictionary? continuationTimestamps = s_runtimeAsyncContinuationTimestamps; - if (continuationTimestamps is null) + var continuationTimestamps = GetOrCreateRuntimeAsyncContinuationTimestamps(); + long timestamp = Stopwatch.GetTimestamp(); + lock (continuationTimestamps) { - timestamp = 0; - return false; + Continuation? nc = continuationChain; + while (nc != null) + { + continuationTimestamps.TryAdd(nc, timestamp); + nc = nc.Next; + } } + } + internal static void TryAddRuntimeAsyncContinuationChainTimestamps(Continuation continuationChain, Continuation timestampSource) + { + var continuationTimestamps = GetOrCreateRuntimeAsyncContinuationTimestamps(); lock (continuationTimestamps) { - return continuationTimestamps.TryGetValue(continuation, out timestamp); + long timestamp = continuationTimestamps.TryGetValue(timestampSource, out long timestampVal) ? timestampVal : Stopwatch.GetTimestamp(); + Continuation? nc = continuationChain; + while (nc != null) + { + continuationTimestamps.TryAdd(nc, timestamp); + nc = nc.Next; + } } } internal static void RemoveRuntimeAsyncContinuationTimestamp(Continuation continuation) { - Dictionary? continuationTimestamps = s_runtimeAsyncContinuationTimestamps; + var continuationTimestamps = s_runtimeAsyncContinuationTimestamps; if (continuationTimestamps is null) return; @@ -258,21 +291,51 @@ internal static void RemoveRuntimeAsyncContinuationTimestamp(Continuation contin } } - internal static void UpdateRuntimeAsyncTaskTimestamp(Task task, long inflightTimestamp) + internal static void RemoveRuntimeAsyncContinuationChainTimestamps(Continuation continuation, uint count) { - Dictionary runtimeAsyncTaskTimestamps = - Volatile.Read(ref s_runtimeAsyncTaskTimestamps) ?? - Interlocked.CompareExchange(ref s_runtimeAsyncTaskTimestamps, new Dictionary(), null) ?? - s_runtimeAsyncTaskTimestamps; + var continuationTimestamps = s_runtimeAsyncContinuationTimestamps; + if (continuationTimestamps is null) + return; + lock (continuationTimestamps) + { + Continuation? nc = continuation; + for (uint i = 0; i < count && nc != null; i++) + { + continuationTimestamps.Remove(nc); + nc = nc.Next; + } + } + } + + internal static void UpdateRuntimeAsyncTaskTimestamp(Task task, Continuation timestampSource) + { + long timestamp = 0; + var continuationTimestamps = s_runtimeAsyncContinuationTimestamps; + if (continuationTimestamps != null) + { + lock (continuationTimestamps) + { + continuationTimestamps.TryGetValue(timestampSource, out timestamp); + } + } + + if (timestamp == 0) + { + timestamp = Stopwatch.GetTimestamp(); + } + + var runtimeAsyncTaskTimestamps = GetOrCreateRuntimeAsyncTaskTimestamps(); lock (runtimeAsyncTaskTimestamps) { - runtimeAsyncTaskTimestamps[task.Id] = inflightTimestamp; + runtimeAsyncTaskTimestamps[task.Id] = timestamp; } } - internal static void RemoveRuntimeAsyncTaskTimestamp(Task task) + internal static void RemoveRuntimeAsyncTask(Task task) { + RemoveFromActiveTasks(task); + Dictionary? runtimeAsyncTaskTimestamps = s_runtimeAsyncTaskTimestamps; if (runtimeAsyncTaskTimestamps is null) return; @@ -282,6 +345,25 @@ internal static void RemoveRuntimeAsyncTaskTimestamp(Task task) runtimeAsyncTaskTimestamps.Remove(task.Id); } } + + internal static void RemoveRuntimeAsyncTask(Task task, Continuation continuationChain) + { + RemoveRuntimeAsyncTask(task); + + Dictionary? continuationTimestamps = s_runtimeAsyncContinuationTimestamps; + if (continuationTimestamps is null) + return; + + lock (continuationTimestamps) + { + Continuation? nc = continuationChain; + while (nc != null) + { + continuationTimestamps.Remove(nc); + nc = nc.Next; + } + } + } #endif // We moved a number of Task properties into this class. The idea is that in most cases, these properties never diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs index 5418d665ab43b1..2b7a4e83454c5a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs @@ -34,6 +34,10 @@ protected override void OnEventCommand(EventCommandEventArgs command) Debug = IsEnabled(EventLevel.Informational, Keywords.Debug); DebugActivityId = IsEnabled(EventLevel.Informational, Keywords.DebugActivityId); + +#if CORECLR || NATIVEAOT + AsyncHelpers.RuntimeAsyncTaskInstrumentation.UpdateTplFlags(this); +#endif } /// From cebcad6009b42916e61fab651678d1630f7e6a4c Mon Sep 17 00:00:00 2001 From: lateralusX Date: Wed, 25 Mar 2026 13:08:31 +0100 Subject: [PATCH 02/23] Adding additional tests. --- .../RuntimeAsyncTests.cs | 375 +++++++++++++++++- 1 file changed, 366 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index 8557a26bc660e0..38b3b2db38a300 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.Tracing; using System.Reflection; using Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.XUnitExtensions; @@ -13,34 +15,389 @@ public class RuntimeAsyncTests { private static bool IsRemoteExecutorAndRuntimeAsyncSupported => RemoteExecutor.IsSupported && PlatformDetection.IsRuntimeAsyncSupported; + // NOTE: This depends on private implementation details generally only used by the debugger. + // If those ever change, this test will need to be updated as well. + private static FieldInfo AsyncDebuggingEnabledField => typeof(Task).GetField("s_asyncDebuggingEnabled", BindingFlags.NonPublic | BindingFlags.Static); + private static FieldInfo TaskTimestampsField => typeof(Task).GetField("s_runtimeAsyncTaskTimestamps", BindingFlags.NonPublic | BindingFlags.Static); + private static FieldInfo ContinuationTimestampsField => typeof(Task).GetField("s_runtimeAsyncContinuationTimestamps", BindingFlags.NonPublic | BindingFlags.Static); + private static FieldInfo ActiveTasksField => typeof(Task).GetField("s_currentActiveTasks", BindingFlags.NonPublic | BindingFlags.Static); + + private static int GetTaskTimestampCount() => + TaskTimestampsField.GetValue(null) is Dictionary dict ? dict.Count : 0; + + private static int GetContinuationTimestampCount() => + ContinuationTimestampsField.GetValue(null) is Dictionary dict ? dict.Count : 0; + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] static async Task Func() { - await Task.Delay(1); await Task.Yield(); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task FuncThatThrows() + { + await DeepThrow1(); + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task DeepThrow1() + { + await DeepThrow2(); + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task DeepThrow2() + { + await Task.Yield(); + throw new InvalidOperationException("test exception"); + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task FuncWithResult() + { + await Task.Yield(); + return 42; + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task OuterFuncThatCatches() + { + try + { + await MiddleFuncThatPropagates(); + } + catch (InvalidOperationException) + { + } + await Task.Yield(); + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task MiddleFuncThatPropagates() + { + await InnerFuncThatThrows(); + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task InnerFuncThatThrows() + { + await Task.Yield(); + throw new InvalidOperationException("inner exception"); + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task FuncThatCancels(CancellationToken ct) + { + await Task.Yield(); + ct.ThrowIfCancellationRequested(); + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task FuncThatWaitsTwice(TaskCompletionSource tcs1, TaskCompletionSource tcs2) + { + await tcs1.Task; + await tcs2.Task; + } + + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] + static async Task FuncThatInspectsContinuationTimestamps(TaskCompletionSource tcs, Action callback) + { + await tcs.Task; + callback(); + } + + static void ValidateTimestampsCleared() + { + // some other tasks may be created by the runtime, so this is just using a reasonably small upper bound + Assert.InRange(GetTaskTimestampCount(), 0, 10); + Assert.InRange(GetContinuationTimestampCount(), 0, 10); + } + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] public void RuntimeAsync_TaskCompleted() { RemoteExecutor.Invoke(async () => { + AsyncDebuggingEnabledField.SetValue(null, true); - // NOTE: This depends on private implementation details generally only used by the debugger. - // If those ever change, this test will need to be updated as well. + for (int i = 0; i < 1000; i++) + { + await Func(); + } - typeof(Task).GetField("s_asyncDebuggingEnabled", BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, true); + ValidateTimestampsCleared(); + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_ExceptionCleanup() + { + RemoteExecutor.Invoke(async () => + { + AsyncDebuggingEnabledField.SetValue(null, true); for (int i = 0; i < 1000; i++) { - await Func(); + try + { + await FuncThatThrows(); + } + catch (InvalidOperationException) + { + } + } + + ValidateTimestampsCleared(); + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_DebuggerDetach() + { + RemoteExecutor.Invoke(async () => + { + AsyncDebuggingEnabledField.SetValue(null, true); + + // Run one task to ensure lazy-initialized collections are created + await Func(); + + var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); + + // Use an in-flight task to deterministically verify tracking is active + var tcs = new TaskCompletionSource(); + Task inflight = FuncThatWaitsTwice(tcs, new TaskCompletionSource()); + + lock (activeTasks) + { + Assert.True(activeTasks.ContainsKey(inflight.Id), + "Expected in-flight task to be tracked while debugger is attached"); + } + + // Complete the first await so it resumes and sets a task timestamp + tcs.SetResult(); + var taskTimestamps = (Dictionary)TaskTimestampsField.GetValue(null); + + bool seenTimestamp = SpinWait.SpinUntil(() => + { + lock (taskTimestamps) + { + return taskTimestamps.ContainsKey(inflight.Id); + } + }, timeout: TimeSpan.FromSeconds(5)); + + Assert.True(seenTimestamp, "Timed out waiting for task timestamp"); + + // Simulate debugger detach — the flag sync should detect the mismatch + // and disable the Debugger flags without crashing. + // Timestamps are not cleared (matches existing behavior). + AsyncDebuggingEnabledField.SetValue(null, false); + + // Run one task to trigger the flag sync that detects the mismatch + await Func(); + + // Now start a new in-flight task after detach — it should NOT be tracked + var tcsPost = new TaskCompletionSource(); + Task postDetach = FuncThatWaitsTwice(tcsPost, new TaskCompletionSource()); + + lock (activeTasks) + { + Assert.False(activeTasks.ContainsKey(postDetach.Id), + "Expected in-flight task NOT to be tracked after debugger detach"); + } + + lock (taskTimestamps) + { + Assert.False(taskTimestamps.ContainsKey(postDetach.Id), + "Expected no task timestamp for post-detach in-flight task"); + } + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_ValueTypeResult() + { + RemoteExecutor.Invoke(async () => + { + AsyncDebuggingEnabledField.SetValue(null, true); + + for (int i = 0; i < 1000; i++) + { + int result = await FuncWithResult(); + Assert.Equal(42, result); + } + + ValidateTimestampsCleared(); + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_HandledExceptionPartialUnwind() + { + RemoteExecutor.Invoke(async () => + { + AsyncDebuggingEnabledField.SetValue(null, true); + + for (int i = 0; i < 1000; i++) + { + await OuterFuncThatCatches(); + } + + ValidateTimestampsCleared(); + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_CancellationCleanup() + { + RemoteExecutor.Invoke(async () => + { + AsyncDebuggingEnabledField.SetValue(null, true); + + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + for (int i = 0; i < 1000; i++) + { + try + { + await FuncThatCancels(cts.Token); + } + catch (OperationCanceledException) + { + } + } + + ValidateTimestampsCleared(); + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_TimestampsTrackedWhileInFlight() + { + RemoteExecutor.Invoke(async () => + { + AsyncDebuggingEnabledField.SetValue(null, true); + + // Run one task to ensure lazy-initialized collections are created + await Func(); + + var tcs1 = new TaskCompletionSource(); + var tcs2 = new TaskCompletionSource(); + Task inflight = FuncThatWaitsTwice(tcs1, tcs2); + + // Task is suspended on tcs1 — should be in active tasks + var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); + + lock (activeTasks) + { + Assert.True(activeTasks.ContainsKey(inflight.Id), "Expected suspended task to be in s_currentActiveTasks"); + } + + // Resume from first suspension — this triggers DispatchContinuations which sets timestamps + tcs1.SetResult(); + + // Poll until the dispatch loop has resumed and re-suspended on tcs2, + // which sets the task timestamp via ResumeRuntimeAsyncMethod. + var taskTimestamps = (Dictionary)TaskTimestampsField.GetValue(null); + + bool seenTimestamp = SpinWait.SpinUntil(() => + { + lock (taskTimestamps) + { + return taskTimestamps.ContainsKey(inflight.Id); + } + }, timeout: TimeSpan.FromSeconds(5)); + + Assert.True(seenTimestamp, "Timed out waiting for task timestamp to appear after resume"); + + // Now the task has been through one resume cycle and is suspended again on tcs2. + lock (taskTimestamps) + { + Assert.True(taskTimestamps[inflight.Id] > 0, "Expected non-zero task timestamp"); + } + + // Complete the task + tcs2.SetResult(); + await inflight; + + // After completion the task should be removed from all collections + lock (activeTasks) + { + Assert.False(activeTasks.ContainsKey(inflight.Id), "Expected completed task to be removed from s_currentActiveTasks"); + } + + lock (taskTimestamps) + { + Assert.False(taskTimestamps.ContainsKey(inflight.Id), "Expected task timestamp to be removed after completion"); + } + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_ContinuationTimestampObservedDuringResume() + { + RemoteExecutor.Invoke(async () => + { + AsyncDebuggingEnabledField.SetValue(null, true); + + bool continuationTimestampObserved = false; + + var tcs = new TaskCompletionSource(); + Task inflight = FuncThatInspectsContinuationTimestamps(tcs, () => + { + // This callback runs inside the resumed async method body, after + // ResumeRuntimeAsyncMethod but before SuspendRuntimeAsyncContext/CompleteRuntimeAsyncMethod. + // The continuation timestamp for the current continuation should still be in the dictionary. + var continuationTimestamps = (Dictionary)ContinuationTimestampsField.GetValue(null); + lock (continuationTimestamps) + { + continuationTimestampObserved = continuationTimestamps.Count > 0; + } + }); + + tcs.SetResult(); + await inflight; + + Assert.True(continuationTimestampObserved, "Expected continuation timestamp to be present during resumed method execution"); + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_TplEvents() + { + RemoteExecutor.Invoke(() => + { + const int TraceOperationBeginId = 14; + const int TraceOperationEndId = 15; + const int TraceSynchronousWorkBeginId = 17; + const int TraceSynchronousWorkEndId = 18; + + var events = new ConcurrentQueue(); + using (var listener = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose)) + { + listener.RunWithCallback(events.Enqueue, () => + { + for (int i = 0; i < 10; i++) + { + Func().GetAwaiter().GetResult(); + } + }); } - int taskCount = ((Dictionary)typeof(Task).GetField("s_runtimeAsyncTaskTimestamps", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null)).Count; - int continuationCount = ((Dictionary)typeof(Task).GetField("s_runtimeAsyncContinuationTimestamps", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null)).Count; - Assert.InRange(taskCount, 0, 10); // some other tasks may be created by the runtime, so this is just using a reasonably small upper bound - Assert.InRange(continuationCount, 0, 10); + Assert.Contains(events, e => e.EventId == TraceOperationBeginId); + Assert.Contains(events, e => e.EventId == TraceOperationEndId); + Assert.Contains(events, e => e.EventId == TraceSynchronousWorkBeginId); + Assert.Contains(events, e => e.EventId == TraceSynchronousWorkEndId); }).Dispose(); } } From a8620c85de73723df9ac10f5a55d57f0911c2c29 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Wed, 25 Mar 2026 13:55:32 +0100 Subject: [PATCH 03/23] Restore FinalizeTaskReturningThunk pattern. --- .../System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 3 +-- 1 file changed, 1 insertion(+), 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 b0a120136142c1..f3f0862fa552d0 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 @@ -718,13 +718,12 @@ public static void CreateRuntimeAsyncContext(RuntimeAsyncTask task) AddToActiveTasks(task); } - HandleSuspended(task, flags); - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) { TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); } + HandleSuspended(task, flags); return; } From f1f6888984b32f71230632630c3f59fb3c6f22b1 Mon Sep 17 00:00:00 2001 From: Johan Lorensson Date: Wed, 25 Mar 2026 15:53:05 +0100 Subject: [PATCH 04/23] Update src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Runtime.CompilerServices/RuntimeAsyncTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index 38b3b2db38a300..29d369a443638d 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -191,7 +191,7 @@ public void RuntimeAsync_DebuggerDetach() Assert.True(seenTimestamp, "Timed out waiting for task timestamp"); - // Simulate debugger detach — the flag sync should detect the mismatch + // Simulate debugger detach — the flag sync should detect the mismatch // and disable the Debugger flags without crashing. // Timestamps are not cleared (matches existing behavior). AsyncDebuggingEnabledField.SetValue(null, false); @@ -199,7 +199,7 @@ public void RuntimeAsync_DebuggerDetach() // Run one task to trigger the flag sync that detects the mismatch await Func(); - // Now start a new in-flight task after detach — it should NOT be tracked + // Now start a new in-flight task after detach - it should NOT be tracked var tcsPost = new TaskCompletionSource(); Task postDetach = FuncThatWaitsTwice(tcsPost, new TaskCompletionSource()); @@ -293,7 +293,7 @@ public void RuntimeAsync_TimestampsTrackedWhileInFlight() var tcs2 = new TaskCompletionSource(); Task inflight = FuncThatWaitsTwice(tcs1, tcs2); - // Task is suspended on tcs1 — should be in active tasks + // Task is suspended on tcs1 — should be in active tasks var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); lock (activeTasks) @@ -301,7 +301,7 @@ public void RuntimeAsync_TimestampsTrackedWhileInFlight() Assert.True(activeTasks.ContainsKey(inflight.Id), "Expected suspended task to be in s_currentActiveTasks"); } - // Resume from first suspension — this triggers DispatchContinuations which sets timestamps + // Resume from first suspension — this triggers DispatchContinuations which sets timestamps tcs1.SetResult(); // Poll until the dispatch loop has resumed and re-suspended on tcs2, From 0b151a30405213e4445ac8c1c8deb18737671f7a Mon Sep 17 00:00:00 2001 From: Johan Lorensson Date: Wed, 25 Mar 2026 15:53:56 +0100 Subject: [PATCH 05/23] Update src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/Threading/Tasks/Task.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7cfbc3c1d9a172..dce20f7fd1aacb 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 @@ -229,7 +229,7 @@ private static Dictionary GetOrCreateRuntimeAsyncTaskTimestamps() { return Volatile.Read(ref s_runtimeAsyncTaskTimestamps) ?? Interlocked.CompareExchange(ref s_runtimeAsyncTaskTimestamps, new Dictionary(), null) ?? - s_runtimeAsyncTaskTimestamps; + s_runtimeAsyncTaskTimestamps!; } internal static void ReplaceOrAddRuntimeAsyncContinuationTimestamp(Continuation curContinuation, Continuation newContinuation) From 9f2eae78ab3f880d6492e989ec0fe4791eaaa137 Mon Sep 17 00:00:00 2001 From: Johan Lorensson Date: Wed, 25 Mar 2026 15:58:16 +0100 Subject: [PATCH 06/23] Update src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../RuntimeAsyncTests.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index 29d369a443638d..74ec1467935358 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -17,11 +17,16 @@ public class RuntimeAsyncTests // NOTE: This depends on private implementation details generally only used by the debugger. // If those ever change, this test will need to be updated as well. - private static FieldInfo AsyncDebuggingEnabledField => typeof(Task).GetField("s_asyncDebuggingEnabled", BindingFlags.NonPublic | BindingFlags.Static); - private static FieldInfo TaskTimestampsField => typeof(Task).GetField("s_runtimeAsyncTaskTimestamps", BindingFlags.NonPublic | BindingFlags.Static); - private static FieldInfo ContinuationTimestampsField => typeof(Task).GetField("s_runtimeAsyncContinuationTimestamps", BindingFlags.NonPublic | BindingFlags.Static); - private static FieldInfo ActiveTasksField => typeof(Task).GetField("s_currentActiveTasks", BindingFlags.NonPublic | BindingFlags.Static); + private static readonly FieldInfo AsyncDebuggingEnabledField = GetRequiredTaskStaticField("s_asyncDebuggingEnabled"); + private static readonly FieldInfo TaskTimestampsField = GetRequiredTaskStaticField("s_runtimeAsyncTaskTimestamps"); + private static readonly FieldInfo ContinuationTimestampsField = GetRequiredTaskStaticField("s_runtimeAsyncContinuationTimestamps"); + private static readonly FieldInfo ActiveTasksField = GetRequiredTaskStaticField("s_currentActiveTasks"); + private static FieldInfo GetRequiredTaskStaticField(string fieldName) + { + FieldInfo? field = typeof(Task).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static); + return field ?? throw new InvalidOperationException($"Expected static field '{fieldName}' to exist on type '{typeof(Task)}'."); + } private static int GetTaskTimestampCount() => TaskTimestampsField.GetValue(null) is Dictionary dict ? dict.Count : 0; From 989feca35abfd51edeba7578b884a68a9421c19c Mon Sep 17 00:00:00 2001 From: Johan Lorensson Date: Wed, 25 Mar 2026 16:15:50 +0100 Subject: [PATCH 07/23] Update src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/Threading/Tasks/Task.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 dce20f7fd1aacb..1a559e653c7049 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 @@ -244,7 +244,7 @@ internal static void ReplaceOrAddRuntimeAsyncContinuationTimestamp(Continuation } else { - continuationTimestamps[newContinuation] = Stopwatch.GetTimestamp(); + continuationTimestamps.TryAdd(newContinuation, Stopwatch.GetTimestamp()); } } } From 19ec951f1e32b46b4795de7cb327d75a82819cfa Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 26 Mar 2026 12:41:39 +0100 Subject: [PATCH 08/23] Update test. --- src/tests/async/reflection/reflection.cs | 34 +++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/tests/async/reflection/reflection.cs b/src/tests/async/reflection/reflection.cs index a5992542037f2e..e539dfc2f65f1d 100644 --- a/src/tests/async/reflection/reflection.cs +++ b/src/tests/async/reflection/reflection.cs @@ -310,8 +310,21 @@ public static void FromStack(int level) { // Note: we go through suspend/resume, that is why we see dispatcher as the caller. // we do not see the resume stub though. - Assert.Equal("Void DispatchContinuations()", FromStackAsync(1).Result); - Assert.Equal("Void DispatchContinuations()", FromStackAwait(1).Result); + // The dispatch loop is now a generic method (DispatchContinuations). In checked/debug builds, + // ResumeRuntimeAsyncMethod may not be inlined, appearing as the immediate caller instead. + string asyncFrame = FromStackAsync(1).Result; + string awaitFrame = FromStackAwait(1).Result; + Assert.True(asyncFrame.Contains("DispatchContinuations") || asyncFrame.Contains("ResumeRuntimeAsyncMethod"), + $"Expected dispatch infrastructure frame, got: {asyncFrame}"); + Assert.True(awaitFrame.Contains("DispatchContinuations") || awaitFrame.Contains("ResumeRuntimeAsyncMethod"), + $"Expected dispatch infrastructure frame, got: {awaitFrame}"); + + // In release builds, ResumeRuntimeAsyncMethod must be inlined into DispatchContinuations. + if (TestLibrary.CoreClrConfigurationDetection.IsReleaseRuntime) + { + Assert.DoesNotContain("ResumeRuntimeAsyncMethod", asyncFrame); + Assert.DoesNotContain("ResumeRuntimeAsyncMethod", awaitFrame); + } Assert.Equal("Void FromStack(Int32)", FromStackTask(1).Result); // Note: we do not go through suspend/resume, that is why we see the actual caller. @@ -370,8 +383,21 @@ public static void FromStackDMI(int level) { // Note: we go through suspend/resume, that is why we see dispatcher as the caller. // we do not see the resume stub though. - Assert.Equal("DispatchContinuations", FromStackDMIAsync(1).Result); - Assert.Equal("DispatchContinuations", FromStackDMIAwait(1).Result); + // The dispatch loop is now a generic method (DispatchContinuations). In checked/debug builds, + // ResumeRuntimeAsyncMethod may not be inlined, appearing as the immediate caller instead. + string asyncName = FromStackDMIAsync(1).Result; + string awaitName = FromStackDMIAwait(1).Result; + Assert.True(asyncName == "DispatchContinuations" || asyncName == "ResumeRuntimeAsyncMethod", + $"Expected DispatchContinuations or ResumeRuntimeAsyncMethod, got: {asyncName}"); + Assert.True(awaitName == "DispatchContinuations" || awaitName == "ResumeRuntimeAsyncMethod", + $"Expected DispatchContinuations or ResumeRuntimeAsyncMethod, got: {awaitName}"); + + // In release builds, ResumeRuntimeAsyncMethod must be inlined into DispatchContinuations. + if (TestLibrary.CoreClrConfigurationDetection.IsReleaseRuntime) + { + Assert.Equal("DispatchContinuations", asyncName); + Assert.Equal("DispatchContinuations", awaitName); + } Assert.Equal("FromStackDMI", FromStackDMITask(1).Result); // Note: we do not go through suspend/resume, that is why we see the actual caller. From 7c0b77cac10a967ec8a939c71ef0ee569e87dec5 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Wed, 1 Apr 2026 15:55:23 +0200 Subject: [PATCH 09/23] Moved over to duplicated DispatchContinuations implementation. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 544 ++++++------------ .../System.Private.CoreLib.Shared.projitems | 1 + .../CompilerServices/AsyncInstrumentation.cs | 111 ++++ .../System/Threading/Tasks/TplEventSource.cs | 4 +- .../RuntimeAsyncTests.cs | 179 +++++- src/tests/async/reflection/reflection.cs | 34 +- 6 files changed, 444 insertions(+), 429 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs 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 f3f0862fa552d0..b8e5987a63faf9 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 @@ -314,148 +314,6 @@ private static void TransparentAwait(object o) AsyncSuspend(sentinelContinuation); } - internal static class RuntimeAsyncTaskInstrumentation - { -#if NATIVEAOT - internal static bool IsSupported { get; } = Debugger.IsSupported || EventSource.IsSupported; -#else - internal static bool IsSupported { get; } = true; -#endif - - internal enum Flags - { - Disabled = 0x0, - CreateAsyncContext = 0x1, - ResumeAsyncContext = 0x2, - SuspendAsyncContext = 0x4, - CompleteAsyncContext = 0x8, - UnwindAsyncException = 0x10, - ResumeAsyncMethod = 0x20, - CompleteAsyncMethod = 0x40, - AsyncProfiler = 0x10000, - Tpl = 0x20000, - Debugger = 0x40000 - } - - public static Flags ActiveFlags => _activeFlags; - - public static Flags UpdateAsyncProfilerFlags(Flags flags) - { - lock (_lock) - { - if (flags != Flags.Disabled) - { - flags |= Flags.AsyncProfiler; - } - - _asyncProfilerActiveFlags = flags; - _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; - - return _activeFlags; - } - } - - public static Flags UpdateTplFlags(EventSource tplEventSource) - { - Flags flags = Flags.Disabled; - - flags |= tplEventSource.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.AsyncCausalitySynchronousWork) ? - Flags.ResumeAsyncContext | - Flags.SuspendAsyncContext | - Flags.CompleteAsyncContext | - Flags.UnwindAsyncException : 0; - - flags |= tplEventSource.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.AsyncCausalityOperation) ? - Flags.CreateAsyncContext | - Flags.CompleteAsyncContext | - Flags.UnwindAsyncException : 0; - - lock (_lock) - { - if (flags != Flags.Disabled) - { - flags |= Flags.Tpl; - } - - _tplActiveFlags = flags; - _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; - - return _activeFlags; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Flags SyncAndGetActiveFlags() - { - Flags flags = _activeFlags; - if (IsEnabled.Debugger(flags) != Task.s_asyncDebuggingEnabled) - { - flags = SyncDebuggerFlagsSlow(flags); - } - return flags; - } - - private static Flags SyncDebuggerFlagsSlow(Flags flags) - { - if (IsEnabled.Debugger(flags) && !Task.s_asyncDebuggingEnabled) - { - return UpdateDebuggerFlags(Flags.Disabled); - } - else if (!IsEnabled.Debugger(flags) && Task.s_asyncDebuggingEnabled) - { - return UpdateDebuggerFlags(DebuggerFlags); - } - - return flags; - } - - private static Flags UpdateDebuggerFlags(Flags flags) - { - lock (_lock) - { - if (flags != Flags.Disabled) - { - flags |= Flags.Debugger; - } - - _debuggerActiveFlags = flags; - _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; - - return _activeFlags; - } - } - - public static class IsEnabled - { - public static bool CreateAsyncContext(Flags flags) => (Flags.CreateAsyncContext & flags) != 0; - public static bool ResumeAsyncContext(Flags flags) => (Flags.ResumeAsyncContext & flags) != 0; - public static bool SuspendAsyncContext(Flags flags) => (Flags.SuspendAsyncContext & flags) != 0; - public static bool CompleteAsyncContext(Flags flags) => (Flags.CompleteAsyncContext & flags) != 0; - public static bool UnwindAsyncException(Flags flags) => (Flags.UnwindAsyncException & flags) != 0; - public static bool ResumeAsyncMethod(Flags flags) => (Flags.ResumeAsyncMethod & flags) != 0; - public static bool CompleteAsyncMethod(Flags flags) => (Flags.CompleteAsyncMethod & flags) != 0; - public static bool AsyncProfiler(Flags flags) => (Flags.AsyncProfiler & flags) != 0; - public static bool Tpl(Flags flags) => (Flags.Tpl & flags) != 0; - public static bool Debugger(Flags flags) => (Flags.Debugger & flags) != 0; - public static bool DebuggerOrTpl(Flags flags) => ((Flags.Tpl | Flags.Debugger) & flags) != 0; - } - - private static Flags _activeFlags; - - private static Flags _asyncProfilerActiveFlags; - - private static Flags _tplActiveFlags; - - private static Flags _debuggerActiveFlags; - - private static readonly object _lock = new object(); - - private const Flags DebuggerFlags = - Flags.CreateAsyncContext | Flags.SuspendAsyncContext | - Flags.CompleteAsyncContext | Flags.UnwindAsyncException | - Flags.ResumeAsyncMethod | Flags.CompleteAsyncMethod; - } - // Represents execution of a chain of suspended and resuming runtime // async functions. private sealed class RuntimeAsyncTask : Task, ITaskCompletionAction @@ -466,18 +324,18 @@ public RuntimeAsyncTask() // Ensure that state object isn't published out for others to see. Debug.Assert((m_stateFlags & (int)InternalTaskOptions.PromiseTask) != 0, "Expected state flags to already be configured."); Debug.Assert(m_stateObject is null, "Expected to be able to use the state object field for Continuation."); - m_action = DispatchContinuations; + m_action = DispatchContinuations; m_stateFlags |= (int)InternalTaskOptions.HiddenState; } internal override void ExecuteFromThreadPool(Thread threadPoolThread) { - DispatchContinuations(); + DispatchContinuations(); } void ITaskCompletionAction.Invoke(Task completingTask) { - DispatchContinuations(); + DispatchContinuations(); } bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true; @@ -606,153 +464,57 @@ public void NotifyDebuggerOfRuntimeAsyncState() } #pragma warning restore CA1822 - internal interface IRuntimeAsyncTaskInstrumentation + internal static class AsyncInstrumentationHelper { - static abstract bool InstrumentEntryPoint { get; } - - static abstract bool InstrumentCheckPoint { get; } - - static abstract RuntimeAsyncTaskInstrumentation.Flags Flags { get; } - - static abstract void InitAsyncDispatcherInfo(RuntimeAsyncTask task, ref AsyncDispatcherInfo info); - - static abstract void CreateRuntimeAsyncContext(RuntimeAsyncTask task); - - static abstract void ResumeRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags); - - static abstract void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation); - - static abstract void SuspendRuntimeAsyncContext(RuntimeAsyncTask task, ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation); - - static abstract void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags); - - static abstract void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames); - - static abstract void UnwindRuntimeAsyncMethodHandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames); - - static abstract Continuation? ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, ref byte resultLoc); - - static abstract void CompleteRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation); - } - - internal struct DisableRuntimeAsyncTaskInstrumentation : IRuntimeAsyncTaskInstrumentation - { - public static bool InstrumentEntryPoint - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => RuntimeAsyncTaskInstrumentation.IsSupported - && RuntimeAsyncTaskInstrumentation.SyncAndGetActiveFlags() != RuntimeAsyncTaskInstrumentation.Flags.Disabled; - } - public static bool InstrumentCheckPoint { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => RuntimeAsyncTaskInstrumentation.IsSupported - && RuntimeAsyncTaskInstrumentation.ActiveFlags != RuntimeAsyncTaskInstrumentation.Flags.Disabled; - } - - public static RuntimeAsyncTaskInstrumentation.Flags Flags => RuntimeAsyncTaskInstrumentation.Flags.Disabled; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitAsyncDispatcherInfo(RuntimeAsyncTask task, ref AsyncDispatcherInfo info) { } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CreateRuntimeAsyncContext(RuntimeAsyncTask task) { task.HandleSuspended(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResumeRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags) { } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation) { } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SuspendRuntimeAsyncContext(RuntimeAsyncTask task, ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation) - { - task.HandleSuspended(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags) { } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) { } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnwindRuntimeAsyncMethodHandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) { } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Continuation? ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, ref byte resultLoc) - { - unsafe - { - return curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); - } + get => AsyncInstrumentation.IsSupported + && AsyncInstrumentation.ActiveFlags != AsyncInstrumentation.Flags.Disabled; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CompleteRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation) { } - } - - internal struct EnableRuntimeAsyncTaskInstrumentation : IRuntimeAsyncTaskInstrumentation - { - public static bool InstrumentEntryPoint => false; - - public static bool InstrumentCheckPoint => false; - - public static RuntimeAsyncTaskInstrumentation.Flags Flags => RuntimeAsyncTaskInstrumentation.ActiveFlags; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitAsyncDispatcherInfo(RuntimeAsyncTask task, ref AsyncDispatcherInfo info) + public static void CreateRuntimeAsyncContext(RuntimeAsyncTask task, AsyncInstrumentation.Flags flags) { - info.CurrentTask = task; - } - - public static void CreateRuntimeAsyncContext(RuntimeAsyncTask task) - { - RuntimeAsyncTaskInstrumentation.Flags flags = Flags; - if (RuntimeAsyncTaskInstrumentation.IsEnabled.CreateAsyncContext(flags)) + if (AsyncInstrumentation.IsEnabled.CreateAsyncContext(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) { task.NotifyDebuggerOfRuntimeAsyncState(); AddToActiveTasks(task); } - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) { TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); } - - HandleSuspended(task, flags); - return; } - - task.HandleSuspended(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResumeRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags) + public static void ResumeRuntimeAsyncContext(RuntimeAsyncTask task, ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.ResumeAsyncContext(flags)) + info.CurrentTask = task; + + if (AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags) && info.CurrentTask != null) + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) { - TplEventSource.Log.TraceSynchronousWorkBegin(info.CurrentTask.Id, CausalitySynchronousWork.Execution); + TplEventSource.Log.TraceSynchronousWorkBegin(task.Id, CausalitySynchronousWork.Execution); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation) + public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Continuation curContinuation) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.SuspendAsyncContext(flags)) + if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags) && info.NextContinuation != null) + if (AsyncInstrumentation.IsEnabled.Debugger(flags) && info.NextContinuation != null) { TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); } - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) { TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } @@ -760,55 +522,50 @@ public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, Runt } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SuspendRuntimeAsyncContext(RuntimeAsyncTask task, ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation) + public static void SuspendRuntimeAsyncContext(AsyncInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.SuspendAsyncContext(flags)) + if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) { ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); } - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) { TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } - - HandleSuspended(task, flags, newContinuation); - return; } - - task.HandleSuspended(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags) + public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.CompleteAsyncContext(flags)) + if (AsyncInstrumentation.IsEnabled.CompleteAsyncContext(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.DebuggerOrTpl(flags)) + if (AsyncInstrumentation.IsEnabled.DebuggerOrTpl(flags)) { CompleteRuntimeAsyncContext(info.CurrentTask, flags); } } } - public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) + public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.UnwindAsyncException(flags)) + if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.DebuggerOrTpl(flags)) + if (AsyncInstrumentation.IsEnabled.DebuggerOrTpl(flags)) { UnwindRuntimeAsyncMethodUnhandledException(info.CurrentTask, flags, ex, curContinuation, unwindedFrames); } } } - public static void UnwindRuntimeAsyncMethodHandledException(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) + public static void UnwindRuntimeAsyncMethodHandledException(AsyncInstrumentation.Flags flags, Continuation curContinuation, uint unwindedFrames) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.UnwindAsyncException(flags)) + if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) { RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); } @@ -816,44 +573,52 @@ public static void UnwindRuntimeAsyncMethodHandledException(ref AsyncDispatcherI } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Continuation? ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation, ref byte resultLoc) + public static void ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Continuation curContinuation) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.ResumeAsyncMethod(flags)) + if (AsyncInstrumentation.IsEnabled.ResumeAsyncMethod(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags) && info.CurrentTask != null) + if (AsyncInstrumentation.IsEnabled.Debugger(flags) && info.CurrentTask != null) { UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); } } - - unsafe - { - return curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); - } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CompleteRuntimeAsyncMethod(ref AsyncDispatcherInfo info, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation curContinuation) + public static void CompleteRuntimeAsyncMethod(AsyncInstrumentation.Flags flags, Continuation curContinuation) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.CompleteAsyncMethod(flags)) + if (AsyncInstrumentation.IsEnabled.CompleteAsyncMethod(flags)) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) { RemoveRuntimeAsyncContinuationTimestamp(curContinuation); } } } - private static void CompleteRuntimeAsyncContext(Task? task, RuntimeAsyncTaskInstrumentation.Flags flags) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void HandleSuspended(RuntimeAsyncTask task, AsyncInstrumentation.Flags flags, Continuation? newContinuation = null) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + { + DebuggerHandleSuspended(task, newContinuation); + } + else + { + task.HandleSuspended(); + } + } + + private static void CompleteRuntimeAsyncContext(Task? task, AsyncInstrumentation.Flags flags) { if (task != null) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) { RemoveRuntimeAsyncTask(task); } - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) { TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); @@ -861,16 +626,16 @@ private static void CompleteRuntimeAsyncContext(Task? task, RuntimeAsyncTaskInst } } - private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, RuntimeAsyncTaskInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint _) + private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint _) { if (task != null) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) { RemoveRuntimeAsyncTask(task, curContinuation); } - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Tpl(flags)) + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) { TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); @@ -878,84 +643,139 @@ private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, Runti } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void HandleSuspended(RuntimeAsyncTask task, RuntimeAsyncTaskInstrumentation.Flags flags) + private static void DebuggerHandleSuspended(RuntimeAsyncTask task, Continuation? newContinuation = null) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; + Continuation? nc = state.SentinelContinuation!.Next; + + if (nc != null) { - DebuggerHandleSuspended(task); + if (newContinuation != null) + TryAddRuntimeAsyncContinuationChainTimestamps(nc, newContinuation); + else + TryAddRuntimeAsyncContinuationChainTimestamps(nc); } - else + + if (!task.HandleSuspended()) { - task.HandleSuspended(); + RemoveRuntimeAsyncTask(task); } } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void HandleSuspended(RuntimeAsyncTask task, RuntimeAsyncTaskInstrumentation.Flags flags, Continuation newContinuation) + [StackTraceHidden] + private unsafe void InstrumentedDispatchContinuations() + { + ExecutionAndSyncBlockStore contexts = default; + contexts.Push(); + + AsyncDispatcherInfo asyncDispatcherInfo; + asyncDispatcherInfo.Next = AsyncDispatcherInfo.t_current; + asyncDispatcherInfo.NextContinuation = MoveContinuationState(); + AsyncDispatcherInfo.t_current = &asyncDispatcherInfo; + + AsyncInstrumentation.Flags flags = AsyncInstrumentation.ActiveFlags; + AsyncInstrumentationHelper.ResumeRuntimeAsyncContext(this, ref asyncDispatcherInfo, flags); + + while (true) { - if (RuntimeAsyncTaskInstrumentation.IsEnabled.Debugger(flags)) + Debug.Assert(asyncDispatcherInfo.NextContinuation != null); + Continuation curContinuation = asyncDispatcherInfo.NextContinuation; + try { - DebuggerHandleSuspended(task, newContinuation); + Continuation? nextContinuation = curContinuation.Next; + asyncDispatcherInfo.NextContinuation = nextContinuation; + + ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); + + AsyncInstrumentationHelper.ResumeRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation); + Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); + + if (newContinuation != null) + { + newContinuation.Next = nextContinuation; + AsyncInstrumentationHelper.SuspendRuntimeAsyncContext(flags, curContinuation, newContinuation); + AsyncInstrumentationHelper.HandleSuspended(this, flags, newContinuation); + + contexts.Pop(); + AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; + return; + } + + AsyncInstrumentationHelper.CompleteRuntimeAsyncMethod(flags, curContinuation); } - else + catch (Exception ex) { - task.HandleSuspended(); - } - } + uint unwindedFrames = 1; // Count current frame. + Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation, ex, ref unwindedFrames); + if (handlerContinuation == null) + { + AsyncInstrumentationHelper.UnwindRuntimeAsyncMethodUnhandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); - private static void DebuggerHandleSuspended(RuntimeAsyncTask task, Continuation newContinuation) - { - ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; - Continuation? nc = state.SentinelContinuation!.Next; + // Tail of AsyncTaskMethodBuilderT.SetException + bool successfullySet = ex is OperationCanceledException oce ? + TrySetCanceled(oce.CancellationToken, oce) : + TrySetException(ex); - if (nc != null) - { - TryAddRuntimeAsyncContinuationChainTimestamps(nc, newContinuation); + contexts.Pop(); + + AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; + + if (!successfullySet) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); + } + + return; + } + + AsyncInstrumentationHelper.UnwindRuntimeAsyncMethodHandledException(flags, curContinuation, unwindedFrames); + + handlerContinuation.SetException(ex); + asyncDispatcherInfo.NextContinuation = handlerContinuation; } - if (!task.HandleSuspended()) + if (asyncDispatcherInfo.NextContinuation == null) { - RemoveRuntimeAsyncTask(task); - } - } + AsyncInstrumentationHelper.CompleteRuntimeAsyncContext(ref asyncDispatcherInfo, flags); - private static void DebuggerHandleSuspended(RuntimeAsyncTask task) - { - ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; - Continuation? nc = state.SentinelContinuation!.Next; + bool successfullySet = TrySetResult(m_result); - if (nc != null) - { - TryAddRuntimeAsyncContinuationChainTimestamps(nc); + contexts.Pop(); + + AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; + + if (!successfullySet) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); + } + + return; } - if (!task.HandleSuspended()) + if (QueueContinuationFollowUpActionIfNecessary(asyncDispatcherInfo.NextContinuation)) { - RemoveRuntimeAsyncTask(task); + AsyncInstrumentationHelper.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); + + contexts.Pop(); + AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; + return; } - } - } - [BypassReadyToRun] - [MethodImpl(MethodImplOptions.NoInlining)] - [StackTraceHidden] - private void InstrumentedDispatchContinuations() - { - DispatchContinuations(); + flags = AsyncInstrumentation.ActiveFlags; + } } [StackTraceHidden] - private unsafe void DispatchContinuations() where TRuntimeAsyncTaskInstrumentation : struct, IRuntimeAsyncTaskInstrumentation + // NOTE, any changes done to this method needs to be replicate in InstrumentedDispatchContinuations as well. + private unsafe void DispatchContinuations() { - if (TRuntimeAsyncTaskInstrumentation.InstrumentEntryPoint) + if (AsyncInstrumentationHelper.InstrumentCheckPoint) { InstrumentedDispatchContinuations(); return; } - RuntimeAsyncTaskInstrumentation.Flags flags = TRuntimeAsyncTaskInstrumentation.Flags; - ExecutionAndSyncBlockStore contexts = default; contexts.Push(); @@ -964,34 +784,28 @@ private unsafe void DispatchContinuations() wh asyncDispatcherInfo.NextContinuation = MoveContinuationState(); AsyncDispatcherInfo.t_current = &asyncDispatcherInfo; - TRuntimeAsyncTaskInstrumentation.InitAsyncDispatcherInfo(this, ref asyncDispatcherInfo); - - TRuntimeAsyncTaskInstrumentation.ResumeRuntimeAsyncContext(ref asyncDispatcherInfo, flags); - while (true) { Debug.Assert(asyncDispatcherInfo.NextContinuation != null); - Continuation curContinuation = asyncDispatcherInfo.NextContinuation; try { + Continuation curContinuation = asyncDispatcherInfo.NextContinuation; Continuation? nextContinuation = curContinuation.Next; asyncDispatcherInfo.NextContinuation = nextContinuation; ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); - Continuation? newContinuation = TRuntimeAsyncTaskInstrumentation.ResumeRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation, ref resultLoc); + Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); if (newContinuation != null) { newContinuation.Next = nextContinuation; - TRuntimeAsyncTaskInstrumentation.SuspendRuntimeAsyncContext(this, ref asyncDispatcherInfo, flags, curContinuation, newContinuation); + HandleSuspended(); contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; return; } - - TRuntimeAsyncTaskInstrumentation.CompleteRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation); } catch (Exception ex) { @@ -999,8 +813,6 @@ private unsafe void DispatchContinuations() wh Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation, ex, ref unwindedFrames); if (handlerContinuation == null) { - TRuntimeAsyncTaskInstrumentation.UnwindRuntimeAsyncMethodUnhandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); - // Tail of AsyncTaskMethodBuilderT.SetException bool successfullySet = ex is OperationCanceledException oce ? TrySetCanceled(oce.CancellationToken, oce) : @@ -1018,16 +830,12 @@ private unsafe void DispatchContinuations() wh return; } - TRuntimeAsyncTaskInstrumentation.UnwindRuntimeAsyncMethodHandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); - handlerContinuation.SetException(ex); asyncDispatcherInfo.NextContinuation = handlerContinuation; } if (asyncDispatcherInfo.NextContinuation == null) { - TRuntimeAsyncTaskInstrumentation.CompleteRuntimeAsyncContext(ref asyncDispatcherInfo, flags); - bool successfullySet = TrySetResult(m_result); contexts.Pop(); @@ -1044,27 +852,21 @@ private unsafe void DispatchContinuations() wh if (QueueContinuationFollowUpActionIfNecessary(asyncDispatcherInfo.NextContinuation)) { - TRuntimeAsyncTaskInstrumentation.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); - contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; return; } - if (TRuntimeAsyncTaskInstrumentation.InstrumentCheckPoint) + if (AsyncInstrumentationHelper.InstrumentCheckPoint) { SetContinuationState(asyncDispatcherInfo.NextContinuation); - TRuntimeAsyncTaskInstrumentation.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); - contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; InstrumentedDispatchContinuations(); return; } - - flags = TRuntimeAsyncTaskInstrumentation.Flags; } } @@ -1157,34 +959,28 @@ private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuatio private static readonly SendOrPostCallback s_postCallback = static state => { Debug.Assert(state is RuntimeAsyncTask); - ((RuntimeAsyncTask)state).DispatchContinuations(); + ((RuntimeAsyncTask)state).DispatchContinuations(); }; private static readonly Action s_runContinuationAction = static state => { Debug.Assert(state is RuntimeAsyncTask); - ((RuntimeAsyncTask)state).DispatchContinuations(); + ((RuntimeAsyncTask)state).DispatchContinuations(); }; } - [BypassReadyToRun] - [MethodImpl(MethodImplOptions.NoInlining)] - [StackTraceHidden] - private static void InstrumentedFinalizeRuntimeAsyncTask(RuntimeAsyncTask task) - { - RuntimeAsyncTask.EnableRuntimeAsyncTaskInstrumentation.CreateRuntimeAsyncContext(task); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void FinalizeRuntimeAsyncTask(RuntimeAsyncTask task) { - if (RuntimeAsyncTask.DisableRuntimeAsyncTaskInstrumentation.InstrumentEntryPoint) + if (RuntimeAsyncTask.AsyncInstrumentationHelper.InstrumentCheckPoint) { - InstrumentedFinalizeRuntimeAsyncTask(task); + AsyncInstrumentation.Flags flags = AsyncInstrumentation.ActiveFlags; + RuntimeAsyncTask.AsyncInstrumentationHelper.CreateRuntimeAsyncContext(task, flags); + RuntimeAsyncTask.AsyncInstrumentationHelper.HandleSuspended(task, flags); return; } - RuntimeAsyncTask.DisableRuntimeAsyncTaskInstrumentation.CreateRuntimeAsyncContext(task); + task.HandleSuspended(); } // Change return type to RuntimeAsyncTask -- no benefit since this is used for Task returning thunks only diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 2db9af120d00cc..cc962e9fe1e4e0 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -841,6 +841,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs new file mode 100644 index 00000000000000..9aa91efdbdc9d0 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.Tracing; + +namespace System.Runtime.CompilerServices +{ + internal static class AsyncInstrumentation + { + public static bool IsSupported => Debugger.IsSupported || EventSource.IsSupported; + + public enum Flags + { + Disabled = 0x0, + CreateAsyncContext = 0x1, + ResumeAsyncContext = 0x2, + SuspendAsyncContext = 0x4, + CompleteAsyncContext = 0x8, + UnwindAsyncException = 0x10, + ResumeAsyncMethod = 0x20, + CompleteAsyncMethod = 0x40, + AsyncProfiler = 0x10000, + Tpl = 0x20000, + Debugger = 0x40000 + } + + public static class IsEnabled + { + public static bool CreateAsyncContext(Flags flags) => (Flags.CreateAsyncContext & flags) != 0; + public static bool ResumeAsyncContext(Flags flags) => (Flags.ResumeAsyncContext & flags) != 0; + public static bool SuspendAsyncContext(Flags flags) => (Flags.SuspendAsyncContext & flags) != 0; + public static bool CompleteAsyncContext(Flags flags) => (Flags.CompleteAsyncContext & flags) != 0; + public static bool UnwindAsyncException(Flags flags) => (Flags.UnwindAsyncException & flags) != 0; + public static bool ResumeAsyncMethod(Flags flags) => (Flags.ResumeAsyncMethod & flags) != 0; + public static bool CompleteAsyncMethod(Flags flags) => (Flags.CompleteAsyncMethod & flags) != 0; + public static bool AsyncProfiler(Flags flags) => (Flags.AsyncProfiler & flags) != 0; + public static bool Tpl(Flags flags) => (Flags.Tpl & flags) != 0; + public static bool Debugger(Flags flags) => (Flags.Debugger & flags) != 0 && Task.s_asyncDebuggingEnabled; + public static bool DebuggerOrTpl(Flags flags) => Debugger(flags) || Tpl(flags); + } + + public static Flags ActiveFlags => _activeFlags; + + public static Flags UpdateAsyncProfilerFlags(Flags flags) + { + if (flags != Flags.Disabled) + { + flags |= Flags.AsyncProfiler; + } + + lock (_lock) + { + _asyncProfilerActiveFlags = flags; + _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; + + return _activeFlags; + } + } + + public static Flags UpdateTplFlags(EventSource tplEventSource) + { + // Until debugger sets its flags directly, piggy back on TPL instrumentation since debugger will enable/disable TPL events + // when attaching/detaching to the runtime. + Flags tplFlags = Flags.Disabled; + Flags debuggerFlags = Flags.Disabled; + + tplFlags |= tplEventSource.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.AsyncCausalitySynchronousWork) ? + Flags.ResumeAsyncContext | + Flags.SuspendAsyncContext | + Flags.CompleteAsyncContext | + Flags.UnwindAsyncException : 0; + + tplFlags |= tplEventSource.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.AsyncCausalityOperation) ? + Flags.CreateAsyncContext | + Flags.CompleteAsyncContext | + Flags.UnwindAsyncException : 0; + + if (tplFlags != Flags.Disabled) + { + tplFlags |= Flags.Tpl; + debuggerFlags |= DebuggerFlags | Flags.Debugger; + } + + lock (_lock) + { + _tplActiveFlags = tplFlags; + _debuggerActiveFlags = debuggerFlags; + _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; + + return _activeFlags; + } + } + + private static Flags _activeFlags; + + private static Flags _asyncProfilerActiveFlags; + + private static Flags _tplActiveFlags; + + private static Flags _debuggerActiveFlags; + + private static readonly object _lock = new object(); + + private const Flags DebuggerFlags = + Flags.CreateAsyncContext | Flags.SuspendAsyncContext | + Flags.CompleteAsyncContext | Flags.UnwindAsyncException | + Flags.ResumeAsyncMethod | Flags.CompleteAsyncMethod; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs index 2b7a4e83454c5a..eb228d3370154c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs @@ -35,9 +35,7 @@ protected override void OnEventCommand(EventCommandEventArgs command) Debug = IsEnabled(EventLevel.Informational, Keywords.Debug); DebugActivityId = IsEnabled(EventLevel.Informational, Keywords.DebugActivityId); -#if CORECLR || NATIVEAOT - AsyncHelpers.RuntimeAsyncTaskInstrumentation.UpdateTplFlags(this); -#endif + AsyncInstrumentation.UpdateTplFlags(this); } /// diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index 74ec1467935358..a6a76a40419b95 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -17,16 +17,85 @@ public class RuntimeAsyncTests // NOTE: This depends on private implementation details generally only used by the debugger. // If those ever change, this test will need to be updated as well. - private static readonly FieldInfo AsyncDebuggingEnabledField = GetRequiredTaskStaticField("s_asyncDebuggingEnabled"); - private static readonly FieldInfo TaskTimestampsField = GetRequiredTaskStaticField("s_runtimeAsyncTaskTimestamps"); - private static readonly FieldInfo ContinuationTimestampsField = GetRequiredTaskStaticField("s_runtimeAsyncContinuationTimestamps"); - private static readonly FieldInfo ActiveTasksField = GetRequiredTaskStaticField("s_currentActiveTasks"); + private static readonly FieldInfo AsyncDebuggingEnabledField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_asyncDebuggingEnabled"); + private static readonly FieldInfo TaskTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncTaskTimestamps"); + private static readonly FieldInfo ContinuationTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncContinuationTimestamps"); + private static readonly FieldInfo ActiveTasksField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_currentActiveTasks"); + private static readonly FieldInfo TplEventSource = GetCorLibClassStaticField("System.Threading.Tasks.TplEventSource", "Log"); + private static readonly FieldInfo ActiveFlags = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "_activeFlags"); - private static FieldInfo GetRequiredTaskStaticField(string fieldName) + private static object _debuggerLock = new object(); + private static TestEventListener? _debuggerTplInstance; + + // Tpl(0x20000) | Debugger(0x40000) | all event flags(0x7F) + private const long EnabledInstrumentationFlags = 0x6007F; + private const long DisabledInstrumentationFlags = 0x0; + + private static void AttachDebugger() + { + // Simulate a debugger attach to process, creating TPL event source session + setting s_asyncDebuggingEnabled. + lock (_debuggerLock) + { + // Touch TplEventSource.Log making sure provider is initialized. + TplEventSource.GetValue(null); + + long flags = Convert.ToInt64(ActiveFlags.GetValue(null)); + Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); + + _debuggerTplInstance = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose); + AsyncDebuggingEnabledField.SetValue(null, true); + + flags = Convert.ToInt64(ActiveFlags.GetValue(null)); + Assert.True(flags == EnabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {EnabledInstrumentationFlags}"); + + // Initialize collections. + Func().GetAwaiter().GetResult(); + + flags = Convert.ToInt64(ActiveFlags.GetValue(null)); + Assert.True(flags == EnabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {EnabledInstrumentationFlags}"); + + var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); + Assert.True(activeTasks != null, "Expected active tasks dictionary to be initialized"); + + var taskTimestamps = (Dictionary)TaskTimestampsField.GetValue(null); + Assert.True(taskTimestamps != null, "Expected tasks timestamps dictionary to be initialized"); + + var continuationTimestamps = (Dictionary)ContinuationTimestampsField.GetValue(null); + Assert.True(continuationTimestamps != null, "Expected continuation timestamps dictionary to be initialized"); + } + } + + private static void DetachDebugger() + { + // Simulate a debugger detach from process. + lock (_debuggerLock) + { + AsyncDebuggingEnabledField.SetValue(null, false); + _debuggerTplInstance.Dispose(); + _debuggerTplInstance = null; + + long flags = Convert.ToInt64(ActiveFlags.GetValue(null)); + Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); + } + } + + private static FieldInfo GetCorLibClassStaticField(string className, string fieldName) { - FieldInfo? field = typeof(Task).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static); - return field ?? throw new InvalidOperationException($"Expected static field '{fieldName}' to exist on type '{typeof(Task)}'."); + Type? classType = typeof(object).Assembly.GetType(className); + if (classType == null) + { + throw new InvalidOperationException($"Type '{className}' doesn't exist in System.Private.CoreLib."); + } + + FieldInfo? field = classType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + if (field == null) + { + throw new InvalidOperationException($"Expected static field '{fieldName}' to exist on type '{className}'."); + } + + return field; } + private static int GetTaskTimestampCount() => TaskTimestampsField.GetValue(null) is Dictionary dict ? dict.Count : 0; @@ -125,7 +194,7 @@ public void RuntimeAsync_TaskCompleted() { RemoteExecutor.Invoke(async () => { - AsyncDebuggingEnabledField.SetValue(null, true); + AttachDebugger(); for (int i = 0; i < 1000; i++) { @@ -133,6 +202,9 @@ public void RuntimeAsync_TaskCompleted() } ValidateTimestampsCleared(); + + DetachDebugger(); + }).Dispose(); } @@ -142,7 +214,7 @@ public void RuntimeAsync_ExceptionCleanup() { RemoteExecutor.Invoke(async () => { - AsyncDebuggingEnabledField.SetValue(null, true); + AttachDebugger(); for (int i = 0; i < 1000; i++) { @@ -156,6 +228,9 @@ public void RuntimeAsync_ExceptionCleanup() } ValidateTimestampsCleared(); + + DetachDebugger(); + }).Dispose(); } @@ -165,10 +240,7 @@ public void RuntimeAsync_DebuggerDetach() { RemoteExecutor.Invoke(async () => { - AsyncDebuggingEnabledField.SetValue(null, true); - - // Run one task to ensure lazy-initialized collections are created - await Func(); + AttachDebugger(); var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); @@ -199,7 +271,7 @@ public void RuntimeAsync_DebuggerDetach() // Simulate debugger detach — the flag sync should detect the mismatch // and disable the Debugger flags without crashing. // Timestamps are not cleared (matches existing behavior). - AsyncDebuggingEnabledField.SetValue(null, false); + DetachDebugger(); // Run one task to trigger the flag sync that detects the mismatch await Func(); @@ -219,6 +291,7 @@ public void RuntimeAsync_DebuggerDetach() Assert.False(taskTimestamps.ContainsKey(postDetach.Id), "Expected no task timestamp for post-detach in-flight task"); } + }).Dispose(); } @@ -228,7 +301,7 @@ public void RuntimeAsync_ValueTypeResult() { RemoteExecutor.Invoke(async () => { - AsyncDebuggingEnabledField.SetValue(null, true); + AttachDebugger(); for (int i = 0; i < 1000; i++) { @@ -237,6 +310,9 @@ public void RuntimeAsync_ValueTypeResult() } ValidateTimestampsCleared(); + + DetachDebugger(); + }).Dispose(); } @@ -246,7 +322,7 @@ public void RuntimeAsync_HandledExceptionPartialUnwind() { RemoteExecutor.Invoke(async () => { - AsyncDebuggingEnabledField.SetValue(null, true); + AttachDebugger(); for (int i = 0; i < 1000; i++) { @@ -254,6 +330,9 @@ public void RuntimeAsync_HandledExceptionPartialUnwind() } ValidateTimestampsCleared(); + + DetachDebugger(); + }).Dispose(); } @@ -263,7 +342,7 @@ public void RuntimeAsync_CancellationCleanup() { RemoteExecutor.Invoke(async () => { - AsyncDebuggingEnabledField.SetValue(null, true); + AttachDebugger(); using var cts = new CancellationTokenSource(); cts.Cancel(); @@ -280,6 +359,9 @@ public void RuntimeAsync_CancellationCleanup() } ValidateTimestampsCleared(); + + DetachDebugger(); + }).Dispose(); } @@ -289,10 +371,7 @@ public void RuntimeAsync_TimestampsTrackedWhileInFlight() { RemoteExecutor.Invoke(async () => { - AsyncDebuggingEnabledField.SetValue(null, true); - - // Run one task to ensure lazy-initialized collections are created - await Func(); + AttachDebugger(); var tcs1 = new TaskCompletionSource(); var tcs2 = new TaskCompletionSource(); @@ -343,6 +422,9 @@ public void RuntimeAsync_TimestampsTrackedWhileInFlight() { Assert.False(taskTimestamps.ContainsKey(inflight.Id), "Expected task timestamp to be removed after completion"); } + + DetachDebugger(); + }).Dispose(); } @@ -352,7 +434,7 @@ public void RuntimeAsync_ContinuationTimestampObservedDuringResume() { RemoteExecutor.Invoke(async () => { - AsyncDebuggingEnabledField.SetValue(null, true); + AttachDebugger(); bool continuationTimestampObserved = false; @@ -373,6 +455,59 @@ public void RuntimeAsync_ContinuationTimestampObservedDuringResume() await inflight; Assert.True(continuationTimestampObserved, "Expected continuation timestamp to be present during resumed method execution"); + + DetachDebugger(); + + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_InFlightInstrumentationUpgrade() + { + RemoteExecutor.Invoke(async () => + { + // Start a multi-await task WITHOUT instrumentation enabled. + var tcs1 = new TaskCompletionSource(); + var tcs2 = new TaskCompletionSource(); + Task inflight = FuncThatWaitsTwice(tcs1, tcs2); + + // Task is now suspended at first await with no instrumentation. + // Attach the debugger mid-flight — this enables instrumentation. + AttachDebugger(); + + var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); + var taskTimestamps = (Dictionary)TaskTimestampsField.GetValue(null); + + // The in-flight task was NOT tracked at creation (no instrumentation then). + lock (activeTasks) + { + Assert.False(activeTasks.ContainsKey(inflight.Id), + "Expected in-flight task NOT to be tracked (started before instrumentation)"); + } + + // Resume the first await — the dispatch loop's InstrumentCheckPoint should + // detect that instrumentation is now active and transition to the instrumented path. + tcs1.SetResult(); + + // Wait for the task to suspend at the second await. + bool seenTimestamp = SpinWait.SpinUntil(() => + { + lock (taskTimestamps) + { + return taskTimestamps.ContainsKey(inflight.Id); + } + }, timeout: TimeSpan.FromSeconds(5)); + + Assert.True(seenTimestamp, + "Expected task timestamp after mid-flight instrumentation upgrade (InstrumentCheckPoint transition)"); + + // Complete the second await and let the task finish. + tcs2.SetResult(); + await inflight; + + DetachDebugger(); + }).Dispose(); } diff --git a/src/tests/async/reflection/reflection.cs b/src/tests/async/reflection/reflection.cs index e539dfc2f65f1d..a5992542037f2e 100644 --- a/src/tests/async/reflection/reflection.cs +++ b/src/tests/async/reflection/reflection.cs @@ -310,21 +310,8 @@ public static void FromStack(int level) { // Note: we go through suspend/resume, that is why we see dispatcher as the caller. // we do not see the resume stub though. - // The dispatch loop is now a generic method (DispatchContinuations). In checked/debug builds, - // ResumeRuntimeAsyncMethod may not be inlined, appearing as the immediate caller instead. - string asyncFrame = FromStackAsync(1).Result; - string awaitFrame = FromStackAwait(1).Result; - Assert.True(asyncFrame.Contains("DispatchContinuations") || asyncFrame.Contains("ResumeRuntimeAsyncMethod"), - $"Expected dispatch infrastructure frame, got: {asyncFrame}"); - Assert.True(awaitFrame.Contains("DispatchContinuations") || awaitFrame.Contains("ResumeRuntimeAsyncMethod"), - $"Expected dispatch infrastructure frame, got: {awaitFrame}"); - - // In release builds, ResumeRuntimeAsyncMethod must be inlined into DispatchContinuations. - if (TestLibrary.CoreClrConfigurationDetection.IsReleaseRuntime) - { - Assert.DoesNotContain("ResumeRuntimeAsyncMethod", asyncFrame); - Assert.DoesNotContain("ResumeRuntimeAsyncMethod", awaitFrame); - } + Assert.Equal("Void DispatchContinuations()", FromStackAsync(1).Result); + Assert.Equal("Void DispatchContinuations()", FromStackAwait(1).Result); Assert.Equal("Void FromStack(Int32)", FromStackTask(1).Result); // Note: we do not go through suspend/resume, that is why we see the actual caller. @@ -383,21 +370,8 @@ public static void FromStackDMI(int level) { // Note: we go through suspend/resume, that is why we see dispatcher as the caller. // we do not see the resume stub though. - // The dispatch loop is now a generic method (DispatchContinuations). In checked/debug builds, - // ResumeRuntimeAsyncMethod may not be inlined, appearing as the immediate caller instead. - string asyncName = FromStackDMIAsync(1).Result; - string awaitName = FromStackDMIAwait(1).Result; - Assert.True(asyncName == "DispatchContinuations" || asyncName == "ResumeRuntimeAsyncMethod", - $"Expected DispatchContinuations or ResumeRuntimeAsyncMethod, got: {asyncName}"); - Assert.True(awaitName == "DispatchContinuations" || awaitName == "ResumeRuntimeAsyncMethod", - $"Expected DispatchContinuations or ResumeRuntimeAsyncMethod, got: {awaitName}"); - - // In release builds, ResumeRuntimeAsyncMethod must be inlined into DispatchContinuations. - if (TestLibrary.CoreClrConfigurationDetection.IsReleaseRuntime) - { - Assert.Equal("DispatchContinuations", asyncName); - Assert.Equal("DispatchContinuations", awaitName); - } + Assert.Equal("DispatchContinuations", FromStackDMIAsync(1).Result); + Assert.Equal("DispatchContinuations", FromStackDMIAwait(1).Result); Assert.Equal("FromStackDMI", FromStackDMITask(1).Result); // Note: we do not go through suspend/resume, that is why we see the actual caller. From 070bc5372b80f5649fb712da485d27a4e26ee095 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Wed, 1 Apr 2026 16:27:33 +0200 Subject: [PATCH 10/23] Simplify diff. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 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 b8e5987a63faf9..615a1296cbd055 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 @@ -664,8 +664,15 @@ private static void DebuggerHandleSuspended(RuntimeAsyncTask task, Continuati } [StackTraceHidden] - private unsafe void InstrumentedDispatchContinuations() + // NOTE, any changes done to this method needs to be replicate in InstrumentedDispatchContinuations as well. + private unsafe void DispatchContinuations() { + if (AsyncInstrumentationHelper.InstrumentCheckPoint) + { + InstrumentedDispatchContinuations(); + return; + } + ExecutionAndSyncBlockStore contexts = default; contexts.Push(); @@ -674,35 +681,28 @@ private unsafe void InstrumentedDispatchContinuations() asyncDispatcherInfo.NextContinuation = MoveContinuationState(); AsyncDispatcherInfo.t_current = &asyncDispatcherInfo; - AsyncInstrumentation.Flags flags = AsyncInstrumentation.ActiveFlags; - AsyncInstrumentationHelper.ResumeRuntimeAsyncContext(this, ref asyncDispatcherInfo, flags); - while (true) { Debug.Assert(asyncDispatcherInfo.NextContinuation != null); - Continuation curContinuation = asyncDispatcherInfo.NextContinuation; try { + Continuation curContinuation = asyncDispatcherInfo.NextContinuation; Continuation? nextContinuation = curContinuation.Next; asyncDispatcherInfo.NextContinuation = nextContinuation; ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); - AsyncInstrumentationHelper.ResumeRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation); Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); if (newContinuation != null) { newContinuation.Next = nextContinuation; - AsyncInstrumentationHelper.SuspendRuntimeAsyncContext(flags, curContinuation, newContinuation); - AsyncInstrumentationHelper.HandleSuspended(this, flags, newContinuation); + HandleSuspended(); contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; return; } - - AsyncInstrumentationHelper.CompleteRuntimeAsyncMethod(flags, curContinuation); } catch (Exception ex) { @@ -710,8 +710,6 @@ private unsafe void InstrumentedDispatchContinuations() Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation, ex, ref unwindedFrames); if (handlerContinuation == null) { - AsyncInstrumentationHelper.UnwindRuntimeAsyncMethodUnhandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); - // Tail of AsyncTaskMethodBuilderT.SetException bool successfullySet = ex is OperationCanceledException oce ? TrySetCanceled(oce.CancellationToken, oce) : @@ -729,16 +727,12 @@ private unsafe void InstrumentedDispatchContinuations() return; } - AsyncInstrumentationHelper.UnwindRuntimeAsyncMethodHandledException(flags, curContinuation, unwindedFrames); - handlerContinuation.SetException(ex); asyncDispatcherInfo.NextContinuation = handlerContinuation; } if (asyncDispatcherInfo.NextContinuation == null) { - AsyncInstrumentationHelper.CompleteRuntimeAsyncContext(ref asyncDispatcherInfo, flags); - bool successfullySet = TrySetResult(m_result); contexts.Pop(); @@ -755,27 +749,27 @@ private unsafe void InstrumentedDispatchContinuations() if (QueueContinuationFollowUpActionIfNecessary(asyncDispatcherInfo.NextContinuation)) { - AsyncInstrumentationHelper.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); - contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; return; } - flags = AsyncInstrumentation.ActiveFlags; + if (AsyncInstrumentationHelper.InstrumentCheckPoint) + { + SetContinuationState(asyncDispatcherInfo.NextContinuation); + + contexts.Pop(); + AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; + + InstrumentedDispatchContinuations(); + return; + } } } [StackTraceHidden] - // NOTE, any changes done to this method needs to be replicate in InstrumentedDispatchContinuations as well. - private unsafe void DispatchContinuations() + private unsafe void InstrumentedDispatchContinuations() { - if (AsyncInstrumentationHelper.InstrumentCheckPoint) - { - InstrumentedDispatchContinuations(); - return; - } - ExecutionAndSyncBlockStore contexts = default; contexts.Push(); @@ -784,28 +778,35 @@ private unsafe void DispatchContinuations() asyncDispatcherInfo.NextContinuation = MoveContinuationState(); AsyncDispatcherInfo.t_current = &asyncDispatcherInfo; + AsyncInstrumentation.Flags flags = AsyncInstrumentation.ActiveFlags; + AsyncInstrumentationHelper.ResumeRuntimeAsyncContext(this, ref asyncDispatcherInfo, flags); + while (true) { Debug.Assert(asyncDispatcherInfo.NextContinuation != null); + Continuation curContinuation = asyncDispatcherInfo.NextContinuation; try { - Continuation curContinuation = asyncDispatcherInfo.NextContinuation; Continuation? nextContinuation = curContinuation.Next; asyncDispatcherInfo.NextContinuation = nextContinuation; ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); + AsyncInstrumentationHelper.ResumeRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation); Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); if (newContinuation != null) { newContinuation.Next = nextContinuation; - HandleSuspended(); + AsyncInstrumentationHelper.SuspendRuntimeAsyncContext(flags, curContinuation, newContinuation); + AsyncInstrumentationHelper.HandleSuspended(this, flags, newContinuation); contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; return; } + + AsyncInstrumentationHelper.CompleteRuntimeAsyncMethod(flags, curContinuation); } catch (Exception ex) { @@ -813,6 +814,8 @@ private unsafe void DispatchContinuations() Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation, ex, ref unwindedFrames); if (handlerContinuation == null) { + AsyncInstrumentationHelper.UnwindRuntimeAsyncMethodUnhandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); + // Tail of AsyncTaskMethodBuilderT.SetException bool successfullySet = ex is OperationCanceledException oce ? TrySetCanceled(oce.CancellationToken, oce) : @@ -830,12 +833,16 @@ private unsafe void DispatchContinuations() return; } + AsyncInstrumentationHelper.UnwindRuntimeAsyncMethodHandledException(flags, curContinuation, unwindedFrames); + handlerContinuation.SetException(ex); asyncDispatcherInfo.NextContinuation = handlerContinuation; } if (asyncDispatcherInfo.NextContinuation == null) { + AsyncInstrumentationHelper.CompleteRuntimeAsyncContext(ref asyncDispatcherInfo, flags); + bool successfullySet = TrySetResult(m_result); contexts.Pop(); @@ -852,21 +859,14 @@ private unsafe void DispatchContinuations() if (QueueContinuationFollowUpActionIfNecessary(asyncDispatcherInfo.NextContinuation)) { - contexts.Pop(); - AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; - return; - } - - if (AsyncInstrumentationHelper.InstrumentCheckPoint) - { - SetContinuationState(asyncDispatcherInfo.NextContinuation); + AsyncInstrumentationHelper.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; - - InstrumentedDispatchContinuations(); return; } + + flags = AsyncInstrumentation.ActiveFlags; } } From eaf3cb81b385510249bb7a052509562e0f60f5fd Mon Sep 17 00:00:00 2001 From: lateralusX Date: Wed, 1 Apr 2026 16:57:56 +0200 Subject: [PATCH 11/23] Review fixes. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 3 +-- .../RuntimeAsyncTests.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 9 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 615a1296cbd055..df02e6aed94792 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 @@ -645,8 +645,7 @@ private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, Async private static void DebuggerHandleSuspended(RuntimeAsyncTask task, Continuation? newContinuation = null) { - ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; - Continuation? nc = state.SentinelContinuation!.Next; + Continuation? nc = t_runtimeAsyncAwaitState.SentinelContinuation!.Next; if (nc != null) { diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index a6a76a40419b95..3681ef910af4ef 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -21,8 +21,8 @@ public class RuntimeAsyncTests private static readonly FieldInfo TaskTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncTaskTimestamps"); private static readonly FieldInfo ContinuationTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncContinuationTimestamps"); private static readonly FieldInfo ActiveTasksField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_currentActiveTasks"); - private static readonly FieldInfo TplEventSource = GetCorLibClassStaticField("System.Threading.Tasks.TplEventSource", "Log"); - private static readonly FieldInfo ActiveFlags = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "_activeFlags"); + private static readonly FieldInfo TplEventSourceLogField = GetCorLibClassStaticField("System.Threading.Tasks.TplEventSource", "Log"); + private static readonly FieldInfo ActiveFlagsField = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "_activeFlags"); private static object _debuggerLock = new object(); private static TestEventListener? _debuggerTplInstance; @@ -37,21 +37,21 @@ private static void AttachDebugger() lock (_debuggerLock) { // Touch TplEventSource.Log making sure provider is initialized. - TplEventSource.GetValue(null); + TplEventSourceLogField.GetValue(null); - long flags = Convert.ToInt64(ActiveFlags.GetValue(null)); + long flags = Convert.ToInt64(ActiveFlagsField.GetValue(null)); Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); _debuggerTplInstance = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose); AsyncDebuggingEnabledField.SetValue(null, true); - flags = Convert.ToInt64(ActiveFlags.GetValue(null)); + flags = Convert.ToInt64(ActiveFlagsField.GetValue(null)); Assert.True(flags == EnabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {EnabledInstrumentationFlags}"); // Initialize collections. Func().GetAwaiter().GetResult(); - flags = Convert.ToInt64(ActiveFlags.GetValue(null)); + flags = Convert.ToInt64(ActiveFlagsField.GetValue(null)); Assert.True(flags == EnabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {EnabledInstrumentationFlags}"); var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); @@ -74,7 +74,7 @@ private static void DetachDebugger() _debuggerTplInstance.Dispose(); _debuggerTplInstance = null; - long flags = Convert.ToInt64(ActiveFlags.GetValue(null)); + long flags = Convert.ToInt64(ActiveFlagsField.GetValue(null)); Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); } } From e2f8b011e74b900f82cc6360724691f8931d49a6 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 2 Apr 2026 09:47:46 +0200 Subject: [PATCH 12/23] Test review feedback. --- .../RuntimeAsyncTests.cs | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index 3681ef910af4ef..e05fe44899b6c5 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -15,14 +15,12 @@ public class RuntimeAsyncTests { private static bool IsRemoteExecutorAndRuntimeAsyncSupported => RemoteExecutor.IsSupported && PlatformDetection.IsRuntimeAsyncSupported; - // NOTE: This depends on private implementation details generally only used by the debugger. - // If those ever change, this test will need to be updated as well. - private static readonly FieldInfo AsyncDebuggingEnabledField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_asyncDebuggingEnabled"); - private static readonly FieldInfo TaskTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncTaskTimestamps"); - private static readonly FieldInfo ContinuationTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncContinuationTimestamps"); - private static readonly FieldInfo ActiveTasksField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_currentActiveTasks"); - private static readonly FieldInfo TplEventSourceLogField = GetCorLibClassStaticField("System.Threading.Tasks.TplEventSource", "Log"); - private static readonly FieldInfo ActiveFlagsField = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "_activeFlags"); + private static readonly FieldInfo s_asyncDebuggingEnabledField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_asyncDebuggingEnabled"); + private static readonly FieldInfo s_taskTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncTaskTimestamps"); + private static readonly FieldInfo s_continuationTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncContinuationTimestamps"); + private static readonly FieldInfo s_activeTasksField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_currentActiveTasks"); + private static readonly FieldInfo s_tplEventSourceLogField = GetCorLibClassStaticField("System.Threading.Tasks.TplEventSource", "Log"); + private static readonly FieldInfo s_activeFlagsField = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "_activeFlags"); private static object _debuggerLock = new object(); private static TestEventListener? _debuggerTplInstance; @@ -37,30 +35,30 @@ private static void AttachDebugger() lock (_debuggerLock) { // Touch TplEventSource.Log making sure provider is initialized. - TplEventSourceLogField.GetValue(null); + s_tplEventSourceLogField.GetValue(null); - long flags = Convert.ToInt64(ActiveFlagsField.GetValue(null)); + long flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); _debuggerTplInstance = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose); - AsyncDebuggingEnabledField.SetValue(null, true); + s_asyncDebuggingEnabledField.SetValue(null, true); - flags = Convert.ToInt64(ActiveFlagsField.GetValue(null)); + flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); Assert.True(flags == EnabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {EnabledInstrumentationFlags}"); // Initialize collections. Func().GetAwaiter().GetResult(); - flags = Convert.ToInt64(ActiveFlagsField.GetValue(null)); + flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); Assert.True(flags == EnabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {EnabledInstrumentationFlags}"); - var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); + var activeTasks = (Dictionary)s_activeTasksField.GetValue(null); Assert.True(activeTasks != null, "Expected active tasks dictionary to be initialized"); - var taskTimestamps = (Dictionary)TaskTimestampsField.GetValue(null); + var taskTimestamps = (Dictionary)s_taskTimestampsField.GetValue(null); Assert.True(taskTimestamps != null, "Expected tasks timestamps dictionary to be initialized"); - var continuationTimestamps = (Dictionary)ContinuationTimestampsField.GetValue(null); + var continuationTimestamps = (Dictionary)s_continuationTimestampsField.GetValue(null); Assert.True(continuationTimestamps != null, "Expected continuation timestamps dictionary to be initialized"); } } @@ -70,11 +68,11 @@ private static void DetachDebugger() // Simulate a debugger detach from process. lock (_debuggerLock) { - AsyncDebuggingEnabledField.SetValue(null, false); + s_asyncDebuggingEnabledField.SetValue(null, false); _debuggerTplInstance.Dispose(); _debuggerTplInstance = null; - long flags = Convert.ToInt64(ActiveFlagsField.GetValue(null)); + long flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); } } @@ -97,10 +95,10 @@ private static FieldInfo GetCorLibClassStaticField(string className, string fiel } private static int GetTaskTimestampCount() => - TaskTimestampsField.GetValue(null) is Dictionary dict ? dict.Count : 0; + s_taskTimestampsField.GetValue(null) is Dictionary dict ? dict.Count : 0; private static int GetContinuationTimestampCount() => - ContinuationTimestampsField.GetValue(null) is Dictionary dict ? dict.Count : 0; + s_continuationTimestampsField.GetValue(null) is Dictionary dict ? dict.Count : 0; [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(true)] static async Task Func() @@ -242,7 +240,7 @@ public void RuntimeAsync_DebuggerDetach() { AttachDebugger(); - var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); + var activeTasks = (Dictionary)s_activeTasksField.GetValue(null); // Use an in-flight task to deterministically verify tracking is active var tcs = new TaskCompletionSource(); @@ -256,7 +254,7 @@ public void RuntimeAsync_DebuggerDetach() // Complete the first await so it resumes and sets a task timestamp tcs.SetResult(); - var taskTimestamps = (Dictionary)TaskTimestampsField.GetValue(null); + var taskTimestamps = (Dictionary)s_taskTimestampsField.GetValue(null); bool seenTimestamp = SpinWait.SpinUntil(() => { @@ -378,7 +376,7 @@ public void RuntimeAsync_TimestampsTrackedWhileInFlight() Task inflight = FuncThatWaitsTwice(tcs1, tcs2); // Task is suspended on tcs1 — should be in active tasks - var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); + var activeTasks = (Dictionary)s_activeTasksField.GetValue(null); lock (activeTasks) { @@ -390,7 +388,7 @@ public void RuntimeAsync_TimestampsTrackedWhileInFlight() // Poll until the dispatch loop has resumed and re-suspended on tcs2, // which sets the task timestamp via ResumeRuntimeAsyncMethod. - var taskTimestamps = (Dictionary)TaskTimestampsField.GetValue(null); + var taskTimestamps = (Dictionary)s_taskTimestampsField.GetValue(null); bool seenTimestamp = SpinWait.SpinUntil(() => { @@ -444,7 +442,7 @@ public void RuntimeAsync_ContinuationTimestampObservedDuringResume() // This callback runs inside the resumed async method body, after // ResumeRuntimeAsyncMethod but before SuspendRuntimeAsyncContext/CompleteRuntimeAsyncMethod. // The continuation timestamp for the current continuation should still be in the dictionary. - var continuationTimestamps = (Dictionary)ContinuationTimestampsField.GetValue(null); + var continuationTimestamps = (Dictionary)s_continuationTimestampsField.GetValue(null); lock (continuationTimestamps) { continuationTimestampObserved = continuationTimestamps.Count > 0; @@ -476,8 +474,8 @@ public void RuntimeAsync_InFlightInstrumentationUpgrade() // Attach the debugger mid-flight — this enables instrumentation. AttachDebugger(); - var activeTasks = (Dictionary)ActiveTasksField.GetValue(null); - var taskTimestamps = (Dictionary)TaskTimestampsField.GetValue(null); + var activeTasks = (Dictionary)s_activeTasksField.GetValue(null); + var taskTimestamps = (Dictionary)s_taskTimestampsField.GetValue(null); // The in-flight task was NOT tracked at creation (no instrumentation then). lock (activeTasks) From d9b54a413a09ea0c5a61c7f99a0af44d7e9123bf Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 2 Apr 2026 09:54:43 +0200 Subject: [PATCH 13/23] Adjust naming of a couple of more statics. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 3 +- .../CompilerServices/AsyncInstrumentation.cs | 30 +++++++++---------- .../RuntimeAsyncTests.cs | 2 +- 3 files changed, 17 insertions(+), 18 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 df02e6aed94792..019b4a527f2d60 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 @@ -469,8 +469,7 @@ internal static class AsyncInstrumentationHelper public static bool InstrumentCheckPoint { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => AsyncInstrumentation.IsSupported - && AsyncInstrumentation.ActiveFlags != AsyncInstrumentation.Flags.Disabled; + get => AsyncInstrumentation.IsSupported && AsyncInstrumentation.ActiveFlags != AsyncInstrumentation.Flags.Disabled; } public static void CreateRuntimeAsyncContext(RuntimeAsyncTask task, AsyncInstrumentation.Flags flags) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs index 9aa91efdbdc9d0..044ccf470f352d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs @@ -41,7 +41,7 @@ public static class IsEnabled public static bool DebuggerOrTpl(Flags flags) => Debugger(flags) || Tpl(flags); } - public static Flags ActiveFlags => _activeFlags; + public static Flags ActiveFlags => s_activeFlags; public static Flags UpdateAsyncProfilerFlags(Flags flags) { @@ -50,12 +50,12 @@ public static Flags UpdateAsyncProfilerFlags(Flags flags) flags |= Flags.AsyncProfiler; } - lock (_lock) + lock (s_lock) { - _asyncProfilerActiveFlags = flags; - _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; + s_asyncProfilerActiveFlags = flags; + s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; - return _activeFlags; + return s_activeFlags; } } @@ -83,25 +83,25 @@ public static Flags UpdateTplFlags(EventSource tplEventSource) debuggerFlags |= DebuggerFlags | Flags.Debugger; } - lock (_lock) + lock (s_lock) { - _tplActiveFlags = tplFlags; - _debuggerActiveFlags = debuggerFlags; - _activeFlags = _asyncProfilerActiveFlags | _tplActiveFlags | _debuggerActiveFlags; + s_tplActiveFlags = tplFlags; + s_debuggerActiveFlags = debuggerFlags; + s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; - return _activeFlags; + return s_activeFlags; } } - private static Flags _activeFlags; + private static Flags s_activeFlags; - private static Flags _asyncProfilerActiveFlags; + private static Flags s_asyncProfilerActiveFlags; - private static Flags _tplActiveFlags; + private static Flags s_tplActiveFlags; - private static Flags _debuggerActiveFlags; + private static Flags s_debuggerActiveFlags; - private static readonly object _lock = new object(); + private static readonly object s_lock = new object(); private const Flags DebuggerFlags = Flags.CreateAsyncContext | Flags.SuspendAsyncContext | diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index e05fe44899b6c5..f0c50a0aa9d743 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -20,7 +20,7 @@ public class RuntimeAsyncTests private static readonly FieldInfo s_continuationTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncContinuationTimestamps"); private static readonly FieldInfo s_activeTasksField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_currentActiveTasks"); private static readonly FieldInfo s_tplEventSourceLogField = GetCorLibClassStaticField("System.Threading.Tasks.TplEventSource", "Log"); - private static readonly FieldInfo s_activeFlagsField = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "_activeFlags"); + private static readonly FieldInfo s_activeFlagsField = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "s_activeFlags"); private static object _debuggerLock = new object(); private static TestEventListener? _debuggerTplInstance; From 6036abe32f124af05c7547e619ed14488f8a35dc Mon Sep 17 00:00:00 2001 From: Johan Lorensson Date: Thu, 2 Apr 2026 10:16:32 +0200 Subject: [PATCH 14/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Runtime.CompilerServices/RuntimeAsyncTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index f0c50a0aa9d743..4fa54ef6aae776 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -69,7 +69,7 @@ private static void DetachDebugger() lock (_debuggerLock) { s_asyncDebuggingEnabledField.SetValue(null, false); - _debuggerTplInstance.Dispose(); + _debuggerTplInstance?.Dispose(); _debuggerTplInstance = null; long flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); From d9101b64eafa44f9eff848c24317dce02b9ad97e Mon Sep 17 00:00:00 2001 From: Johan Lorensson Date: Thu, 2 Apr 2026 10:20:23 +0200 Subject: [PATCH 15/23] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 11 +++++++++-- 1 file changed, 9 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 019b4a527f2d60..f3a27ba282b89e 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 @@ -656,13 +656,20 @@ private static void DebuggerHandleSuspended(RuntimeAsyncTask task, Continuati if (!task.HandleSuspended()) { - RemoveRuntimeAsyncTask(task); + if (nc != null) + { + RemoveRuntimeAsyncTask(task, nc); + } + else + { + RemoveRuntimeAsyncTask(task); + } } } } [StackTraceHidden] - // NOTE, any changes done to this method needs to be replicate in InstrumentedDispatchContinuations as well. + // NOTE, any changes done to this method need to be replicated in InstrumentedDispatchContinuations as well. private unsafe void DispatchContinuations() { if (AsyncInstrumentationHelper.InstrumentCheckPoint) From 59809e3e8d05ff497c59e5e3604ffc0f35229774 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 2 Apr 2026 10:29:58 +0200 Subject: [PATCH 16/23] Review feedback. --- .../CompilerServices/AsyncInstrumentation.cs | 1 + .../RuntimeAsyncTests.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs index 044ccf470f352d..b72a4882f6f73a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs @@ -11,6 +11,7 @@ internal static class AsyncInstrumentation { public static bool IsSupported => Debugger.IsSupported || EventSource.IsSupported; + [Flags] public enum Flags { Disabled = 0x0, diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index 4fa54ef6aae776..8c39a84ef515ba 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -22,8 +22,8 @@ public class RuntimeAsyncTests private static readonly FieldInfo s_tplEventSourceLogField = GetCorLibClassStaticField("System.Threading.Tasks.TplEventSource", "Log"); private static readonly FieldInfo s_activeFlagsField = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "s_activeFlags"); - private static object _debuggerLock = new object(); - private static TestEventListener? _debuggerTplInstance; + private static readonly object s_debuggerLock = new object(); + private static TestEventListener? s_debuggerTplInstance; // Tpl(0x20000) | Debugger(0x40000) | all event flags(0x7F) private const long EnabledInstrumentationFlags = 0x6007F; @@ -32,7 +32,7 @@ public class RuntimeAsyncTests private static void AttachDebugger() { // Simulate a debugger attach to process, creating TPL event source session + setting s_asyncDebuggingEnabled. - lock (_debuggerLock) + lock (s_debuggerLock) { // Touch TplEventSource.Log making sure provider is initialized. s_tplEventSourceLogField.GetValue(null); @@ -40,7 +40,7 @@ private static void AttachDebugger() long flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); - _debuggerTplInstance = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose); + s_debuggerTplInstance = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose); s_asyncDebuggingEnabledField.SetValue(null, true); flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); @@ -66,11 +66,11 @@ private static void AttachDebugger() private static void DetachDebugger() { // Simulate a debugger detach from process. - lock (_debuggerLock) + lock (s_debuggerLock) { s_asyncDebuggingEnabledField.SetValue(null, false); - _debuggerTplInstance?.Dispose(); - _debuggerTplInstance = null; + s_debuggerTplInstance?.Dispose(); + s_debuggerTplInstance = null; long flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); From 124fcde2792c202cd768cff43117d17da915abe4 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Tue, 7 Apr 2026 13:17:58 +0200 Subject: [PATCH 17/23] Make sure instrumentation flags are init and synched before used. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 24 ++++--- .../CompilerServices/AsyncInstrumentation.cs | 67 ++++++++++++------- .../System/Threading/Tasks/TplEventSource.cs | 15 ++++- .../RuntimeAsyncTests.cs | 22 +++--- 4 files changed, 81 insertions(+), 47 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 f3a27ba282b89e..f7e6c2531887ae 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 @@ -674,8 +674,12 @@ private unsafe void DispatchContinuations() { if (AsyncInstrumentationHelper.InstrumentCheckPoint) { - InstrumentedDispatchContinuations(); - return; + AsyncInstrumentation.Flags flags = AsyncInstrumentation.SyncActiveFlags(); + if (flags != AsyncInstrumentation.Flags.Disabled) + { + InstrumentedDispatchContinuations(flags); + return; + } } ExecutionAndSyncBlockStore contexts = default; @@ -766,14 +770,14 @@ private unsafe void DispatchContinuations() contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; - InstrumentedDispatchContinuations(); + InstrumentedDispatchContinuations(AsyncInstrumentation.ActiveFlags); return; } } } [StackTraceHidden] - private unsafe void InstrumentedDispatchContinuations() + private unsafe void InstrumentedDispatchContinuations(AsyncInstrumentation.Flags flags) { ExecutionAndSyncBlockStore contexts = default; contexts.Push(); @@ -783,7 +787,6 @@ private unsafe void InstrumentedDispatchContinuations() asyncDispatcherInfo.NextContinuation = MoveContinuationState(); AsyncDispatcherInfo.t_current = &asyncDispatcherInfo; - AsyncInstrumentation.Flags flags = AsyncInstrumentation.ActiveFlags; AsyncInstrumentationHelper.ResumeRuntimeAsyncContext(this, ref asyncDispatcherInfo, flags); while (true) @@ -979,10 +982,13 @@ private static void FinalizeRuntimeAsyncTask(RuntimeAsyncTask task) { if (RuntimeAsyncTask.AsyncInstrumentationHelper.InstrumentCheckPoint) { - AsyncInstrumentation.Flags flags = AsyncInstrumentation.ActiveFlags; - RuntimeAsyncTask.AsyncInstrumentationHelper.CreateRuntimeAsyncContext(task, flags); - RuntimeAsyncTask.AsyncInstrumentationHelper.HandleSuspended(task, flags); - return; + AsyncInstrumentation.Flags flags = AsyncInstrumentation.SyncActiveFlags(); + if (flags != AsyncInstrumentation.Flags.Disabled) + { + RuntimeAsyncTask.AsyncInstrumentationHelper.CreateRuntimeAsyncContext(task, flags); + RuntimeAsyncTask.AsyncInstrumentationHelper.HandleSuspended(task, flags); + return; + } } task.HandleSuspended(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs index b72a4882f6f73a..27825e7dd431e0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs @@ -12,7 +12,7 @@ internal static class AsyncInstrumentation public static bool IsSupported => Debugger.IsSupported || EventSource.IsSupported; [Flags] - public enum Flags + public enum Flags : uint { Disabled = 0x0, CreateAsyncContext = 0x1, @@ -44,40 +44,40 @@ public static class IsEnabled public static Flags ActiveFlags => s_activeFlags; - public static Flags UpdateAsyncProfilerFlags(Flags flags) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Flags SyncActiveFlags() { - if (flags != Flags.Disabled) + Flags flags = ActiveFlags; + if (IsUninitialized(flags)) { - flags |= Flags.AsyncProfiler; + return InitializeFlags(); } + return flags; + } - lock (s_lock) + public static void UpdateAsyncProfilerFlags(Flags asyncProfilerFlags) + { + if (asyncProfilerFlags != Flags.Disabled) { - s_asyncProfilerActiveFlags = flags; - s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; + asyncProfilerFlags |= Flags.AsyncProfiler; + } - return s_activeFlags; + lock (s_lock) + { + s_asyncProfilerActiveFlags = asyncProfilerFlags; + if (IsInitialized(s_activeFlags)) + { + s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; + } } } - public static Flags UpdateTplFlags(EventSource tplEventSource) + public static void UpdateTplFlags(Flags tplFlags) { // Until debugger sets its flags directly, piggy back on TPL instrumentation since debugger will enable/disable TPL events // when attaching/detaching to the runtime. - Flags tplFlags = Flags.Disabled; Flags debuggerFlags = Flags.Disabled; - tplFlags |= tplEventSource.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.AsyncCausalitySynchronousWork) ? - Flags.ResumeAsyncContext | - Flags.SuspendAsyncContext | - Flags.CompleteAsyncContext | - Flags.UnwindAsyncException : 0; - - tplFlags |= tplEventSource.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.AsyncCausalityOperation) ? - Flags.CreateAsyncContext | - Flags.CompleteAsyncContext | - Flags.UnwindAsyncException : 0; - if (tplFlags != Flags.Disabled) { tplFlags |= Flags.Tpl; @@ -88,13 +88,34 @@ public static Flags UpdateTplFlags(EventSource tplEventSource) { s_tplActiveFlags = tplFlags; s_debuggerActiveFlags = debuggerFlags; - s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; + if (IsInitialized(s_activeFlags)) + { + s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; + } + } + } + + private const uint UninitializedFlag = 0x80000000; + + private static bool IsInitialized(Flags flags) => !IsUninitialized(flags); + private static bool IsUninitialized(Flags flags) => (flags & (Flags)UninitializedFlag) != 0; + + private static Flags InitializeFlags() + { + _ = TplEventSource.Log; // Touch TplEventSource to trigger static constructor which will initialize TPL flags if EventSource is supported. + + lock (s_lock) + { + if (IsUninitialized(s_activeFlags)) + { + s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; + } return s_activeFlags; } } - private static Flags s_activeFlags; + private static Flags s_activeFlags = (Flags)UninitializedFlag; private static Flags s_asyncProfilerActiveFlags; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs index eb228d3370154c..5f5f1a0502b93a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs @@ -35,7 +35,20 @@ protected override void OnEventCommand(EventCommandEventArgs command) Debug = IsEnabled(EventLevel.Informational, Keywords.Debug); DebugActivityId = IsEnabled(EventLevel.Informational, Keywords.DebugActivityId); - AsyncInstrumentation.UpdateTplFlags(this); + AsyncInstrumentation.Flags tplFlags = AsyncInstrumentation.Flags.Disabled; + + tplFlags |= IsEnabled(EventLevel.Informational, Keywords.AsyncCausalitySynchronousWork) ? + AsyncInstrumentation.Flags.ResumeAsyncContext | + AsyncInstrumentation.Flags.SuspendAsyncContext | + AsyncInstrumentation.Flags.CompleteAsyncContext | + AsyncInstrumentation.Flags.UnwindAsyncException : 0; + + tplFlags |= IsEnabled(EventLevel.Informational, Keywords.AsyncCausalityOperation) ? + AsyncInstrumentation.Flags.CreateAsyncContext | + AsyncInstrumentation.Flags.CompleteAsyncContext | + AsyncInstrumentation.Flags.UnwindAsyncException : 0; + + AsyncInstrumentation.UpdateTplFlags(tplFlags); } /// diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index 8c39a84ef515ba..ab5f169a62fa0d 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -19,37 +19,31 @@ public class RuntimeAsyncTests private static readonly FieldInfo s_taskTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncTaskTimestamps"); private static readonly FieldInfo s_continuationTimestampsField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_runtimeAsyncContinuationTimestamps"); private static readonly FieldInfo s_activeTasksField = GetCorLibClassStaticField("System.Threading.Tasks.Task", "s_currentActiveTasks"); - private static readonly FieldInfo s_tplEventSourceLogField = GetCorLibClassStaticField("System.Threading.Tasks.TplEventSource", "Log"); private static readonly FieldInfo s_activeFlagsField = GetCorLibClassStaticField("System.Runtime.CompilerServices.AsyncInstrumentation", "s_activeFlags"); private static readonly object s_debuggerLock = new object(); private static TestEventListener? s_debuggerTplInstance; // Tpl(0x20000) | Debugger(0x40000) | all event flags(0x7F) - private const long EnabledInstrumentationFlags = 0x6007F; - private const long DisabledInstrumentationFlags = 0x0; + private const uint EnabledInstrumentationFlags = 0x6007F; + private const uint DisabledInstrumentationFlags = 0x0; + private const uint UninitializedInstrumentationFlags = 0x80000000; private static void AttachDebugger() { // Simulate a debugger attach to process, creating TPL event source session + setting s_asyncDebuggingEnabled. lock (s_debuggerLock) { - // Touch TplEventSource.Log making sure provider is initialized. - s_tplEventSourceLogField.GetValue(null); - - long flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); - Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); + uint flags = Convert.ToUInt32(s_activeFlagsField.GetValue(null)); + Assert.True(flags == UninitializedInstrumentationFlags || flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {UninitializedInstrumentationFlags} || {DisabledInstrumentationFlags}"); s_debuggerTplInstance = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose); s_asyncDebuggingEnabledField.SetValue(null, true); - flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); - Assert.True(flags == EnabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {EnabledInstrumentationFlags}"); - - // Initialize collections. + // Initialize flags and collections. Func().GetAwaiter().GetResult(); - flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); + flags = Convert.ToUInt32(s_activeFlagsField.GetValue(null)); Assert.True(flags == EnabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {EnabledInstrumentationFlags}"); var activeTasks = (Dictionary)s_activeTasksField.GetValue(null); @@ -72,7 +66,7 @@ private static void DetachDebugger() s_debuggerTplInstance?.Dispose(); s_debuggerTplInstance = null; - long flags = Convert.ToInt64(s_activeFlagsField.GetValue(null)); + uint flags = Convert.ToUInt32(s_activeFlagsField.GetValue(null)); Assert.True(flags == DisabledInstrumentationFlags, $"ActiveFlags equals {flags}, expected {DisabledInstrumentationFlags}"); } } From fef3bd1e05092e36e5abf22334bfadcdcf606a5f Mon Sep 17 00:00:00 2001 From: Johan Lorensson Date: Tue, 7 Apr 2026 13:25:44 +0200 Subject: [PATCH 18/23] Update src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/System/Threading/Tasks/TplEventSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs index 5f5f1a0502b93a..970aa7b716304a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs @@ -41,12 +41,12 @@ protected override void OnEventCommand(EventCommandEventArgs command) AsyncInstrumentation.Flags.ResumeAsyncContext | AsyncInstrumentation.Flags.SuspendAsyncContext | AsyncInstrumentation.Flags.CompleteAsyncContext | - AsyncInstrumentation.Flags.UnwindAsyncException : 0; + AsyncInstrumentation.Flags.UnwindAsyncException : AsyncInstrumentation.Flags.Disabled; tplFlags |= IsEnabled(EventLevel.Informational, Keywords.AsyncCausalityOperation) ? AsyncInstrumentation.Flags.CreateAsyncContext | AsyncInstrumentation.Flags.CompleteAsyncContext | - AsyncInstrumentation.Flags.UnwindAsyncException : 0; + AsyncInstrumentation.Flags.UnwindAsyncException : AsyncInstrumentation.Flags.Disabled; AsyncInstrumentation.UpdateTplFlags(tplFlags); } From 2a6d76a1631ae75571bbf6553b4997bb0fe26205 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 9 Apr 2026 12:16:34 +0200 Subject: [PATCH 19/23] Reduce generic specialization. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 407 +++++++++--------- 1 file changed, 204 insertions(+), 203 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 f7e6c2531887ae..35657970204165 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 @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Diagnostics.Tracing; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.InteropServices; @@ -457,222 +456,56 @@ internal bool HandleSuspended() return false; } -#pragma warning disable CA1822 // Mark members as static - [MethodImpl(MethodImplOptions.NoOptimization)] - public void NotifyDebuggerOfRuntimeAsyncState() - { - } -#pragma warning restore CA1822 - - internal static class AsyncInstrumentationHelper + internal bool InstrumentedHandleSuspended(AsyncInstrumentation.Flags flags, Continuation? newContinuation = null) { - public static bool InstrumentCheckPoint - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => AsyncInstrumentation.IsSupported && AsyncInstrumentation.ActiveFlags != AsyncInstrumentation.Flags.Disabled; - } - - public static void CreateRuntimeAsyncContext(RuntimeAsyncTask task, AsyncInstrumentation.Flags flags) - { - if (AsyncInstrumentation.IsEnabled.CreateAsyncContext(flags)) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - task.NotifyDebuggerOfRuntimeAsyncState(); - AddToActiveTasks(task); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { - TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResumeRuntimeAsyncContext(RuntimeAsyncTask task, ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags) - { - info.CurrentTask = task; - - if (AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags)) - { - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { - TplEventSource.Log.TraceSynchronousWorkBegin(task.Id, CausalitySynchronousWork.Execution); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Continuation curContinuation) - { - if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags)) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags) && info.NextContinuation != null) - { - TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SuspendRuntimeAsyncContext(AsyncInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation) - { - if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags)) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags) - { - if (AsyncInstrumentation.IsEnabled.CompleteAsyncContext(flags)) - { - if (AsyncInstrumentation.IsEnabled.DebuggerOrTpl(flags)) - { - CompleteRuntimeAsyncContext(info.CurrentTask, flags); - } - } - } - - public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) - { - if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) - { - if (AsyncInstrumentation.IsEnabled.DebuggerOrTpl(flags)) - { - UnwindRuntimeAsyncMethodUnhandledException(info.CurrentTask, flags, ex, curContinuation, unwindedFrames); - } - } - } - - public static void UnwindRuntimeAsyncMethodHandledException(AsyncInstrumentation.Flags flags, Continuation curContinuation, uint unwindedFrames) - { - if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Continuation curContinuation) - { - if (AsyncInstrumentation.IsEnabled.ResumeAsyncMethod(flags)) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags) && info.CurrentTask != null) - { - UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CompleteRuntimeAsyncMethod(AsyncInstrumentation.Flags flags, Continuation curContinuation) - { - if (AsyncInstrumentation.IsEnabled.CompleteAsyncMethod(flags)) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - RemoveRuntimeAsyncContinuationTimestamp(curContinuation); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HandleSuspended(RuntimeAsyncTask task, AsyncInstrumentation.Flags flags, Continuation? newContinuation = null) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - DebuggerHandleSuspended(task, newContinuation); - } - else - { - task.HandleSuspended(); - } - } - - private static void CompleteRuntimeAsyncContext(Task? task, AsyncInstrumentation.Flags flags) - { - if (task != null) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - RemoveRuntimeAsyncTask(task); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { - TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } - } - } - - private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint _) - { - if (task != null) - { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - RemoveRuntimeAsyncTask(task, curContinuation); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { - TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } - } - } - - private static void DebuggerHandleSuspended(RuntimeAsyncTask task, Continuation? newContinuation = null) + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) { Continuation? nc = t_runtimeAsyncAwaitState.SentinelContinuation!.Next; if (nc != null) { if (newContinuation != null) + { TryAddRuntimeAsyncContinuationChainTimestamps(nc, newContinuation); + } else + { TryAddRuntimeAsyncContinuationChainTimestamps(nc); + } } - if (!task.HandleSuspended()) + if (!HandleSuspended()) { if (nc != null) { - RemoveRuntimeAsyncTask(task, nc); + RemoveRuntimeAsyncTask(this, nc); } else { - RemoveRuntimeAsyncTask(task); + RemoveRuntimeAsyncTask(this); } + + return false; } + + return true; } + + return HandleSuspended(); } +#pragma warning disable CA1822 // Mark members as static + [MethodImpl(MethodImplOptions.NoOptimization)] + public void NotifyDebuggerOfRuntimeAsyncState() + { + } +#pragma warning restore CA1822 + [StackTraceHidden] // NOTE, any changes done to this method need to be replicated in InstrumentedDispatchContinuations as well. private unsafe void DispatchContinuations() { - if (AsyncInstrumentationHelper.InstrumentCheckPoint) + if (RuntimeAsyncInstrumentationHelpers.InstrumentCheckPoint) { AsyncInstrumentation.Flags flags = AsyncInstrumentation.SyncActiveFlags(); if (flags != AsyncInstrumentation.Flags.Disabled) @@ -763,7 +596,7 @@ private unsafe void DispatchContinuations() return; } - if (AsyncInstrumentationHelper.InstrumentCheckPoint) + if (RuntimeAsyncInstrumentationHelpers.InstrumentCheckPoint) { SetContinuationState(asyncDispatcherInfo.NextContinuation); @@ -787,7 +620,7 @@ private unsafe void InstrumentedDispatchContinuations(AsyncInstrumentation.Flags asyncDispatcherInfo.NextContinuation = MoveContinuationState(); AsyncDispatcherInfo.t_current = &asyncDispatcherInfo; - AsyncInstrumentationHelper.ResumeRuntimeAsyncContext(this, ref asyncDispatcherInfo, flags); + RuntimeAsyncInstrumentationHelpers.ResumeRuntimeAsyncContext(this, ref asyncDispatcherInfo, flags); while (true) { @@ -800,21 +633,21 @@ private unsafe void InstrumentedDispatchContinuations(AsyncInstrumentation.Flags ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); - AsyncInstrumentationHelper.ResumeRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation); + RuntimeAsyncInstrumentationHelpers.ResumeRuntimeAsyncMethod(ref asyncDispatcherInfo, flags, curContinuation); Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); if (newContinuation != null) { newContinuation.Next = nextContinuation; - AsyncInstrumentationHelper.SuspendRuntimeAsyncContext(flags, curContinuation, newContinuation); - AsyncInstrumentationHelper.HandleSuspended(this, flags, newContinuation); + RuntimeAsyncInstrumentationHelpers.SuspendRuntimeAsyncContext(flags, curContinuation, newContinuation); + InstrumentedHandleSuspended(flags, newContinuation); contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; return; } - AsyncInstrumentationHelper.CompleteRuntimeAsyncMethod(flags, curContinuation); + RuntimeAsyncInstrumentationHelpers.CompleteRuntimeAsyncMethod(flags, curContinuation); } catch (Exception ex) { @@ -822,7 +655,7 @@ private unsafe void InstrumentedDispatchContinuations(AsyncInstrumentation.Flags Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation, ex, ref unwindedFrames); if (handlerContinuation == null) { - AsyncInstrumentationHelper.UnwindRuntimeAsyncMethodUnhandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); + RuntimeAsyncInstrumentationHelpers.UnwindRuntimeAsyncMethodUnhandledException(ref asyncDispatcherInfo, flags, ex, curContinuation, unwindedFrames); // Tail of AsyncTaskMethodBuilderT.SetException bool successfullySet = ex is OperationCanceledException oce ? @@ -841,7 +674,7 @@ private unsafe void InstrumentedDispatchContinuations(AsyncInstrumentation.Flags return; } - AsyncInstrumentationHelper.UnwindRuntimeAsyncMethodHandledException(flags, curContinuation, unwindedFrames); + RuntimeAsyncInstrumentationHelpers.UnwindRuntimeAsyncMethodHandledException(flags, curContinuation, unwindedFrames); handlerContinuation.SetException(ex); asyncDispatcherInfo.NextContinuation = handlerContinuation; @@ -849,7 +682,7 @@ private unsafe void InstrumentedDispatchContinuations(AsyncInstrumentation.Flags if (asyncDispatcherInfo.NextContinuation == null) { - AsyncInstrumentationHelper.CompleteRuntimeAsyncContext(ref asyncDispatcherInfo, flags); + RuntimeAsyncInstrumentationHelpers.CompleteRuntimeAsyncContext(ref asyncDispatcherInfo, flags); bool successfullySet = TrySetResult(m_result); @@ -867,7 +700,7 @@ private unsafe void InstrumentedDispatchContinuations(AsyncInstrumentation.Flags if (QueueContinuationFollowUpActionIfNecessary(asyncDispatcherInfo.NextContinuation)) { - AsyncInstrumentationHelper.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); + RuntimeAsyncInstrumentationHelpers.SuspendRuntimeAsyncContext(ref asyncDispatcherInfo, flags, curContinuation); contexts.Pop(); AsyncDispatcherInfo.t_current = asyncDispatcherInfo.Next; @@ -977,16 +810,35 @@ private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuatio }; } + private static void InstrumentedFinalizeRuntimeAsyncTask(RuntimeAsyncTask task, AsyncInstrumentation.Flags flags) + { + if (AsyncInstrumentation.IsEnabled.CreateAsyncContext(flags)) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + { + task.NotifyDebuggerOfRuntimeAsyncState(); + Task.AddToActiveTasks(task); + } + + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); + } + } + + task.InstrumentedHandleSuspended(flags); + return; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void FinalizeRuntimeAsyncTask(RuntimeAsyncTask task) { - if (RuntimeAsyncTask.AsyncInstrumentationHelper.InstrumentCheckPoint) + if (RuntimeAsyncInstrumentationHelpers.InstrumentCheckPoint) { AsyncInstrumentation.Flags flags = AsyncInstrumentation.SyncActiveFlags(); if (flags != AsyncInstrumentation.Flags.Disabled) { - RuntimeAsyncTask.AsyncInstrumentationHelper.CreateRuntimeAsyncContext(task, flags); - RuntimeAsyncTask.AsyncInstrumentationHelper.HandleSuspended(task, flags); + InstrumentedFinalizeRuntimeAsyncTask(task, flags); return; } } @@ -1168,5 +1020,154 @@ internal static void CompletedTask(Task task) { TaskAwaiter.ValidateEnd(task); } + + internal static class RuntimeAsyncInstrumentationHelpers + { + public static bool InstrumentCheckPoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsyncInstrumentation.IsSupported && AsyncInstrumentation.ActiveFlags != AsyncInstrumentation.Flags.Disabled; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ResumeRuntimeAsyncContext(Task task, ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags) + { + info.CurrentTask = task; + + if (AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags)) + { + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceSynchronousWorkBegin(task.Id, CausalitySynchronousWork.Execution); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Continuation curContinuation) + { + if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags)) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags) && info.NextContinuation != null) + { + Task.TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); + } + + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SuspendRuntimeAsyncContext(AsyncInstrumentation.Flags flags, Continuation curContinuation, Continuation newContinuation) + { + if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags)) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + { + Task.ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); + } + + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags) + { + if (AsyncInstrumentation.IsEnabled.CompleteAsyncContext(flags)) + { + if (AsyncInstrumentation.IsEnabled.DebuggerOrTpl(flags)) + { + CompleteRuntimeAsyncContext(info.CurrentTask, flags); + } + } + } + + public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) + { + if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) + { + if (AsyncInstrumentation.IsEnabled.DebuggerOrTpl(flags)) + { + UnwindRuntimeAsyncMethodUnhandledException(info.CurrentTask, flags, ex, curContinuation, unwindedFrames); + } + } + } + + public static void UnwindRuntimeAsyncMethodHandledException(AsyncInstrumentation.Flags flags, Continuation curContinuation, uint unwindedFrames) + { + if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + { + Task.RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Continuation curContinuation) + { + if (AsyncInstrumentation.IsEnabled.ResumeAsyncMethod(flags)) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags) && info.CurrentTask != null) + { + Task.UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CompleteRuntimeAsyncMethod(AsyncInstrumentation.Flags flags, Continuation curContinuation) + { + if (AsyncInstrumentation.IsEnabled.CompleteAsyncMethod(flags)) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + { + Task.RemoveRuntimeAsyncContinuationTimestamp(curContinuation); + } + } + } + + private static void CompleteRuntimeAsyncContext(Task? task, AsyncInstrumentation.Flags flags) + { + if (task != null) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + { + Task.RemoveRuntimeAsyncTask(task); + } + + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + } + + private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint _) + { + if (task != null) + { + if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + { + Task.RemoveRuntimeAsyncTask(task, curContinuation); + } + + if (AsyncInstrumentation.IsEnabled.Tpl(flags)) + { + TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + } + } } } From db70b1b7b77b2b827f584f61fab721acb44fc124 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 9 Apr 2026 15:30:05 +0200 Subject: [PATCH 20/23] Merge debugger and tpl flags. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 70 ++++++----------- .../CompilerServices/AsyncInstrumentation.cs | 76 ++++++++++--------- .../System/Threading/Tasks/TplEventSource.cs | 36 +++++---- .../RuntimeAsyncTests.cs | 44 ++++++++++- 4 files changed, 129 insertions(+), 97 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 35657970204165..36372df37e3116 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 @@ -458,7 +458,7 @@ internal bool HandleSuspended() internal bool InstrumentedHandleSuspended(AsyncInstrumentation.Flags flags, Continuation? newContinuation = null) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { Continuation? nc = t_runtimeAsyncAwaitState.SentinelContinuation!.Next; @@ -814,14 +814,10 @@ private static void InstrumentedFinalizeRuntimeAsyncTask(RuntimeAsyncTask { if (AsyncInstrumentation.IsEnabled.CreateAsyncContext(flags)) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { task.NotifyDebuggerOfRuntimeAsyncState(); Task.AddToActiveTasks(task); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); } } @@ -1036,7 +1032,7 @@ public static void ResumeRuntimeAsyncContext(Task task, ref AsyncDispatcherInfo if (AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags)) { - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { TplEventSource.Log.TraceSynchronousWorkBegin(task.Id, CausalitySynchronousWork.Execution); } @@ -1048,13 +1044,13 @@ public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, Asyn { if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags)) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags) && info.NextContinuation != null) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - Task.TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); - } + if (info.NextContinuation != null) + { + Task.TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); + } - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } } @@ -1065,13 +1061,9 @@ public static void SuspendRuntimeAsyncContext(AsyncInstrumentation.Flags flags, { if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags)) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { Task.ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } } @@ -1082,9 +1074,9 @@ public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, Asy { if (AsyncInstrumentation.IsEnabled.CompleteAsyncContext(flags)) { - if (AsyncInstrumentation.IsEnabled.DebuggerOrTpl(flags)) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - CompleteRuntimeAsyncContext(info.CurrentTask, flags); + CompleteRuntimeAsyncContext(info.CurrentTask); } } } @@ -1093,9 +1085,9 @@ public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatche { if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) { - if (AsyncInstrumentation.IsEnabled.DebuggerOrTpl(flags)) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - UnwindRuntimeAsyncMethodUnhandledException(info.CurrentTask, flags, ex, curContinuation, unwindedFrames); + UnwindRuntimeAsyncMethodUnhandledException(info.CurrentTask, ex, curContinuation, unwindedFrames); } } } @@ -1104,7 +1096,7 @@ public static void UnwindRuntimeAsyncMethodHandledException(AsyncInstrumentation { if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { Task.RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); } @@ -1116,7 +1108,7 @@ public static void ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, AsyncI { if (AsyncInstrumentation.IsEnabled.ResumeAsyncMethod(flags)) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags) && info.CurrentTask != null) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags) && info.CurrentTask != null) { Task.UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); } @@ -1128,44 +1120,30 @@ public static void CompleteRuntimeAsyncMethod(AsyncInstrumentation.Flags flags, { if (AsyncInstrumentation.IsEnabled.CompleteAsyncMethod(flags)) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { Task.RemoveRuntimeAsyncContinuationTimestamp(curContinuation); } } } - private static void CompleteRuntimeAsyncContext(Task? task, AsyncInstrumentation.Flags flags) + private static void CompleteRuntimeAsyncContext(Task? task) { if (task != null) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - Task.RemoveRuntimeAsyncTask(task); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { - TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } + Task.RemoveRuntimeAsyncTask(task); + TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } } - private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint _) + private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, Exception ex, Continuation curContinuation, uint _) { if (task != null) { - if (AsyncInstrumentation.IsEnabled.Debugger(flags)) - { - Task.RemoveRuntimeAsyncTask(task, curContinuation); - } - - if (AsyncInstrumentation.IsEnabled.Tpl(flags)) - { - TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } + Task.RemoveRuntimeAsyncTask(task, curContinuation); + TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs index 27825e7dd431e0..72934077767e48 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using System.Diagnostics.Tracing; @@ -9,12 +10,14 @@ namespace System.Runtime.CompilerServices { internal static class AsyncInstrumentation { - public static bool IsSupported => Debugger.IsSupported || EventSource.IsSupported; + public static bool IsSupported => Debugger.IsSupported && EventSource.IsSupported; [Flags] public enum Flags : uint { Disabled = 0x0, + + // Bit 1 - 24 reserved for async instrumentation points. CreateAsyncContext = 0x1, ResumeAsyncContext = 0x2, SuspendAsyncContext = 0x4, @@ -22,11 +25,20 @@ public enum Flags : uint UnwindAsyncException = 0x10, ResumeAsyncMethod = 0x20, CompleteAsyncMethod = 0x40, - AsyncProfiler = 0x10000, - Tpl = 0x20000, - Debugger = 0x40000 + + // Bit 25 - 31 reserved for instrumentation clients. + AsyncProfiler = 0x1000000, + AsyncDebugger = 0x2000000, + + // Bit 32 reserved for initialization state. + Uninitialized = 0x80000000 } + public const Flags DefaultFlags = + Flags.CreateAsyncContext | Flags.ResumeAsyncContext | Flags.SuspendAsyncContext | + Flags.CompleteAsyncContext | Flags.UnwindAsyncException | + Flags.ResumeAsyncMethod | Flags.CompleteAsyncMethod; + public static class IsEnabled { public static bool CreateAsyncContext(Flags flags) => (Flags.CreateAsyncContext & flags) != 0; @@ -37,17 +49,23 @@ public static class IsEnabled public static bool ResumeAsyncMethod(Flags flags) => (Flags.ResumeAsyncMethod & flags) != 0; public static bool CompleteAsyncMethod(Flags flags) => (Flags.CompleteAsyncMethod & flags) != 0; public static bool AsyncProfiler(Flags flags) => (Flags.AsyncProfiler & flags) != 0; - public static bool Tpl(Flags flags) => (Flags.Tpl & flags) != 0; - public static bool Debugger(Flags flags) => (Flags.Debugger & flags) != 0 && Task.s_asyncDebuggingEnabled; - public static bool DebuggerOrTpl(Flags flags) => Debugger(flags) || Tpl(flags); + public static bool AsyncDebugger(Flags flags) => (Flags.AsyncDebugger & flags) != 0 && Task.s_asyncDebuggingEnabled; } - public static Flags ActiveFlags => s_activeFlags; + public static Flags ActiveFlags + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(IsInitialized(s_activeFlags), "ActiveFlags accessed before initialized. Make sure SyncActiveFlags gets called first."); + return s_activeFlags; + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Flags SyncActiveFlags() { - Flags flags = ActiveFlags; + Flags flags = s_activeFlags; if (IsUninitialized(flags)) { return InitializeFlags(); @@ -67,39 +85,28 @@ public static void UpdateAsyncProfilerFlags(Flags asyncProfilerFlags) s_asyncProfilerActiveFlags = asyncProfilerFlags; if (IsInitialized(s_activeFlags)) { - s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; + s_activeFlags = s_asyncProfilerActiveFlags | s_asyncDebuggerActiveFlags; } } } - public static void UpdateTplFlags(Flags tplFlags) + public static void UpdateAsyncDebuggerFlags(Flags asyncDebuggerFlags) { - // Until debugger sets its flags directly, piggy back on TPL instrumentation since debugger will enable/disable TPL events - // when attaching/detaching to the runtime. - Flags debuggerFlags = Flags.Disabled; - - if (tplFlags != Flags.Disabled) + if (asyncDebuggerFlags != Flags.Disabled) { - tplFlags |= Flags.Tpl; - debuggerFlags |= DebuggerFlags | Flags.Debugger; + asyncDebuggerFlags |= Flags.AsyncDebugger; } lock (s_lock) { - s_tplActiveFlags = tplFlags; - s_debuggerActiveFlags = debuggerFlags; + s_asyncDebuggerActiveFlags = asyncDebuggerFlags; if (IsInitialized(s_activeFlags)) { - s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; + s_activeFlags = s_asyncProfilerActiveFlags | s_asyncDebuggerActiveFlags; } } } - private const uint UninitializedFlag = 0x80000000; - - private static bool IsInitialized(Flags flags) => !IsUninitialized(flags); - private static bool IsUninitialized(Flags flags) => (flags & (Flags)UninitializedFlag) != 0; - private static Flags InitializeFlags() { _ = TplEventSource.Log; // Touch TplEventSource to trigger static constructor which will initialize TPL flags if EventSource is supported. @@ -108,26 +115,23 @@ private static Flags InitializeFlags() { if (IsUninitialized(s_activeFlags)) { - s_activeFlags = s_asyncProfilerActiveFlags | s_tplActiveFlags | s_debuggerActiveFlags; + s_activeFlags = s_asyncProfilerActiveFlags | s_asyncDebuggerActiveFlags; } return s_activeFlags; } } - private static Flags s_activeFlags = (Flags)UninitializedFlag; + private static bool IsInitialized(Flags flags) => !IsUninitialized(flags); - private static Flags s_asyncProfilerActiveFlags; + private static bool IsUninitialized(Flags flags) => (flags & Flags.Uninitialized) != 0; - private static Flags s_tplActiveFlags; + private static Flags s_activeFlags = Flags.Uninitialized; - private static Flags s_debuggerActiveFlags; + private static Flags s_asyncProfilerActiveFlags; - private static readonly object s_lock = new object(); + private static Flags s_asyncDebuggerActiveFlags; - private const Flags DebuggerFlags = - Flags.CreateAsyncContext | Flags.SuspendAsyncContext | - Flags.CompleteAsyncContext | Flags.UnwindAsyncException | - Flags.ResumeAsyncMethod | Flags.CompleteAsyncMethod; + private static readonly Lock s_lock = new(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs index 970aa7b716304a..89e537c74e92eb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TplEventSource.cs @@ -35,20 +35,25 @@ protected override void OnEventCommand(EventCommandEventArgs command) Debug = IsEnabled(EventLevel.Informational, Keywords.Debug); DebugActivityId = IsEnabled(EventLevel.Informational, Keywords.DebugActivityId); - AsyncInstrumentation.Flags tplFlags = AsyncInstrumentation.Flags.Disabled; - - tplFlags |= IsEnabled(EventLevel.Informational, Keywords.AsyncCausalitySynchronousWork) ? - AsyncInstrumentation.Flags.ResumeAsyncContext | - AsyncInstrumentation.Flags.SuspendAsyncContext | - AsyncInstrumentation.Flags.CompleteAsyncContext | - AsyncInstrumentation.Flags.UnwindAsyncException : AsyncInstrumentation.Flags.Disabled; - - tplFlags |= IsEnabled(EventLevel.Informational, Keywords.AsyncCausalityOperation) ? - AsyncInstrumentation.Flags.CreateAsyncContext | - AsyncInstrumentation.Flags.CompleteAsyncContext | - AsyncInstrumentation.Flags.UnwindAsyncException : AsyncInstrumentation.Flags.Disabled; + // Until debugger explicitly set the AsyncInstrumentation keyword, we enable async instrumentation when + // Tasks, AsyncCausalitySynchronousWork, AsyncCausalityOperation and TasksFlowActivityIds keywords are enabled. + bool asyncInstrumentationEnabled = IsEnabled(EventLevel.Informational, Keywords.AsyncInstrumentation); + if (!asyncInstrumentationEnabled) + { + asyncInstrumentationEnabled = IsEnabled(EventLevel.Informational, Keywords.Tasks); + asyncInstrumentationEnabled &= IsEnabled(EventLevel.Informational, Keywords.AsyncCausalityOperation); + asyncInstrumentationEnabled &= IsEnabled(EventLevel.Informational, Keywords.AsyncCausalitySynchronousWork); + asyncInstrumentationEnabled &= IsEnabled(EventLevel.Informational, Keywords.TasksFlowActivityIds); + } - AsyncInstrumentation.UpdateTplFlags(tplFlags); + if (asyncInstrumentationEnabled) + { + AsyncInstrumentation.UpdateAsyncDebuggerFlags(AsyncInstrumentation.DefaultFlags); + } + else + { + AsyncInstrumentation.UpdateAsyncDebuggerFlags(AsyncInstrumentation.Flags.Disabled); + } } /// @@ -143,6 +148,11 @@ public enum TaskWaitBehavior : int /// Relatively Verbose logging meant for debugging the Task library itself. Will probably be removed in the future /// public const EventKeywords DebugActivityId = (EventKeywords)0x40000; + /// + /// Enable async instrumentation to track async operations across await/async method boundaries. + /// Mainly used by debugger to track task and continuation chain execution. + /// + public const EventKeywords AsyncInstrumentation = (EventKeywords)0x80000; } //----------------------------------------------------------------------------------- diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs index ab5f169a62fa0d..7e28cbbfd1e1d9 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/RuntimeAsyncTests.cs @@ -24,8 +24,8 @@ public class RuntimeAsyncTests private static readonly object s_debuggerLock = new object(); private static TestEventListener? s_debuggerTplInstance; - // Tpl(0x20000) | Debugger(0x40000) | all event flags(0x7F) - private const uint EnabledInstrumentationFlags = 0x6007F; + // AsyncDebugger(0x2000000) | all event flags(0x7F) + private const uint EnabledInstrumentationFlags = 0x200007F; private const uint DisabledInstrumentationFlags = 0x0; private const uint UninitializedInstrumentationFlags = 0x80000000; @@ -514,6 +514,8 @@ public void RuntimeAsync_TplEvents() const int TraceSynchronousWorkBeginId = 17; const int TraceSynchronousWorkEndId = 18; + AttachDebugger(); + var events = new ConcurrentQueue(); using (var listener = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose)) { @@ -530,6 +532,44 @@ public void RuntimeAsync_TplEvents() Assert.Contains(events, e => e.EventId == TraceOperationEndId); Assert.Contains(events, e => e.EventId == TraceSynchronousWorkBeginId); Assert.Contains(events, e => e.EventId == TraceSynchronousWorkEndId); + + DetachDebugger(); + + }).Dispose(); + } + + [ConditionalFact(typeof(RuntimeAsyncTests), nameof(IsRemoteExecutorAndRuntimeAsyncSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124072", typeof(PlatformDetection), nameof(PlatformDetection.IsInterpreter))] + public void RuntimeAsync_NoTplEventsWithoutDebugger() + { + RemoteExecutor.Invoke(() => + { + const int TraceOperationBeginId = 14; + const string RuntimeAsyncTaskOperationName = "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask"; + + // Enable TplEventSource WITHOUT setting s_asyncDebuggingEnabled. + // The AsyncDebugger guard should prevent the V2 async instrumentation + // from emitting any TPL causality events. + var events = new ConcurrentQueue(); + using (var listener = new TestEventListener("System.Threading.Tasks.TplEventSource", EventLevel.Verbose)) + { + listener.RunWithCallback(events.Enqueue, () => + { + for (int i = 0; i < 10; i++) + { + Func().GetAwaiter().GetResult(); + } + }); + } + + // TraceOperationBegin with the RuntimeAsyncTask operation name is uniquely + // emitted by V2 async instrumentation. It must not appear without a debugger. + Assert.DoesNotContain(events, e => + e.EventId == TraceOperationBeginId && + e.Payload?.Count > 1 && + e.Payload[1] is string name && + name == RuntimeAsyncTaskOperationName); + }).Dispose(); } } From fc60bbc32a36e95027c1a1bb32cdaf0c9d8115d9 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 9 Apr 2026 16:34:48 +0200 Subject: [PATCH 21/23] Prevent exceptions to break instrumented code. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 222 ++++++++++++++---- 1 file changed, 171 insertions(+), 51 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 36372df37e3116..f002047034493a 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 @@ -456,42 +456,23 @@ internal bool HandleSuspended() return false; } - internal bool InstrumentedHandleSuspended(AsyncInstrumentation.Flags flags, Continuation? newContinuation = null) + internal void InstrumentedHandleSuspended(AsyncInstrumentation.Flags flags, Continuation? newContinuation = null) { if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - Continuation? nc = t_runtimeAsyncAwaitState.SentinelContinuation!.Next; + Continuation? nextContinuation = t_runtimeAsyncAwaitState.SentinelContinuation!.Next; - if (nc != null) - { - if (newContinuation != null) - { - TryAddRuntimeAsyncContinuationChainTimestamps(nc, newContinuation); - } - else - { - TryAddRuntimeAsyncContinuationChainTimestamps(nc); - } - } + AsyncDebugger.HandleSuspended(nextContinuation, newContinuation); if (!HandleSuspended()) { - if (nc != null) - { - RemoveRuntimeAsyncTask(this, nc); - } - else - { - RemoveRuntimeAsyncTask(this); - } - - return false; + AsyncDebugger.HandleSuspendedFailed(this, nextContinuation); } - return true; + return; } - return HandleSuspended(); + HandleSuspended(); } #pragma warning disable CA1822 // Mark members as static @@ -817,8 +798,7 @@ private static void InstrumentedFinalizeRuntimeAsyncTask(RuntimeAsyncTask if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { task.NotifyDebuggerOfRuntimeAsyncState(); - Task.AddToActiveTasks(task); - TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); + AsyncDebugger.CreateAsyncContext(task); } } @@ -1017,6 +997,8 @@ internal static void CompletedTask(Task task) TaskAwaiter.ValidateEnd(task); } + // Instrumentation helpers called from InstrumentedDispatchContinuations. + // These methods must not throw — exceptions would break the dispatch loop. internal static class RuntimeAsyncInstrumentationHelpers { public static bool InstrumentCheckPoint @@ -1034,7 +1016,7 @@ public static void ResumeRuntimeAsyncContext(Task task, ref AsyncDispatcherInfo { if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - TplEventSource.Log.TraceSynchronousWorkBegin(task.Id, CausalitySynchronousWork.Execution); + AsyncDebugger.ResumeAsyncContext(task.Id); } } } @@ -1046,12 +1028,7 @@ public static void SuspendRuntimeAsyncContext(ref AsyncDispatcherInfo info, Asyn { if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - if (info.NextContinuation != null) - { - Task.TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); - } - - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + AsyncDebugger.SuspendAsyncContext(ref info, curContinuation); } } } @@ -1063,8 +1040,7 @@ public static void SuspendRuntimeAsyncContext(AsyncInstrumentation.Flags flags, { if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - Task.ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + AsyncDebugger.SuspendAsyncContext(curContinuation, newContinuation); } } } @@ -1076,18 +1052,18 @@ public static void CompleteRuntimeAsyncContext(ref AsyncDispatcherInfo info, Asy { if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - CompleteRuntimeAsyncContext(info.CurrentTask); + AsyncDebugger.CompleteAsyncContext(info.CurrentTask); } } } - public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint unwindedFrames) + public static void UnwindRuntimeAsyncMethodUnhandledException(ref AsyncDispatcherInfo info, AsyncInstrumentation.Flags flags, Exception ex, Continuation curContinuation, uint _) { if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags)) { if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - UnwindRuntimeAsyncMethodUnhandledException(info.CurrentTask, ex, curContinuation, unwindedFrames); + AsyncDebugger.AsyncMethodUnhandledException(info.CurrentTask, ex, curContinuation); } } } @@ -1098,7 +1074,7 @@ public static void UnwindRuntimeAsyncMethodHandledException(AsyncInstrumentation { if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - Task.RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); + AsyncDebugger.AsyncMethodHandledException(curContinuation, unwindedFrames); } } } @@ -1108,9 +1084,9 @@ public static void ResumeRuntimeAsyncMethod(ref AsyncDispatcherInfo info, AsyncI { if (AsyncInstrumentation.IsEnabled.ResumeAsyncMethod(flags)) { - if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags) && info.CurrentTask != null) + if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - Task.UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); + AsyncDebugger.ResumeAsyncMethod(ref info, curContinuation); } } } @@ -1122,29 +1098,173 @@ public static void CompleteRuntimeAsyncMethod(AsyncInstrumentation.Flags flags, { if (AsyncInstrumentation.IsEnabled.AsyncDebugger(flags)) { - Task.RemoveRuntimeAsyncContinuationTimestamp(curContinuation); + AsyncDebugger.CompleteAsyncMethod(curContinuation); } } } + } - private static void CompleteRuntimeAsyncContext(Task? task) + // All methods in this class guard against exceptions to protect the instrumented async execution. + // Instrumentation failures are silently swallowed — correctness of async execution takes priority over debugger diagnostics. + internal static class AsyncDebugger + { + public static void CreateAsyncContext(Task task) { - if (task != null) + try + { + Task.AddToActiveTasks(task); + TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); + } + catch { - Task.RemoveRuntimeAsyncTask(task); - TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); + } + + } + + public static void ResumeAsyncContext(int id) + { + try + { + TplEventSource.Log.TraceSynchronousWorkBegin(id, CausalitySynchronousWork.Execution); + } + catch + { + } + } + + public static void SuspendAsyncContext(ref AsyncDispatcherInfo info, Continuation curContinuation) + { + try + { + if (info.NextContinuation != null) + { + Task.TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); + } + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } + catch + { + } } - private static void UnwindRuntimeAsyncMethodUnhandledException(Task? task, Exception ex, Continuation curContinuation, uint _) + public static void SuspendAsyncContext(Continuation curContinuation, Continuation newContinuation) { - if (task != null) + try { - Task.RemoveRuntimeAsyncTask(task, curContinuation); - TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); + Task.ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } + catch + { + } + } + + public static void CompleteAsyncContext(Task? task) + { + try + { + if (task != null) + { + Task.RemoveRuntimeAsyncTask(task); + TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + catch + { + } + } + + public static void AsyncMethodUnhandledException(Task? task, Exception ex, Continuation curContinuation) + { + try + { + if (task != null) + { + Task.RemoveRuntimeAsyncTask(task, curContinuation); + TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); + } + } + catch + { + } + } + + public static void AsyncMethodHandledException(Continuation curContinuation, uint unwindedFrames) + { + try + { + Task.RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); + } + catch + { + } + } + + public static void ResumeAsyncMethod(ref AsyncDispatcherInfo info, Continuation curContinuation) + { + try + { + if (info.CurrentTask != null) + { + Task.UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); + } + } + catch + { + } + } + + public static void CompleteAsyncMethod(Continuation curContinuation) + { + try + { + Task.RemoveRuntimeAsyncContinuationTimestamp(curContinuation); + } + catch + { + } + } + + public static void HandleSuspended(Continuation? nextContinuation, Continuation? newContinuation) + { + try + { + if (nextContinuation != null) + { + if (newContinuation != null) + { + Task.TryAddRuntimeAsyncContinuationChainTimestamps(nextContinuation, newContinuation); + } + else + { + Task.TryAddRuntimeAsyncContinuationChainTimestamps(nextContinuation); + } + } + } + catch + { + } + } + + public static void HandleSuspendedFailed(Task task, Continuation? nextContinuation) + { + try + { + if (nextContinuation != null) + { + Task.RemoveRuntimeAsyncTask(task, nextContinuation); + } + else + { + Task.RemoveRuntimeAsyncTask(task); + } + } + catch + { + } } } } From e7eff4ae4653bf269df02ef8020f7dece2c1fe67 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 9 Apr 2026 16:43:03 +0200 Subject: [PATCH 22/23] Fix weired character in comments. --- .../System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 4 ++-- 1 file changed, 2 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 f002047034493a..4f99aa901c865e 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 @@ -998,7 +998,7 @@ internal static void CompletedTask(Task task) } // Instrumentation helpers called from InstrumentedDispatchContinuations. - // These methods must not throw — exceptions would break the dispatch loop. + // These methods must not throw - exceptions would break the dispatch loop. internal static class RuntimeAsyncInstrumentationHelpers { public static bool InstrumentCheckPoint @@ -1105,7 +1105,7 @@ public static void CompleteRuntimeAsyncMethod(AsyncInstrumentation.Flags flags, } // All methods in this class guard against exceptions to protect the instrumented async execution. - // Instrumentation failures are silently swallowed — correctness of async execution takes priority over debugger diagnostics. + // Instrumentation failures are silently swallowed - correctness of async execution takes priority over debugger diagnostics. internal static class AsyncDebugger { public static void CreateAsyncContext(Task task) From 75f5f5e8ca355e8b39d6374bb6d08c7c718b0616 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 9 Apr 2026 17:02:55 +0200 Subject: [PATCH 23/23] Review feedback. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 135 +++++------------- .../CompilerServices/AsyncInstrumentation.cs | 10 +- 2 files changed, 34 insertions(+), 111 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 4f99aa901c865e..2230aa1e8b008f 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 @@ -998,7 +998,7 @@ internal static void CompletedTask(Task task) } // Instrumentation helpers called from InstrumentedDispatchContinuations. - // These methods must not throw - exceptions would break the dispatch loop. + // These methods should not throw - exceptions would break the dispatch loop. internal static class RuntimeAsyncInstrumentationHelpers { public static bool InstrumentCheckPoint @@ -1104,166 +1104,97 @@ public static void CompleteRuntimeAsyncMethod(AsyncInstrumentation.Flags flags, } } - // All methods in this class guard against exceptions to protect the instrumented async execution. - // Instrumentation failures are silently swallowed - correctness of async execution takes priority over debugger diagnostics. internal static class AsyncDebugger { public static void CreateAsyncContext(Task task) { - try - { - Task.AddToActiveTasks(task); - TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); - } - catch - { - } - + Task.AddToActiveTasks(task); + TplEventSource.Log.TraceOperationBegin(task.Id, "System.Runtime.CompilerServices.AsyncHelpers+RuntimeAsyncTask", 0); } public static void ResumeAsyncContext(int id) { - try - { - TplEventSource.Log.TraceSynchronousWorkBegin(id, CausalitySynchronousWork.Execution); - } - catch - { - } + TplEventSource.Log.TraceSynchronousWorkBegin(id, CausalitySynchronousWork.Execution); } public static void SuspendAsyncContext(ref AsyncDispatcherInfo info, Continuation curContinuation) { - try - { - if (info.NextContinuation != null) - { - Task.TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); - } - - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } - catch + if (info.NextContinuation != null) { + Task.TryAddRuntimeAsyncContinuationChainTimestamps(info.NextContinuation, curContinuation); } + + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } public static void SuspendAsyncContext(Continuation curContinuation, Continuation newContinuation) { - try - { - Task.ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } - catch - { - } + Task.ReplaceOrAddRuntimeAsyncContinuationTimestamp(curContinuation, newContinuation); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } public static void CompleteAsyncContext(Task? task) { - try - { - if (task != null) - { - Task.RemoveRuntimeAsyncTask(task); - TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } - } - catch + if (task != null) { + Task.RemoveRuntimeAsyncTask(task); + TplEventSource.Log.TraceOperationEnd(task.Id, AsyncCausalityStatus.Completed); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } } public static void AsyncMethodUnhandledException(Task? task, Exception ex, Continuation curContinuation) { - try - { - if (task != null) - { - Task.RemoveRuntimeAsyncTask(task, curContinuation); - TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); - TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); - } - } - catch + if (task != null) { + Task.RemoveRuntimeAsyncTask(task, curContinuation); + TplEventSource.Log.TraceOperationEnd(task.Id, ex is OperationCanceledException ? AsyncCausalityStatus.Canceled : AsyncCausalityStatus.Error); + TplEventSource.Log.TraceSynchronousWorkEnd(CausalitySynchronousWork.Execution); } } public static void AsyncMethodHandledException(Continuation curContinuation, uint unwindedFrames) { - try - { - Task.RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); - } - catch - { - } + Task.RemoveRuntimeAsyncContinuationChainTimestamps(curContinuation, unwindedFrames); } public static void ResumeAsyncMethod(ref AsyncDispatcherInfo info, Continuation curContinuation) { - try - { - if (info.CurrentTask != null) - { - Task.UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); - } - } - catch + if (info.CurrentTask != null) { + Task.UpdateRuntimeAsyncTaskTimestamp(info.CurrentTask, curContinuation); } } public static void CompleteAsyncMethod(Continuation curContinuation) { - try - { - Task.RemoveRuntimeAsyncContinuationTimestamp(curContinuation); - } - catch - { - } + Task.RemoveRuntimeAsyncContinuationTimestamp(curContinuation); } public static void HandleSuspended(Continuation? nextContinuation, Continuation? newContinuation) { - try + if (nextContinuation != null) { - if (nextContinuation != null) + if (newContinuation != null) { - if (newContinuation != null) - { - Task.TryAddRuntimeAsyncContinuationChainTimestamps(nextContinuation, newContinuation); - } - else - { - Task.TryAddRuntimeAsyncContinuationChainTimestamps(nextContinuation); - } + Task.TryAddRuntimeAsyncContinuationChainTimestamps(nextContinuation, newContinuation); + } + else + { + Task.TryAddRuntimeAsyncContinuationChainTimestamps(nextContinuation); } - } - catch - { } } public static void HandleSuspendedFailed(Task task, Continuation? nextContinuation) { - try + if (nextContinuation != null) { - if (nextContinuation != null) - { - Task.RemoveRuntimeAsyncTask(task, nextContinuation); - } - else - { - Task.RemoveRuntimeAsyncTask(task); - } + Task.RemoveRuntimeAsyncTask(task, nextContinuation); } - catch + else { + Task.RemoveRuntimeAsyncTask(task); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs index 72934077767e48..e04495632eb28b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncInstrumentation.cs @@ -52,15 +52,7 @@ public static class IsEnabled public static bool AsyncDebugger(Flags flags) => (Flags.AsyncDebugger & flags) != 0 && Task.s_asyncDebuggingEnabled; } - public static Flags ActiveFlags - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - Debug.Assert(IsInitialized(s_activeFlags), "ActiveFlags accessed before initialized. Make sure SyncActiveFlags gets called first."); - return s_activeFlags; - } - } + public static Flags ActiveFlags => s_activeFlags; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Flags SyncActiveFlags()