Skip to content

Async Profiler: Reuse V1 dispatcher across a method's suspensions.#130299

Open
lateralusX wants to merge 2 commits into
dotnet:mainfrom
lateralusX:lateralusX/async-profiler-reuse-v1-dispatcher-on-suspend
Open

Async Profiler: Reuse V1 dispatcher across a method's suspensions.#130299
lateralusX wants to merge 2 commits into
dotnet:mainfrom
lateralusX:lateralusX/async-profiler-reuse-v1-dispatcher-on-suspend

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Summary

Optimizes the V1 (StateMachine) async profiler so a single AsyncStateMachineDispatcher spans all of a method's suspensions, instead of allocating a fresh dispatcher on every await/yield.

Motivation

In the previous model, each time a box's continuation was scheduled (i.e. each yield), CreateDispatcher allocated a new dispatcher wrapping the same box. A method that suspends N times produced N dispatchers and N Create events within the same tree. Since the box identity is stable across its suspensions, that per-yield allocation is avoidable: when the active dispatcher is already dispatching this exact box, we can reuse it. This matches asyncv2's behavior.

What changed

AsyncStateMachineDispatcher / CreateDispatcher:

  • Reuse guard: if the active dispatcher's inner box is reference-equal to the box being scheduled, return the existing dispatcher instead of allocating a new one.
  • MoveNext now resets LastContinuation / ReachedLastContinuation on re-entry, since a reused dispatcher would otherwise carry stale callstack-walk state from its previous resume cycle.

Preserving the late-frame append on reuse (AsyncProfiler.CreateAsyncContext.Append):

  • Allocating a new dispatcher previously ran CreateAsyncContext.Create, whose append hook flushes callstack frames that registered after the last resume walk (e.g. a parent continuation that attached late). The reuse path skips Create, so it now performs just that append-flush (no Create event emitted), gated on IsEnabled.ResumeAsyncContext(flags). This keeps AppendStateMachineAsyncCallstack firing before the subsequent Suspend, exactly as before.

Tests

Updated the shared balance assertions to the reuse invariant:

  • AssertCreateBalancesSuspendAndCompleteInChain — now checks creates == completes and resumes == suspends + completes.
  • StateMachineAsync_WaitThenYield_BalancesResumeAndComplete — same split (createCount == completeCount, plus the model-agnostic resumeCount == completeCount + suspendCount).
  • StateMachineAsync_PoolingValueTask_InlineReentrantCompletion — the mis-attribution assertion now verifies the reused

Outer dispatcher re-suspends before it completes (suspendIdx < completeIdx) rather than the old per-yield "re-suspending frame emits no Complete".

Copilot AI review requested due to automatic review settings July 7, 2026 12:53
@lateralusX lateralusX changed the title Async Profiler: Async Profiler: Reuse V1 dispatcher across a method's suspensions. Jul 7, 2026
@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.

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 changes async-profiler (asyncv1 / state-machine) dispatcher behavior so the same AsyncStateMachineDispatcher instance can be reused across multiple suspensions of the same boxed state machine, avoiding per-await dispatcher allocation and repeated Create events. It also preserves the “late-frame append” behavior on reuse and updates test invariants to match the new model.

Changes:

  • Reuse the currently-active AsyncStateMachineDispatcher when scheduling the same inner box, and reset per-resume callstack-walk state on re-entry.
  • Add an AsyncProfiler.CreateAsyncContext.Append helper to flush late-frame async callstack append on the reuse path.
  • Update async-profiler tests to assert the new create/complete vs. resume/suspend balancing invariants.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs Updates v1 tests’ balancing and ordering assertions for dispatcher reuse.
src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs Updates shared assertion helper to reflect reuse semantics (creates == completes; resumes == completes + suspends).
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDispatcher.cs Adds reuse guard in CreateDispatcher and resets LastContinuation/ReachedLastContinuation per MoveNext entry; exposes InnerBox for reuse comparison.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncProfiler.cs Adds CreateAsyncContext.Append to preserve late-frame append behavior when dispatcher allocation is skipped.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 13:14

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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +1696 to 1700
// StateMachine dispatcher model with reuse: a single dispatcher spans all of a method's yields
// (it resumes/suspends multiple times) and is created + completed exactly once. So within a
// dispatcher tree every Create is balanced by exactly one Complete; Suspends are interior events.
private static void AssertCreateBalancesSuspendAndCompleteInChain(ParsedEventStream stream, ulong dispatcherId, string chainName)
{
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.

3 participants