Skip to content

Fix double monitor release in synchronized runtime-async methods - #133866

Merged
jakobbotsch merged 2 commits into
mainfrom
copilot/bugfix-runtime-async-synchronized-method
Sep 15, 2026
Merged

jakobbotsch merged 2 commits into
mainfrom
copilot/bugfix-runtime-async-synchronized-method

Conversation

Copilot AI commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Awaiting a faulted awaitable returned by a synchronized method can release the monitor twice, replacing the original exception or releasing a caller-owned recursive acquisition.

  • JIT: Clear lvaMonAcquired immediately after the importer-generated monitor exit, preventing a subsequent await exception from triggering another release. The released-state value is 0; 1 means acquired.
  • Regression coverage: Extend the existing Task and ValueTask tests to verify exception identity and monitor ownership, with and without a caller-held lock.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
16 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: @akoeplinger, @matouskozak, @simonrozsival
See info in area-owners.md if you want to be subscribed.

Co-authored-by: jakobbotsch <7887810+jakobbotsch@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix runtime-async version of synchronized method releasing monitor twice Fix double monitor release in synchronized runtime-async methods Sep 14, 2026
Copilot AI requested a review from jakobbotsch September 14, 2026 13:24
@jakobbotsch
jakobbotsch marked this pull request as ready for review September 14, 2026 13:27
Copilot AI lite review requested due to automatic review settings September 14, 2026 13:27
@azure-pipelines

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

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.

🔵 Needs a closer look

The JIT monitor-release change affects exception handling and lock ownership, warranting final human review.

Pull request overview

Fixes double monitor release in synchronized runtime-async methods and adds regression coverage.

Changes:

  • Clears lvaMonAcquired after monitor release.
  • Tests Task and ValueTask exception identity and lock ownership.
File summaries
File Description
src/tests/async/regression/synchronized-async-version.cs Adds synchronized async exception and ownership tests.
src/coreclr/jit/importer.cpp Prevents duplicate monitor release during fault handling.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

@jakobbotsch

Copy link
Copy Markdown
Member

cc @dotnet/jit-contrib PTAL @EgorBo

I will backport this. To my surprise we actually have synchronized task-returning methods in the BCL that may throw (due to cancellation):

[MethodImpl(MethodImplOptions.Synchronized)]
public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
Write(value);
return Task.CompletedTask;
}

So this problem is reproducible with:

using StreamWriter sw = new StreamWriter(new MemoryStream());
var sb = new StringBuilder();
await TextWriter.Synchronized(sw).WriteLineAsync(sb, new CancellationToken(canceled: true));

in .NET 11 with runtime async enabled.

@jakobbotsch
jakobbotsch requested a review from EgorBo September 14, 2026 19:38
@jkoritzinsky jkoritzinsky added area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI runtime-async and removed area-Infrastructure-mono labels Sep 15, 2026
@jkoritzinsky
jkoritzinsky deployed to copilot-pat-pool September 15, 2026 02:42 — with GitHub Actions Active
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@jkoritzinsky
jkoritzinsky deployed to copilot-pat-pool September 15, 2026 02:43 — with GitHub Actions Active
@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Failure is the one fixed by #133775

@jakobbotsch
jakobbotsch merged commit 6af607b into main Sep 15, 2026
158 of 163 checks passed
@jakobbotsch
jakobbotsch deleted the copilot/bugfix-runtime-async-synchronized-method branch September 15, 2026 08:37
@jakobbotsch

Copy link
Copy Markdown
Member

/backport to release/11.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0 (link to workflow run)

JulieLeeMSFT pushed a commit that referenced this pull request Sep 15, 2026
…nc methods (#133935)

Backport of #133866 to release/11.0

/cc @jakobbotsch @Copilot

## Customer Impact

- [ ] Customer reported
- [X] Found internally

Awaiting a synchronized task-returning method that throws from a runtime
async method results in double releasing the synchronized lock, throwing
the wrong exception to the consumer.
For example, in .NET 11, the following code throws
`System.Threading.SynchronizationLockException` instead of
`System.Threading.Tasks.TaskCanceledException` if compiled with runtime
async:
```csharp
using StreamWriter sw = new StreamWriter(new MemoryStream());
var sb = new StringBuilder();
await TextWriter.Synchronized(sw).WriteLineAsync(sb, new CancellationToken(canceled: true));
```

## Regression

- [X] Yes
- [ ] No

Introduced by async version work in #129901

## Testing

Unit test added.

## Risk

Low.

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jakobbotsch <7887810+jakobbotsch@users.noreply.github.com>
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Sep 16, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Sep 18, 2026
…dotnet#133866)

Awaiting a faulted awaitable returned by a synchronized method can
release the monitor twice, replacing the original exception or releasing
a caller-owned recursive acquisition.

- **JIT:** Clear `lvaMonAcquired` immediately after the
importer-generated monitor exit, preventing a subsequent await exception
from triggering another release. The released-state value is `0`; `1`
means acquired.
- **Regression coverage:** Extend the existing `Task` and `ValueTask`
tests to verify exception identity and monitor ownership, with and
without a caller-held lock.

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jakobbotsch <7887810+jakobbotsch@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI runtime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: (bug) Runtime-async version of a Synchronized method releases the monitor twice, replacing the real exception and corrupting the caller's lock

5 participants