From 12bab6e3c8c9f505e2463c659679e6d46b21cb52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 12:55:31 +0000 Subject: [PATCH 1/2] Initial plan From ae812240923e07fe3905d2085c8f7adbd320722c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 13:23:16 +0000 Subject: [PATCH 2/2] Fix double monitor release in synchronized runtime-async methods Co-authored-by: jakobbotsch <7887810+jakobbotsch@users.noreply.github.com> --- src/coreclr/jit/importer.cpp | 3 ++ .../regression/synchronized-async-version.cs | 43 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index b9d41196859865..796043ac61dab8 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -11999,6 +11999,9 @@ bool Compiler::impWrapTopOfStackInAwait() info.compIsStatic ? fgGetCritSectOfStaticMethod() : gtNewLclvNode(info.compThisArg, TYP_REF); GenTree* exitMon = gtNewHelperCallNode(CORINFO_HELP_MON_EXIT, TYP_VOID, lockObject, varAddrNode); impAppendTree(exitMon, CHECK_SPILL_ALL, impCurStmtDI); + + // The fault handler must not release the monitor again if the await throws. + impStoreToTemp(lvaMonAcquired, gtNewZeroConNode(TYP_I_IMPL), CHECK_SPILL_ALL); } if (impFoldAwaitedTopOfStack()) diff --git a/src/tests/async/regression/synchronized-async-version.cs b/src/tests/async/regression/synchronized-async-version.cs index b032dd17eaef89..a7f239c994c741 100644 --- a/src/tests/async/regression/synchronized-async-version.cs +++ b/src/tests/async/regression/synchronized-async-version.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -8,6 +9,48 @@ public class Async2Synchronized { + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public static void FaultedAwaitable(bool useValueTask, bool callerHoldsLock) + { + Async2Synchronized p = new(); + InvalidOperationException expected = new("boom"); + Task task = Task.FromException(expected); + + if (callerHoldsLock) + { + Monitor.Enter(p); + } + + try + { + InvalidOperationException actual = Assert.Throws(() => + { + if (useValueTask) + { + p.FooValueTask(new ValueTask(task)).GetAwaiter().GetResult(); + } + else + { + p.Foo(task).GetAwaiter().GetResult(); + } + }); + + Assert.Same(expected, actual); + Assert.Equal(callerHoldsLock, Monitor.IsEntered(p)); + } + finally + { + if (Monitor.IsEntered(p)) + { + Monitor.Exit(p); + } + } + } + [Fact] public static void TestEntryPoint() {