-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Cache continuation used for runtime async callable value task thunks #127973
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
jakobbotsch
wants to merge
2
commits into
dotnet:main
Choose a base branch
from
jakobbotsch:cache-value-task-source-continuation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -125,6 +125,16 @@ public ref byte GetResultStorageOrNull() | |
| ref byte data = ref RuntimeHelpers.GetRawData(this); | ||
| return ref Unsafe.Add(ref data, (DataOffset - PointerSize) + index * PointerSize); | ||
| } | ||
|
|
||
| protected void EncodeFieldOffsetInFlags(ref byte field, ContinuationFlags firstBit, ContinuationFlags numBits) | ||
| { | ||
| int offset = (int)Unsafe.ByteOffset(ref RuntimeHelpers.GetRawData(this), ref field); | ||
| offset -= DataOffset; | ||
| Debug.Assert(offset >= 0 && offset % PointerSize == 0); | ||
| uint index = 1 + (uint)offset / PointerSize; | ||
| Debug.Assert(index < (1 << (int)numBits)); | ||
| Flags |= (ContinuationFlags)((uint)index << (int)firstBit); | ||
| } | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Explicit)] | ||
|
|
@@ -202,7 +212,7 @@ private ref struct RuntimeAsyncStackState | |
| // to one of these notifiers. | ||
| public ICriticalNotifyCompletion? CriticalNotifier; | ||
| public INotifyCompletion? Notifier; | ||
| public ValueTaskSourceNotifier? ValueTaskSourceNotifier; | ||
| public ValueTaskContinuation? ValueTaskContinuation; | ||
| public Task? TaskNotifier; | ||
|
|
||
| // When we suspend in the leaf, the contexts are captured into these fields. | ||
|
|
@@ -245,6 +255,7 @@ public void Pop(Thread thread) | |
| private unsafe struct RuntimeAsyncAwaitState | ||
| { | ||
| public Continuation? SentinelContinuation; | ||
| public ValueTaskContinuation? CachedValueTaskContinuation; | ||
|
|
||
| // We cache the thread here to avoid unnecessary repeated TLS lookups. | ||
| public Thread? CurrentThread; | ||
|
|
@@ -330,6 +341,202 @@ private static unsafe Continuation AllocContinuationClass(Continuation prevConti | |
| } | ||
| #endif | ||
|
|
||
| private sealed unsafe class ValueTaskContinuation : Continuation | ||
| { | ||
| // Currently all continuations are expected to capture and restore | ||
| // ExecutionContext, even though we do not actually need it here. | ||
| public ExecutionContext? ExecutionContext; | ||
| private object? _source; | ||
| private short _token; | ||
| private delegate* managed<object, Action<object?>, object?, short, ValueTaskSourceOnCompletedFlags, void> _onCompleted; | ||
| private delegate* managed<object, short, ref byte, void> _getResult; | ||
|
|
||
| public ValueTaskContinuation() | ||
| { | ||
| ResumeInfo = (ResumeInfo*)Unsafe.AsPointer(ref ValueTaskContinuationResume.ResumeInfo); | ||
|
|
||
| EncodeFieldOffsetInFlags( | ||
| ref Unsafe.As<ExecutionContext?, byte>(ref ExecutionContext), | ||
| ContinuationFlags.ExecutionContextIndexFirstBit, | ||
| ContinuationFlags.ExecutionContextIndexNumBits); | ||
| } | ||
|
|
||
| public void OnCompleted(Action<object?> continuation, object? state, ValueTaskSourceOnCompletedFlags flags) | ||
| { | ||
| Debug.Assert(_source != null); | ||
| _onCompleted(_source, continuation, state, _token, flags); | ||
| } | ||
|
|
||
| public void GetResult(ref byte returnValue) | ||
| { | ||
| Debug.Assert(_source != null); | ||
|
|
||
| // Avoid retaining source. The call below may throw. | ||
| object source = _source; | ||
| _source = null; | ||
|
|
||
| _getResult(source, _token, ref returnValue); | ||
| } | ||
|
|
||
| public void Initialize(object source, short token) | ||
| { | ||
| _source = source; | ||
| _token = token; | ||
| _onCompleted = &OnCompleted; | ||
| _getResult = &GetResult; | ||
| } | ||
|
|
||
| public void Initialize<T>(object source, short token) | ||
| { | ||
| _source = source; | ||
| _token = token; | ||
| _onCompleted = &OnCompleted<T>; | ||
| _getResult = &GetResult<T>; | ||
| } | ||
|
|
||
| private static void OnCompleted(object source, Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) | ||
| { | ||
| if (source is Task t) | ||
| { | ||
| Debug.Assert(state is ITaskCompletionAction); | ||
| if (!t.TryAddCompletionAction(Unsafe.As<object, ITaskCompletionAction>(ref state))) | ||
| { | ||
| ThreadPool.UnsafeQueueUserWorkItemInternal(state, preferLocal: true); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| Debug.Assert(source is IValueTaskSource); | ||
| IValueTaskSource typedSource = Unsafe.As<object, IValueTaskSource>(ref source); | ||
| typedSource.OnCompleted(continuation, state, token, flags); | ||
| } | ||
| } | ||
|
|
||
| private static void GetResult(object source, short token, ref byte result) | ||
| { | ||
| if (source is Task t) | ||
| { | ||
| TaskAwaiter.ValidateEnd(t); | ||
| } | ||
| else | ||
| { | ||
| Debug.Assert(source is IValueTaskSource); | ||
| IValueTaskSource typedSource = Unsafe.As<object, IValueTaskSource>(ref source); | ||
| typedSource.GetResult(token); | ||
| } | ||
| } | ||
|
|
||
| private static void OnCompleted<T>(object source, Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) | ||
| { | ||
| if (source is Task t) | ||
| { | ||
| Debug.Assert(state is ITaskCompletionAction); | ||
| if (!t.TryAddCompletionAction(Unsafe.As<object, ITaskCompletionAction>(ref state))) | ||
| { | ||
| ThreadPool.UnsafeQueueUserWorkItemInternal(state, preferLocal: true); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| Debug.Assert(source is IValueTaskSource<T>); | ||
| IValueTaskSource<T> typedSource = Unsafe.As<object, IValueTaskSource<T>>(ref source); | ||
| typedSource.OnCompleted(continuation, state, token, flags); | ||
| } | ||
| } | ||
|
|
||
| private static void GetResult<T>(object source, short token, ref byte result) | ||
| { | ||
| if (source is Task<T> t) | ||
| { | ||
| TaskAwaiter.ValidateEnd(t); | ||
| Unsafe.As<byte, T>(ref result) = t.ResultOnSuccess; | ||
| } | ||
| else | ||
| { | ||
| Debug.Assert(source is IValueTaskSource<T>); | ||
| IValueTaskSource<T> typedSource = Unsafe.As<object, IValueTaskSource<T>>(ref source); | ||
| Unsafe.As<byte, T>(ref result) = typedSource.GetResult(token); | ||
| } | ||
| } | ||
|
|
||
| private static class ValueTaskContinuationResume | ||
| { | ||
| [FixedAddressValueType] | ||
| public static ResumeInfo ResumeInfo = new ResumeInfo | ||
| { | ||
| DiagnosticIP = null, | ||
| Resume = &ResumeValueTaskContinuation, | ||
| }; | ||
|
|
||
| public static Continuation? ResumeValueTaskContinuation(Continuation cont, ref byte result) | ||
| { | ||
| var vtsCont = (ValueTaskContinuation)cont; | ||
| vtsCont.Next = null; | ||
| vtsCont.ExecutionContext = null; | ||
| t_runtimeAsyncAwaitState.CachedValueTaskContinuation = vtsCont; | ||
|
|
||
| vtsCont.GetResult(ref result); | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [BypassReadyToRun] | ||
| [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] | ||
| private static unsafe void TransparentAwaitValueTask(ValueTask valueTask) | ||
| { | ||
| ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; | ||
| Continuation? sentinelContinuation = state.SentinelContinuation ??= new Continuation(); | ||
|
|
||
| ValueTaskContinuation? vtsCont = state.CachedValueTaskContinuation; | ||
| if (vtsCont != null) | ||
| { | ||
| state.CachedValueTaskContinuation = null; | ||
| } | ||
| else | ||
| { | ||
| vtsCont = new ValueTaskContinuation(); | ||
| } | ||
|
|
||
| Debug.Assert(valueTask._obj != null); | ||
| vtsCont.Initialize(valueTask._obj, valueTask._token); | ||
| vtsCont.ExecutionContext = ExecutionContext.CaptureForSuspension(state.CurrentThread!); | ||
|
|
||
| sentinelContinuation.Next = vtsCont; | ||
| state.StackState->ValueTaskContinuation = vtsCont; | ||
|
|
||
|
jakobbotsch marked this conversation as resolved.
|
||
| state.CaptureContexts(); | ||
| AsyncSuspend(vtsCont); | ||
| } | ||
|
|
||
| [BypassReadyToRun] | ||
| [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] | ||
| private static unsafe void TransparentAwaitValueTaskOfT<T>(ValueTask<T?> valueTask) | ||
| { | ||
| ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; | ||
| Continuation? sentinelContinuation = state.SentinelContinuation ??= new Continuation(); | ||
|
|
||
| ValueTaskContinuation? vtsCont = state.CachedValueTaskContinuation; | ||
| if (vtsCont != null) | ||
| { | ||
| state.CachedValueTaskContinuation = null; | ||
| } | ||
| else | ||
| { | ||
| vtsCont = new ValueTaskContinuation(); | ||
| } | ||
|
|
||
| Debug.Assert(valueTask._obj != null); | ||
| vtsCont.Initialize<T>(valueTask._obj, valueTask._token); | ||
| vtsCont.ExecutionContext = ExecutionContext.CaptureForSuspension(state.CurrentThread!); | ||
|
|
||
| sentinelContinuation.Next = vtsCont; | ||
| state.StackState->ValueTaskContinuation = vtsCont; | ||
|
|
||
| state.CaptureContexts(); | ||
| AsyncSuspend(vtsCont); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Used by internal thunks that implement awaiting on Task or a ValueTask. | ||
| /// A ValueTask may wrap: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the comments about ValueTask may be dropped now. |
||
|
|
@@ -339,22 +546,15 @@ private static unsafe Continuation AllocContinuationClass(Continuation prevConti | |
| /// Therefore, when we are awaiting a ValueTask completion we are really | ||
| /// awaiting a completion of an underlying Task or ValueTaskSource. | ||
| /// </summary> | ||
| /// <param name="o"> Task or a ValueTaskNotifier whose completion we are awaiting.</param> | ||
| /// <param name="t"> Task whose completion we are awaiting.</param> | ||
| [BypassReadyToRun] | ||
| [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] | ||
| private static unsafe void TransparentAwait(object o) | ||
| private static unsafe void TransparentAwait(Task t) | ||
| { | ||
| ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; | ||
| Continuation? sentinelContinuation = state.SentinelContinuation ??= new Continuation(); | ||
|
|
||
| if (o is Task t) | ||
| { | ||
| state.StackState->TaskNotifier = t; | ||
| } | ||
| else | ||
| { | ||
| state.StackState->ValueTaskSourceNotifier = (ValueTaskSourceNotifier)o; | ||
| } | ||
| state.StackState->TaskNotifier = t; | ||
|
|
||
| state.CaptureContexts(); | ||
| AsyncSuspend(sentinelContinuation); | ||
|
|
@@ -456,8 +656,9 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) | |
| ThreadPool.UnsafeQueueUserWorkItemInternal(this, preferLocal: true); | ||
| } | ||
| } | ||
| else if (stackState->ValueTaskSourceNotifier is { } valueTaskSourceNotifier) | ||
| else if (stackState->ValueTaskContinuation is { } valueTaskSourceCont) | ||
| { | ||
| Debug.Assert(headContinuation == valueTaskSourceCont); | ||
| // 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 | ||
|
|
@@ -491,7 +692,7 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) | |
|
|
||
| // Clear continuation flags, so that continuation runs transparently | ||
| nextUserContinuation.Flags &= ~continueFlags; | ||
| valueTaskSourceNotifier.OnCompleted(s_runContinuationAction, this, configFlags); | ||
| valueTaskSourceCont.OnCompleted(s_runContinuationAction, this, configFlags); | ||
| } | ||
| else | ||
| { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.