Skip to content

Async Profiler: Instrument custom awaiters in V1 dispatcher.#130297

Merged
lateralusX merged 2 commits into
dotnet:mainfrom
lateralusX:lateralusX/async-profiler-custom-awaiter-support
Jul 8, 2026
Merged

Async Profiler: Instrument custom awaiters in V1 dispatcher.#130297
lateralusX merged 2 commits into
dotnet:mainfrom
lateralusX:lateralusX/async-profiler-custom-awaiter-support

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Summary

Extends the async profiler (V1 / StateMachine) so that continuations awaiting a custom awaiter are wrapped in an AsyncStateMachineDispatcher just like the built-in ones, making them visible to instrumentation.

Motivation

The V1 dispatcher is created on the awaiter's box-aware path (IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted), which only the framework awaiters (Task/ValueTask/YieldAwaitable) implement. For an arbitrary user awaiter, the async method builders fall back to the generic AwaitOnCompleted<TAwaiter, TStateMachine> / AwaitUnsafeOnCompleted paths, which registered box.MoveNextAction without creating a dispatcher. As a result, continuations that resumed through a custom awaiter never produced Create/Resume/Complete async-profiler events — leaving a gap in the captured async causality chain.

What changed

  • AsyncTaskMethodBuilder and PoolingAsyncValueTaskMethodBuilder: in the generic AwaitOnCompleted and the
  • AsyncStateMachineDispatcher: removed the dedicated _moveNextAction field and now backs MoveNextAction with the inherited Task.m_action slot ((Action)(m_action ??= new Action(MoveNext))), matching how the framework builder boxes expose their action.

Tests

  • New [ConditionalTheory] StateMachineAsync_CustomAwaiter_EmitsCreateResumeComplete covering the full matrix of (pooling vs. non-pooling) × (ICriticalNotifyCompletion vs. INotifyCompletion) via four [InlineData] cases, each with a dedicated custom-awaiter marker method, asserting the expected Create/Resume/Complete events are emitted.

Notes / risk

  • Gated behind AsyncInstrumentation.IsActive + IsEnabled.AsyncProfiler, so there is no behavioral or allocation change when the profiler is disabled (the common case).
  • No change to NativeAOT behavior (V1 remains disabled there via AsyncStateMachineDispatcherInfo.IsSupported).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends async-profiler (V1 / state-machine) instrumentation so that custom awaiters (i.e., awaiters that don’t hit the Task/ValueTask/Yield “known awaiter” fast paths) still get their continuations wrapped in an AsyncStateMachineDispatcher, enabling Create/Resume/Complete events to be emitted for those awaits.

Changes:

  • Wrap the state machine box in an AsyncStateMachineDispatcher from the generic builder fallback paths (AwaitOnCompleted and the non-known awaiter branch of AwaitUnsafeOnCompleted) when async-profiler instrumentation is active.
  • Align AsyncStateMachineDispatcher.MoveNextAction with framework state-machine boxes by backing it with the inherited Task.m_action slot.
  • Add new tests covering custom awaiters across the (pooling vs non-pooling) × (INotifyCompletion vs ICriticalNotifyCompletion) matrix, asserting the expected Create → Resume → Complete chain.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs Adds coverage ensuring custom awaiters still produce V1 dispatcher events and callstack markers across pooling/non-pooling and notify/critical notify cases.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/PoolingAsyncValueTaskMethodBuilderT.cs Creates a dispatcher for the pooling builder’s AwaitOnCompleted fallback path when async-profiler instrumentation is enabled.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs Creates a dispatcher for AwaitOnCompleted and for the “unknown awaiter” branch of AwaitUnsafeOnCompleted under async-profiler instrumentation.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDispatcher.cs Changes MoveNextAction caching to use Task.m_action, matching the existing AsyncStateMachineBox<TStateMachine> pattern.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-runtime-compilerservices
See info in area-owners.md if you want to be subscribed.

@lateralusX

Copy link
Copy Markdown
Member Author

/ba-g known issue #130177

@lateralusX lateralusX merged commit 20a6bde into dotnet:main Jul 8, 2026
160 of 163 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants