From fe01440723937ae0fe95ee44ad54998670ede8be Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:43:23 -0700 Subject: [PATCH 1/8] Enable async feature/tests --- src/coreclr/inc/clrconfigvalues.h | 2 +- src/tests/async/Directory.Build.targets | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index b3d2d580cdf652..c1c492d1501106 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -710,7 +710,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64 #endif // Runtime-async -RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 0, "Enables runtime async method support") +RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 1, "Enables runtime async method support") /// /// Uncategorized diff --git a/src/tests/async/Directory.Build.targets b/src/tests/async/Directory.Build.targets index f4b2b948dfd68c..01f1cf7eaa0eff 100644 --- a/src/tests/async/Directory.Build.targets +++ b/src/tests/async/Directory.Build.targets @@ -12,10 +12,5 @@ true - - - true - - From 0c22f4194035a4ca042eec7f7ce994ab676da6a9 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:48:01 -0800 Subject: [PATCH 2/8] add a test --- .../valuetask-source/valuetask-source-stub.cs | 58 +++++++++++++++++++ .../valuetask-source-stub.csproj | 13 +++++ 2 files changed, 71 insertions(+) create mode 100644 src/tests/async/valuetask-source/valuetask-source-stub.cs create mode 100644 src/tests/async/valuetask-source/valuetask-source-stub.csproj diff --git a/src/tests/async/valuetask-source/valuetask-source-stub.cs b/src/tests/async/valuetask-source/valuetask-source-stub.cs new file mode 100644 index 00000000000000..7945d0b29b8fa7 --- /dev/null +++ b/src/tests/async/valuetask-source/valuetask-source-stub.cs @@ -0,0 +1,58 @@ +// 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.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; +using Xunit; + +public class ValueTaskSourceAndStubs +{ + [Fact] + public static void EntryPoint() + { + SynchronizationContext.SetSynchronizationContext(new MySyncContext()); + new ValueTaskSourceAndStubs().TestAsync(new C()).GetAwaiter().GetResult(); + } + + private async Task TestAsync(IFace i) + { + await i.Foo(0, 1, 2, 3, 4, 5, 6, 7, "value"); + } + + private struct C : IFace + { + public ValueTask Foo(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, T value) + { + return new ValueTask(new Source(), 0); + } + + private class Source : IValueTaskSource + { + public void GetResult(short token) + { + } + + public ValueTaskSourceStatus GetStatus(short token) + { + return ValueTaskSourceStatus.Pending; + } + + public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) + { + Assert.Equal(ValueTaskSourceOnCompletedFlags.UseSchedulingContext, flags); + ThreadPool.UnsafeQueueUserWorkItem(continuation, state, preferLocal: true); + } + } + } + + private interface IFace + { + ValueTask Foo(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, T value); + } + + private class MySyncContext : SynchronizationContext + { + } +} diff --git a/src/tests/async/valuetask-source/valuetask-source-stub.csproj b/src/tests/async/valuetask-source/valuetask-source-stub.csproj new file mode 100644 index 00000000000000..43028905d33cbd --- /dev/null +++ b/src/tests/async/valuetask-source/valuetask-source-stub.csproj @@ -0,0 +1,13 @@ + + + True + + true + + + + + + + + From 1f995b1705a795b4b55ef8db230cd99ecbbb4606 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Tue, 25 Nov 2025 20:55:09 -0800 Subject: [PATCH 3/8] Skip over transparent non-user continuations --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index ba4119270cbfde..d34f9e8a889c00 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -598,7 +598,7 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet } else if (vtsNotifier != null) { - // The awaiter must inform the ValueTaskSource source on whether the continuation + // The awaiter must inform the ValueTaskSource on whether the continuation // wants to run on a context, although the source may decide to ignore the suggestion. // Since the behavior of the source takes precedence, we clear the context flags of // the awaiting continuation (so it will run transparently on what the source decides) @@ -607,8 +607,15 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet // the continuation chain builds from the innermost frame out and at the time when the // notifier is created we do not know yet if the caller wants to continue on a context. ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None; - ContinuationFlags continuationFlags = headContinuation.Next!.Flags; + // skip to the next non-transparent continuation, if such exists + Continuation nextUserContinuaton = headContinuation; + while ((nextUserContinuaton.Flags & continueFlags) == 0 && nextUserContinuaton.Next != null) + { + nextUserContinuaton = nextUserContinuaton.Next; + } + + ContinuationFlags continuationFlags = nextUserContinuaton.Flags; const ContinuationFlags continueOnContextFlags = ContinuationFlags.ContinueOnCapturedSynchronizationContext | ContinuationFlags.ContinueOnCapturedTaskScheduler; @@ -620,7 +627,7 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet } // Clear continuation flags, so that continuation runs transparently - headContinuation.Next!.Flags &= ~continueFlags; + nextUserContinuaton.Flags &= ~continueFlags; TOps.ValueTaskSourceOnCompleted(task, vtsNotifier, configFlags); } else From b1a6b505f71331b4c94f6021c01c8145189a10ac Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:04:23 -0800 Subject: [PATCH 4/8] Some cleanups for the test --- .../async/valuetask-source/valuetask-source-stub.cs | 11 ++++++++++- .../valuetask-source/valuetask-source-stub.csproj | 5 ----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/tests/async/valuetask-source/valuetask-source-stub.cs b/src/tests/async/valuetask-source/valuetask-source-stub.cs index 7945d0b29b8fa7..4b95656558de98 100644 --- a/src/tests/async/valuetask-source/valuetask-source-stub.cs +++ b/src/tests/async/valuetask-source/valuetask-source-stub.cs @@ -12,8 +12,17 @@ public class ValueTaskSourceAndStubs [Fact] public static void EntryPoint() { + SynchronizationContext? original = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(new MySyncContext()); - new ValueTaskSourceAndStubs().TestAsync(new C()).GetAwaiter().GetResult(); + + try + { + new ValueTaskSourceAndStubs().TestAsync(new C()).GetAwaiter().GetResult(); + } + finally + { + SynchronizationContext.SetSynchronizationContext(original); + } } private async Task TestAsync(IFace i) diff --git a/src/tests/async/valuetask-source/valuetask-source-stub.csproj b/src/tests/async/valuetask-source/valuetask-source-stub.csproj index 43028905d33cbd..9367a79b2edbb1 100644 --- a/src/tests/async/valuetask-source/valuetask-source-stub.csproj +++ b/src/tests/async/valuetask-source/valuetask-source-stub.csproj @@ -1,13 +1,8 @@ True - - true - - - From a98201c568d5e42bf1b82dfa1f0c27ca86e641a2 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:47:03 -0800 Subject: [PATCH 5/8] Assume "headContinuation.Next != null" --- .../Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index d34f9e8a889c00..54e6787fc3cf20 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -576,7 +576,11 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet ContinuationFlags.ContinueOnCapturedSynchronizationContext | ContinuationFlags.ContinueOnThreadPool | ContinuationFlags.ContinueOnCapturedTaskScheduler; + + // Note: One place that relies on these invariants is the path that deals with ValueTaskSource. + // We have the asserts here though, since ValueTaskSource is a relatively rare scenario. Debug.Assert((headContinuation.Flags & continueFlags) == 0); + Debug.Assert(headContinuation.Next != null); TOps.SetContinuationState(task, headContinuation); @@ -608,8 +612,8 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet // notifier is created we do not know yet if the caller wants to continue on a context. ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None; - // skip to the next non-transparent continuation, if such exists - Continuation nextUserContinuaton = headContinuation; + // skip to a nontransparent continuation, if such exists. + Continuation nextUserContinuaton = headContinuation.Next!; while ((nextUserContinuaton.Flags & continueFlags) == 0 && nextUserContinuaton.Next != null) { nextUserContinuaton = nextUserContinuaton.Next; From 0824cbaecb0f817e9d3fd7a2be9432261769e6cb Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:48:47 -0800 Subject: [PATCH 6/8] spelling --- .../Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 54e6787fc3cf20..e3cad949350cb3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -613,13 +613,13 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None; // skip to a nontransparent continuation, if such exists. - Continuation nextUserContinuaton = headContinuation.Next!; - while ((nextUserContinuaton.Flags & continueFlags) == 0 && nextUserContinuaton.Next != null) + Continuation nextUserContinuation = headContinuation.Next!; + while ((nextUserContinuation.Flags & continueFlags) == 0 && nextUserContinuation.Next != null) { - nextUserContinuaton = nextUserContinuaton.Next; + nextUserContinuation = nextUserContinuation.Next; } - ContinuationFlags continuationFlags = nextUserContinuaton.Flags; + ContinuationFlags continuationFlags = nextUserContinuation.Flags; const ContinuationFlags continueOnContextFlags = ContinuationFlags.ContinueOnCapturedSynchronizationContext | ContinuationFlags.ContinueOnCapturedTaskScheduler; @@ -631,7 +631,7 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet } // Clear continuation flags, so that continuation runs transparently - nextUserContinuaton.Flags &= ~continueFlags; + nextUserContinuation.Flags &= ~continueFlags; TOps.ValueTaskSourceOnCompleted(task, vtsNotifier, configFlags); } else From 14589b9187a16442be228583d54c363a78b3a757 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Thu, 27 Nov 2025 12:08:52 -0800 Subject: [PATCH 7/8] Removed the assert. --- .../Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index e3cad949350cb3..54d116e0d20b5f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -577,10 +577,7 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet ContinuationFlags.ContinueOnThreadPool | ContinuationFlags.ContinueOnCapturedTaskScheduler; - // Note: One place that relies on these invariants is the path that deals with ValueTaskSource. - // We have the asserts here though, since ValueTaskSource is a relatively rare scenario. Debug.Assert((headContinuation.Flags & continueFlags) == 0); - Debug.Assert(headContinuation.Next != null); TOps.SetContinuationState(task, headContinuation); @@ -612,7 +609,10 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet // notifier is created we do not know yet if the caller wants to continue on a context. ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None; - // skip to a nontransparent continuation, if such exists. + // Skip to a nontransparent/user continuation. Such continuaton must exist. + // Since we see a VTS notifier, something was directly or indirectly + // awaiting an async thunk for a ValueTask-returning method. + // That can only happen in nontransparent/user code. Continuation nextUserContinuation = headContinuation.Next!; while ((nextUserContinuation.Flags & continueFlags) == 0 && nextUserContinuation.Next != null) { From 6460bac74f55d4a4decb2fcc5ac01f4a4505f266 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Fri, 28 Nov 2025 08:16:12 -0800 Subject: [PATCH 8/8] Revert "Enable async feature/tests" This reverts commit fe01440723937ae0fe95ee44ad54998670ede8be. --- src/coreclr/inc/clrconfigvalues.h | 2 +- src/tests/async/Directory.Build.targets | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index c1c492d1501106..b3d2d580cdf652 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -710,7 +710,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64 #endif // Runtime-async -RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 1, "Enables runtime async method support") +RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 0, "Enables runtime async method support") /// /// Uncategorized diff --git a/src/tests/async/Directory.Build.targets b/src/tests/async/Directory.Build.targets index 01f1cf7eaa0eff..f4b2b948dfd68c 100644 --- a/src/tests/async/Directory.Build.targets +++ b/src/tests/async/Directory.Build.targets @@ -12,5 +12,10 @@ true + + + true + +