Skip to content

ValueTaskPromise never captures a custom TaskScheduler #38

Description

@jhorv

In src/Eff/Utils/ValueTaskPromise.cs, CaptureThreadContext assigns TaskScheduler.Default and then compares it against TaskScheduler.Default, so the guard is always false and the branch is unreachable:

// src/Eff/Utils/ValueTaskPromise.cs:126-133
else
{
    TaskScheduler ts = TaskScheduler.Default;   // should be TaskScheduler.Current
    if (ts != TaskScheduler.Default)
    {
        destination.Scheduler = ts;
    }
}

The equivalent code in ManualResetValueTaskSourceCore reads TaskScheduler.Current. The SynchronizationContext branch just above it is correct — only the scheduler fallback is affected.

Effect

A custom scheduler is silently dropped, so the continuation resumes on the thread pool instead of on it. Reachable whenever a handler awaits EffStateMachine.TaskAwaiter without ConfigureAwait(false) while running under a non-default scheduler (ConcurrentExclusiveSchedulerPair, a Dataflow block with a custom scheduler, etc.).

EffectHandler.Handle uses ConfigureAwait(false), so the built-in path is unaffected. Two of the samples' handlers do await it bare, though:

  • samples/Eff.Examples.NonDeterminism/NonDetEffectHandler.cs:73
  • samples/Eff.Examples.Continuation/ContinuationHandler.cs:77

Low severity: the bug errs towards not capturing, so it cannot deadlock. It just diverges from what a plain await would do.

Fix

-                TaskScheduler ts = TaskScheduler.Default;
+                TaskScheduler ts = TaskScheduler.Current;

Verified against a fork: with TaskScheduler.Current, a bare await of TaskAwaiter.Value under a custom scheduler resumes on that scheduler; reverting the one word fails the test. An equivalent ConfigureAwait(false) await correctly keeps resuming on TaskScheduler.Default either way.

Introduced in 5cfb9d6, still present on main (1388385).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions