Skip to content

Clarify that runtime-async suspension points are forbidden in handlers, not try blocks - #132426

Merged
jakobbotsch merged 1 commit into
dotnet:mainfrom
pcshrosbree:clarify-runtime-async-suspension-in-handlers
Aug 17, 2026
Merged

jakobbotsch merged 1 commit into
dotnet:mainfrom
pcshrosbree:clarify-runtime-async-suspension-in-handlers

Conversation

@pcshrosbree

Copy link
Copy Markdown
Contributor

docs/design/specs/runtime-async.md lists, under restrictions likely to be permanent:

  • Suspension points may not appear in exception handling blocks.

The intended meaning is clear enough on a careful reading — "exception handling block" is the handler block — but it is easy to read as the whole try/catch construct, including the protected try block. Under that reading await inside a try would be forbidden, which would rule out a shape the runtime's own tests exercise. Naming the handler explicitly removes the ambiguity.

ECMA-335 defines four handler kinds, so the enumeration names all four: catch, filter, finally and fault. C# never emits fault, but IL from other producers does, and this document is an ECMA-335 augment rather than a C# specification.

Evidence from this repository

src/tests/async/simple-eh/simple-eh.cs awaits inside a try with a catch, in a method that is not opted out of runtime-async generation:

public static async Task<int> Handler()
{
    try
    {
        return await Throw(42);
    }
    catch (IntegerException ex)
    {
        return ex.Value;
    }
}

The methods in that file which must not be runtime-async are explicitly marked [RuntimeAsyncMethodGeneration(false)]; Handler is not among them.

src/tests/async/eh-microbench/eh-microbench.cs goes further, deliberately injecting a try/finally around awaits at a configurable rate — "Inject a finally every N frames" — as a runtime-async benchmark arm.

Evidence from a shipping SDK

Built against 11.0.100-preview.5.26302.115 with <Features>runtime-async=on</Features>, reading the emitted metadata and IL back:

method ImplAttributes where the AsyncHelpers suspension point sits
await in a try with a catch 0x2000 (Async) inside the try region — call @0x0018, try [0x0000,0x0028)
await in a try with a finally 0x2000 (Async) inside the try region — call @0x001A, try [0x0002,0x002A)
await written inside a catch handler 0x2000 (Async) outside all EH — the await is lowered out of the handler; the EH region collapses to try [0x0002,0x000B) / handler [0x000B,0x0010)
await written inside a finally handler 0x2000 (Async) outside all EH — likewise

All four compile and run correctly.

The third and fourth rows may be worth a sentence in the spec as well, since they are the natural follow-up question and are not stated anywhere: when a source-level await appears in a handler, the compiler lowers it out of the handler rather than abandoning runtime-async codegen for the method — the method remains MethodImplOptions.Async. I have left that out of this change to keep it to the one ambiguity, but I am happy to add it if you would like.

Why it is worth changing

The sentence is load-bearing for anyone writing an IL rewriter, profiler, or analyzer against this spec, and the two readings differ on whether a whole class of instrumentation is viable at all. A more careful reading resolves it; this wording means no one has to arrive at that reading on their own.

"Exception handling blocks" reads naturally as the whole try/catch
construct. The implementation means the handler: the runtime's own tests
await inside try blocks under runtime-async. Name the four handler kinds
ECMA-335 defines, and state that the protected block is permitted.
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Aug 17, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

@jakobbotsch
jakobbotsch merged commit 68069b7 into dotnet:main Aug 17, 2026
32 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Aug 18, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-VM-coreclr community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants