File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Runtime\CompilerServices\AsyncStateMachineDispatcher.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace System.Runtime.CompilerServices
{
    [StructLayout(LayoutKind.Explicit)]
    internal unsafe ref struct AsyncStateMachineDispatcherInfo
    {
        [FieldOffset(0)]
        public AsyncStateMachineDispatcherInfo* Next;

        /// <safety>Managed-reference field at an explicit offset that does not overlap any other field, so reading or writing it cannot alias a pointer or forge a reference.</safety>
#if TARGET_64BIT
        [FieldOffset(8)]
#else
        [FieldOffset(4)]
#endif
        public safe Task? Dispatcher;

        /// <safety>Value-type field at an explicit offset that does not overlap the Next pointer or Dispatcher reference, so accessing it cannot alias a pointer or forge a reference.</safety>
#if TARGET_64BIT
        [FieldOffset(16)]
#else
        [FieldOffset(8)]
#endif
        public safe AsyncProfiler.Info AsyncProfilerInfo;

        [ThreadStatic]
        internal static unsafe AsyncStateMachineDispatcherInfo* t_current;

        public static bool IsSupported
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
#if NATIVEAOT
            // Asyncv1 instrumentation is disabled on Native AOT until support for
            // async callstack IP and state has been implemented in ILC. Native AOT
            // currently have limited asyncv1 diagnostic support in tooling, we can
            // postpone the support until proven needed.
            get => false;
#else
            get => AsyncInstrumentation.IsAsyncProfilerSupported;
#endif
        }

        internal object? NextContinuationForDiagnostics
        {
            get
            {
                IAsyncStateMachineBox? last = AsyncProfilerInfo.LastContinuation;
                if (last is Task task)
                {
                    return task.ContinuationForDiagnostics;
                }

                return last is not null && last.GetDiagnosticData(out _, out _, out object? next) ? next : null;
            }
        }

        internal bool ContinuationChainChanged => NextContinuationForDiagnostics != null;

        internal static unsafe IAsyncStateMachineBox CreateDispatcher(IAsyncStateMachineBox box, AsyncInstrumentation.Flags flags)
        {
            if (!IsSupported)
            {
                return box;
            }

            IAsyncStateMachineDispatcher? dispatcherBox = box as IAsyncStateMachineDispatcher;

            if (dispatcherBox?.IsLeaf == true)
            {
                return box;
            }

            AsyncStateMachineDispatcherInfo* info = AsyncStateMachineDispatcherInfo.t_current;
            Task? activeDispatcher = info != null ? info->Dispatcher : null;

            if (activeDispatcher is AsyncStateMachineDispatcher reusedDispatcher)
            {
                if (ReferenceEquals(reusedDispatcher.InnerBox, box))
                {
                    if (AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags))
                    {
                        AsyncProfiler.CreateAsyncContext.Append(ref *info);
                    }

                    info->AsyncProfilerInfo.CurrentContinuationResumes = true;
                    return reusedDispatcher;
                }
            }
            else if (ReferenceEquals(activeDispatcher, box))
            {
                if (AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags))
                {
                    AsyncProfiler.CreateAsyncContext.Append(ref *info);
                }

                Debug.Assert(dispatcherBox != null);
                dispatcherBox!.IsLeaf = true;

                info->AsyncProfilerInfo.CurrentContinuationResumes = true;
                return box;
            }

            if (dispatcherBox is Task)
            {
                EmitCreateAsyncContext(info, dispatcherBox, flags);
                dispatcherBox.IsLeaf = true;
                return box;
            }

            AsyncStateMachineDispatcher dispatcher = new AsyncStateMachineDispatcher(box);
            EmitCreateAsyncContext(info, dispatcher, flags);
            return dispatcher;
        }

        private static unsafe void EmitCreateAsyncContext(AsyncStateMachineDispatcherInfo* info, IAsyncStateMachineDispatcher dispatcher, AsyncInstrumentation.Flags flags)
        {
            if (AsyncInstrumentation.IsEnabled.CreateAsyncContext(flags) || AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags))
            {
                if (info != null)
                {
                    AsyncProfiler.CreateAsyncContext.Create(ref *info, dispatcher);
                }
                else
                {
                    AsyncProfiler.CreateAsyncContext.Create(dispatcher);
                }
            }
        }

        internal static unsafe void UnwindAsyncFrame(object completingBox, AsyncInstrumentation.Flags flags)
        {
            if (!IsSupported)
            {
                return;
            }

            AsyncStateMachineDispatcherInfo* info = t_current;
            if (info != null && ReferenceEquals(info->AsyncProfilerInfo.CurrentContinuation, completingBox))
            {
                info->AsyncProfilerInfo.CurrentContinuationCompleted = true;

                if (AsyncInstrumentation.IsEnabled.UnwindAsyncException(flags))
                {
                    AsyncProfiler.AsyncMethodException.UnwindFrames(ref *info, 1);
                }
            }
        }

        internal static unsafe void ResumeAsyncMethod(IAsyncStateMachineBox box, AsyncInstrumentation.Flags flags)
        {
            if (!IsSupported)
            {
                return;
            }

            AsyncStateMachineDispatcherInfo* info = t_current;
            Task? activeDispatcher = info != null ? info->Dispatcher : null;
            if (activeDispatcher == null)
            {
                return;
            }

            info->AsyncProfilerInfo.CurrentContinuation = box;
            info->AsyncProfilerInfo.CurrentContinuationCompleted = false;

            AsyncProfiler.SyncPoint.Check(ref info->AsyncProfilerInfo);

            bool methodEventEnabled = AsyncInstrumentation.IsEnabled.ResumeAsyncMethod(flags);
            bool callstackEnabled = AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags);
            if (!methodEventEnabled && !(callstackEnabled && info->AsyncProfilerInfo.LastContinuation != null))
            {
                return;
            }

            ResumeAsyncMethod(info, box, methodEventEnabled, callstackEnabled);
        }

        private static unsafe void ResumeAsyncMethod(AsyncStateMachineDispatcherInfo* info, IAsyncStateMachineBox box, bool methodEventEnabled, bool callstackEnabled)
        {
            bool callstackEventEnabled = callstackEnabled && info->AsyncProfilerInfo.ReachedLastContinuation;

            if (!info->AsyncProfilerInfo.ReachedLastContinuation && ReferenceEquals(info->AsyncProfilerInfo.LastContinuation, box))
            {
                info->AsyncProfilerInfo.ReachedLastContinuation = true;
            }

            if (methodEventEnabled || callstackEventEnabled)
            {
                AsyncProfiler.ResumeAsyncMethod.Resume(ref *info, box);
            }
        }

        internal static bool SuspendOrCompleteContext(ref AsyncStateMachineDispatcherInfo info, AsyncInstrumentation.Flags flags)
        {
            bool suspended = false;

            try
            {
                // A node ends this dispatch in a suspend only when the method has not completed and
                // it re-armed itself as a leaf (it will be resumed again under this same node).
                // Otherwise the node is done: the method completed, or leaf-ship was handed off to a
                // child context that took over the chain (this node won't be resumed again).
                suspended = !info.AsyncProfilerInfo.CurrentContinuationCompleted && info.AsyncProfilerInfo.CurrentContinuationResumes;
                if (suspended)
                {
                    if (AsyncInstrumentation.IsEnabled.SuspendAsyncContext(flags))
                    {
                        AsyncProfiler.SuspendAsyncContext.Suspend(ref info.AsyncProfilerInfo);
                    }
                }
                else
                {
                    if (AsyncInstrumentation.IsEnabled.CompleteAsyncContext(flags))
                    {
                        AsyncProfiler.CompleteAsyncContext.Complete(ref info);
                    }
                }
            }
            catch (Exception)
            {
                // Best-effort instrumentation: swallow so the dispatch frame is always popped.
            }

            return suspended;
        }

        internal static unsafe void CompleteAsyncMethod(object completingBox, AsyncInstrumentation.Flags flags)
        {
            if (!IsSupported)
            {
                return;
            }

            AsyncStateMachineDispatcherInfo* info = t_current;
            if (info != null && ReferenceEquals(info->AsyncProfilerInfo.CurrentContinuation, completingBox))
            {
                info->AsyncProfilerInfo.CurrentContinuationCompleted = true;

                if (AsyncInstrumentation.IsEnabled.CompleteAsyncMethod(flags))
                {
                    AsyncProfiler.CompleteAsyncMethod.Complete(ref *info);
                }
            }
        }
    }

    internal interface IAsyncStateMachineDispatcher
    {
        bool IsLeaf { get; set; }
        ulong DispatcherId { get; }
    }

    // Diagnostic tooling depends on this type name (together with the MoveNext method name) when classifying
    // async callstack frames.
    internal sealed class AsyncStateMachineDispatcher : Task<VoidTaskResult>, IAsyncStateMachineBox, IAsyncStateMachineDispatcher
    {
        private IAsyncStateMachineBox? _inner;

        internal IAsyncStateMachineBox? InnerBox => _inner;

        internal AsyncStateMachineDispatcher(IAsyncStateMachineBox inner) : base()
        {
            _inner = inner;
        }

        // The wrapper is always the leaf dispatcher for its inner box, so this is permanently true;
        bool IAsyncStateMachineDispatcher.IsLeaf
        {
            get => true;
            set { }
        }

        ulong IAsyncStateMachineDispatcher.DispatcherId => (ulong)Id;

        internal sealed override void ExecuteDirectly(Thread? threadPoolThread) => MoveNext();

        [StackTraceHidden]
        // Diagnostic tooling depends on this name when classifying async callstack frames.
        public unsafe void MoveNext()
        {
            IAsyncStateMachineBox? inner = _inner;
            if (inner is null)
            {
                return;
            }

            AsyncStateMachineDispatcherInfo info;
            ref AsyncStateMachineDispatcherInfo* refInfo = ref AsyncStateMachineDispatcherInfo.t_current;
            AsyncStateMachineDispatcherInfo* refPreviousInfo = refInfo;
            refInfo = &info;
            info.Next = refPreviousInfo;

            AsyncProfiler.InitInfo(ref info.AsyncProfilerInfo);

            info.Dispatcher = this;
            info.AsyncProfilerInfo.DispatcherId = (ulong)Id;
            info.AsyncProfilerInfo.CurrentContinuation = inner;

            AsyncInstrumentation.Flags flags = AsyncInstrumentation.LoadFlags();

            try
            {
                if (AsyncInstrumentation.IsEnabled.ResumeAsyncContext(flags))
                {
                    AsyncProfiler.ResumeAsyncContext.Resume(ref info);
                }

                inner.MoveNext();
            }
            finally
            {
                AsyncStateMachineDispatcherInfo.SuspendOrCompleteContext(ref info, flags);
                refInfo = info.Next;
            }
        }

        public Action MoveNextAction => (Action)(m_action ??= new Action(MoveNext));

        public IAsyncStateMachine GetStateMachineObject()
        {
            IAsyncStateMachineBox? inner = _inner;
            return inner != null ? inner.GetStateMachineObject() : null!;
        }

        public void ClearStateUponCompletion()
        {
            _inner?.ClearStateUponCompletion();
            _inner = null;
        }

        public bool GetDiagnosticData(out ulong methodId, out int state, out object? nextContinuation)
        {
            IAsyncStateMachineBox? inner = _inner;
            if (inner != null)
            {
                return inner.GetDiagnosticData(out methodId, out state, out nextContinuation);
            }

            methodId = 0;
            state = -1;
            nextContinuation = null;
            return false;
        }
    }
}