Skip to content

Conversation

@VSadov
Copy link
Member

@VSadov VSadov commented Oct 14, 2025

Fixes: #120709

There is a scenario where ValueTask decides on whether to run continuations on the scheduling context or not.

It happens in a case when an await calls into a ValueTask that wraps a ValueTaskSource.
In such case we may have a nontrivial context provided to the ValueTaskSource at the time of the await, but ValueTaskSource may choose to ignore the context when running callbacks.

A typical test sensitive to this looks like:

public async Task UseSynchronizationContextFalseIgnoresSyncContextForReaderScheduler()
{
SynchronizationContext previous = SynchronizationContext.Current;
var sc = new CustomSynchronizationContext();
try
{
SynchronizationContext.SetSynchronizationContext(sc);
var pipe = new Pipe(new PipeOptions(useSynchronizationContext: false));
Func<Task> doRead = async () =>
{
ReadResult result = await pipe.Reader.ReadAsync();
pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End);
pipe.Reader.Complete();
};
// This needs to run on the current SynchronizationContext
Task reading = doRead();
PipeWriter buffer = pipe.Writer;
buffer.Write("Hello World"u8.ToArray());
// Don't run code on our sync context (we just want to make sure the callbacks)
// are scheduled on the sync context
await buffer.FlushAsync().ConfigureAwait(false);
// Nothing posted to the sync context
Assert.Equal(0, sc.Callbacks.Count);
pipe.Writer.Complete();
// Don't run code on our sync context
await reading.ConfigureAwait(false);
}
finally
{
SynchronizationContext.SetSynchronizationContext(previous);
}
}

The part

                    ReadResult result = await pipe.Reader.ReadAsync();

should behave as

                    ReadResult result = await pipe.Reader.ReadAsync().ConfigureAwait(false);

Otherwise we end up posting the doRead continuation to the tracking CustomSynchronizationContext which only tracks Posts and does not run them, thus the test makes no progress.

@VSadov VSadov requested a review from jakobbotsch October 14, 2025 16:48
@VSadov
Copy link
Member Author

VSadov commented Oct 14, 2025

CC: @stephentoub Are we getting the desired behavior correctly?

We do have testcases sensitive to these behaviors, which fail without this fix.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR addresses a scenario where ValueTask doesn't properly handle configuration of async continuations when wrapping ValueTaskSource implementations. The fix ensures that unconfigured awaits behave as if they were configured with ConfigureAwait(false) when the underlying ValueTaskSource can choose to ignore scheduling context.

Key changes:

  • Introduces deferred configuration mechanism for ValueTaskSource callbacks
  • Updates runtime async thunks to use unconfigured task conversion
  • Enables runtime async tests that were previously disabled

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs Adds AsUnconfiguredTask() methods and deferred configuration support for ValueTaskSource instances
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs Implements configuration logic to handle context flags between continuations and ValueTaskSource
src/coreclr/vm/corelib.h Updates method bindings to use unconfigured task conversion
src/coreclr/vm/asyncthunks.cpp Changes async thunks to call AsUnconfiguredTask instead of AsTask
src/coreclr/inc/clrconfigvalues.h Enables runtime async support by default
src/tests/async/Directory.Build.targets Removes project build disable for runtime async tests
src/tests/async/Directory.Build.props Adds warning suppression for new analyzer rule
src/tests/async/struct/struct.cs Adds attributes to opt out of runtime async generation for struct methods

@VSadov
Copy link
Member Author

VSadov commented Nov 5, 2025

@jakobbotsch @stephentoub - I have pushed the latest changes that add a directed testcase that exercise the discussed ValueTaskSource scenario a bit deeper than existing tests in Libraries do.
There is also a fix that makes the new and preexisting tests to pass with runtime async enabled.

Please take a look.

Copy link
Member

@jakobbotsch jakobbotsch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM beyond the few minor pieces of feedback.
It is unfortunate with the extra layers of indirection around ValueTaskSourceNotifier, but it is probably something we can look into removing as part of #119842.

VSadov and others added 2 commits November 6, 2025 11:30
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@VSadov
Copy link
Member Author

VSadov commented Nov 6, 2025

This LGTM beyond the few minor pieces of feedback. It is unfortunate with the extra layers of indirection around ValueTaskSourceNotifier, but it is probably something we can look into removing as part of #119842.

ValueTaskSourceNotifier is there just because ValueTaskSource by itself does not have an identity, in "await in progress" sense. That is to enable reuse, pooling, etc,... The identity is handled separately as a token, thus we have ValueTaskSourceNotifier, which is basically a {source, token} tuple.
It is also used to abstract generic/nongeneric cases, as generic IValueTaskSource does not derive from nongeneric one. It could, as OnCompleted does not need T, but somehow it doesn't.

Yes, #119842 might allow removing ValueTaskSourceNotifier. Both the source and the token must be present in the continuation, as they are needed for the GetResult to work. If there is a way fish out the parts of ValueTaskSourceNotifier from the continuation, we would not need ValueTaskSourceNotifier.

@VSadov
Copy link
Member Author

VSadov commented Nov 7, 2025

/ba-g scenarios sensitive to this change can only happen on platforms that support async and when async is enabled. (verified that they are passing)

Libraries failures are #5015 and #120527
osx-like failures are #6410

@VSadov VSadov merged commit 704d429 into dotnet:main Nov 7, 2025
144 of 172 checks passed
@VSadov VSadov deleted the vtSrc branch November 7, 2025 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RuntimeAsync] Awaiting a configured ValueTaskSource may end up running in a wrong context.

2 participants