Skip to content

Commit 3baf6d2

Browse files
authored
JIT: Move Thread sync and exec context save/restore to happen around runtime async bodies (#121448)
This fixes an issue where there was a behavioral difference between async1 and async2 for async to sync runtime async calls. Before this PR the JIT would always save and restore contexts around awaits of task-returning methods, regardless of whether they were implemented as runtime async functions or not. That does not match async1: in async1, the context save and restore happens around the body in each async method. The former logic was an optimization, but the optimization is only correct for runtime async to runtime async calls. We cannot in general know if a callee is implemented by runtime async or not, so we have to move the context save/restore to happen around the body of all runtime async methods. An example test program that saw the behavioral difference before is the following: ```csharp using System; using System.Threading; using System.Threading.Tasks; public class Program { static void Main() { Foo().GetAwaiter().GetResult(); } static async Task Foo() { SynchronizationContext.SetSynchronizationContext(new MySyncCtx(123)); Console.WriteLine(((MySyncCtx)SynchronizationContext.Current).Arg); await Bar(); Console.WriteLine(((MySyncCtx)SynchronizationContext.Current).Arg); } static Task Bar() { SynchronizationContext.SetSynchronizationContext(new MySyncCtx(124)); return Task.CompletedTask; } class MySyncCtx(int arg) : SynchronizationContext { public int Arg => arg; } } ``` Async1: 123 124 Runtime async before: 123 123 Runtime async after: 123 124
1 parent 4afc03f commit 3baf6d2

File tree

20 files changed

+552
-631
lines changed

20 files changed

+552
-631
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ public static void HandleSuspended<T, TOps>(T task) where T : Task, ITaskComplet
556556
sentinelContinuation.Next = null;
557557

558558
// Head continuation should be the result of async call to AwaitAwaiter or UnsafeAwaitAwaiter.
559-
// These never have special continuation handling.
559+
// These never have special continuation context handling.
560560
const ContinuationFlags continueFlags =
561561
ContinuationFlags.ContinueOnCapturedSynchronizationContext |
562562
ContinuationFlags.ContinueOnThreadPool |
@@ -774,36 +774,41 @@ private static void CaptureContexts(out ExecutionContext? execCtx, out Synchroni
774774
syncCtx = thread._synchronizationContext;
775775
}
776776

777+
// Restore contexts onto current Thread. If "resumed" then this is not the first starting call for the async method.
777778
[MethodImpl(MethodImplOptions.AggressiveInlining)]
778-
private static void RestoreContexts(bool suspended, ExecutionContext? previousExecCtx, SynchronizationContext? previousSyncCtx)
779+
private static void RestoreContexts(bool resumed, ExecutionContext? previousExecCtx, SynchronizationContext? previousSyncCtx)
779780
{
780-
Thread thread = Thread.CurrentThreadAssumedInitialized;
781-
if (!suspended && previousSyncCtx != thread._synchronizationContext)
781+
if (!resumed)
782782
{
783-
thread._synchronizationContext = previousSyncCtx;
784-
}
783+
Thread thread = Thread.CurrentThreadAssumedInitialized;
784+
if (previousSyncCtx != thread._synchronizationContext)
785+
{
786+
thread._synchronizationContext = previousSyncCtx;
787+
}
785788

786-
ExecutionContext? currentExecCtx = thread._executionContext;
787-
if (previousExecCtx != currentExecCtx)
788-
{
789-
ExecutionContext.RestoreChangedContextToThread(thread, previousExecCtx, currentExecCtx);
789+
ExecutionContext? currentExecCtx = thread._executionContext;
790+
if (previousExecCtx != currentExecCtx)
791+
{
792+
ExecutionContext.RestoreChangedContextToThread(thread, previousExecCtx, currentExecCtx);
793+
}
790794
}
791795
}
792796

793-
private static void CaptureContinuationContext(SynchronizationContext syncCtx, ref object context, ref ContinuationFlags flags)
797+
private static void CaptureContinuationContext(ref object continuationContext, ref ContinuationFlags flags)
794798
{
799+
SynchronizationContext? syncCtx = Thread.CurrentThreadAssumedInitialized._synchronizationContext;
795800
if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
796801
{
797802
flags |= ContinuationFlags.ContinueOnCapturedSynchronizationContext;
798-
context = syncCtx;
803+
continuationContext = syncCtx;
799804
return;
800805
}
801806

802807
TaskScheduler? sched = TaskScheduler.InternalCurrent;
803808
if (sched != null && sched != TaskScheduler.Default)
804809
{
805810
flags |= ContinuationFlags.ContinueOnCapturedTaskScheduler;
806-
context = sched;
811+
continuationContext = sched;
807812
return;
808813
}
809814

src/coreclr/inc/corinfo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ enum CorInfoOptions
722722
CORINFO_GENERICS_CTXT_FROM_METHODDESC |
723723
CORINFO_GENERICS_CTXT_FROM_METHODTABLE),
724724
CORINFO_GENERICS_CTXT_KEEP_ALIVE = 0x00000100, // Keep the generics context alive throughout the method even if there is no explicit use, and report its location to the CLR
725+
CORINFO_ASYNC_SAVE_CONTEXTS = 0x00000200, // Runtime async method must save and restore contexts
725726
};
726727

727728
//

src/coreclr/inc/jiteeversionguid.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@
3737

3838
#include <minipal/guid.h>
3939

40-
constexpr GUID JITEEVersionIdentifier = { /* a802fbbf-3e14-4b34-a348-5fba9fd756d4 */
41-
0xa802fbbf,
42-
0x3e14,
43-
0x4b34,
44-
{0xa3, 0x48, 0x5f, 0xba, 0x9f, 0xd7, 0x56, 0xd4}
40+
constexpr GUID JITEEVersionIdentifier = { /* f0752445-e116-444d-98ea-aaa7cbc30baa */
41+
0xf0752445,
42+
0xe116,
43+
0x444d,
44+
{0x98, 0xea, 0xaa, 0xa7, 0xcb, 0xc3, 0x0b, 0xaa}
4545
};
4646

4747
#endif // JIT_EE_VERSIONING_GUID_H

0 commit comments

Comments
 (0)