Skip to content

Commit 0829208

Browse files
committed
Fix for hang in IO.Pipelines.Pipe tests
1 parent 93d6920 commit 0829208

File tree

1 file changed

+34
-1
lines changed

1 file changed

+34
-1
lines changed

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,29 @@ public static unsafe void MoveNext<T, TOps>(T task) where T : Task where TOps :
349349

350350
while (true)
351351
{
352+
const CorInfoContinuationFlags continueFlags =
353+
CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_SYNCHRONIZATION_CONTEXT |
354+
CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_THREAD_POOL |
355+
CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_TASK_SCHEDULER;
356+
357+
// Make a note if have a context-neutral continuation. Such continuation indicates an async call that is not a result of
358+
// an await. So far such calls can only happen as a part of infrastructure helpers/thunks/stubs, as user code must await
359+
// into async code, can't simply call. The context-nutral continuation will run on the current context and will resume
360+
// the next continuation on this same context unconditionally. The idea is to pretend that infrastructure code does not
361+
// exist from the context propagation point of view and the notifier resumes the non-infrastructure code on whatever
362+
// context it wants.
363+
// Basically - we pass through to the notifier the context in which the suspend happened. The notifier needs to know that,
364+
// but may choose to ignore. One example is `IO.Pipelines.Pipe` that can be configured to resume everything on threadpool
365+
// regardless of the calling context.
366+
// It is ok if the leaf continuation runs on a context other that it asked for. It is also possible that non-infra
367+
// continuation gets posted to the original context, but then the context reschedules the action to the
368+
// threadpool (fairly common) or to another context (also possible, but uncommon).
369+
//
370+
// Either way the leaf continuation of the user code can only _ask_ for the context it wants to continue in,
371+
// but may end up running in what the notifier decided, and even then the context/scheduler may decide something else.
372+
// NB: if the leaf user continuation could re-decide and post or schedule itself to another context, the circle of
373+
// re-deciding could never end.
374+
bool isContexNeutralContinuation = (continuation.Flags & continueFlags) == 0;
352375
try
353376
{
354377
Continuation? newContinuation = continuation.Resume(continuation);
@@ -366,7 +389,15 @@ public static unsafe void MoveNext<T, TOps>(T task) where T : Task where TOps :
366389
}
367390
catch (Exception ex)
368391
{
392+
Continuation? callerContinuation = continuation.Next;
369393
Continuation nextContinuation = UnwindToPossibleHandler(continuation);
394+
395+
if (nextContinuation != callerContinuation)
396+
{
397+
// If we were unwoud above immediate call into infra thunks, the continuation context takes over.
398+
isContexNeutralContinuation = false;
399+
}
400+
370401
if (nextContinuation.Resume == null)
371402
{
372403
// Tail of AsyncTaskMethodBuilderT.SetException
@@ -403,7 +434,8 @@ public static unsafe void MoveNext<T, TOps>(T task) where T : Task where TOps :
403434
return;
404435
}
405436

406-
if (QueueContinuationFollowUpActionIfNecessary<T, TOps>(task, continuation))
437+
if (!isContexNeutralContinuation &&
438+
QueueContinuationFollowUpActionIfNecessary<T, TOps>(task, continuation))
407439
{
408440
contexts.Pop();
409441
return;
@@ -432,6 +464,7 @@ public static void HandleSuspended<T, TOps>(T task, Continuation suspendContinua
432464
CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_SYNCHRONIZATION_CONTEXT |
433465
CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_THREAD_POOL |
434466
CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_TASK_SCHEDULER;
467+
435468
Debug.Assert((headContinuation.Flags & continueFlags) == 0);
436469

437470
TOps.SetContinuationState(task, headContinuation);

0 commit comments

Comments
 (0)