microsoft / microsoft/vs-threading

Free-threaded validation sample not working

Open
#727 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
1k
Forks
160
Avg merge
1d 12h
Merged PRs (30d)
28

Description

Bug description

The cookbook has this sample for how to test if a method is free-threaded: https://github.com/microsoft/vs-threading/blame/bbc8fc3e26490bf58a46690fbc749389afce9e06/doc/cookbook_vs.md#L353-L364

Using it in an extension with VS 16.9, it always hangs. Even if I change the "component" being tested to nothing more than return 0;, the jtf.Run never returns. Using Visual Studio's Parallel Stacks debugging feature, I can no longer find any tasks from the delegate that it ran, so that task appears to have completed, yet jtf.Run doesn't return.

Repro steps

Here's the code that I'm using. It's a little test extension to test NuGet APIs in Visual Studio, and we'd like to be able to check if an API is free-threaded or not: https://github.com/NuGet/Entropy/blob/6bc031c2f8c668079069afcce1b0b6b16bcc41b3/IVsTestingExtension/src/Xaml/ApiInvoker/ApiInvokeView.xaml.cs#L78-L93

I also tried simplifying and introducing new methods, to make call stacks easier to understand, rather than having an anonymous function inside another anonymous function:

public void RunButton_Click(object sender, RoutedEventArgs e)
{
    var message = jtf.Run(CheckFreeThreaded);
    _results.Text = message;
}

private async Task<string> CheckFreeThreaded()
{
    string message;
    var o = jtf.Context.SuppressRelevance();
    try
    {
        await Task.Run(MethodToCheck);
    }
    catch (Exception e)
    {
        message = e.ToString();
    }
    finally
    {
        o.Dispose();
    }
    return message;
}

private string MethodToCheck()
{
    return "worked";
}
Expected behavior

Given the "simplified" example above, I'd expect when I click the WPF button, that the _results text block's message becomes the string "worked".

Actual behavior

VS hangs.

Putting a breakpoint in MethodToCheck and looking at Visual Studio's threads window, I can see that the main thread's call stack is on CheckFreeThreaded's await Task.Run line, and MethodToCheck is being run on a background thread. I don't know if it's normal, but the Parallel Stacks window shows the task running MethodToCheck, but there are no tasks that are linked/waiting to it. Also, none of the tasks in the Parallel Stacks tasks view shows CheckFreeThreaded. I know that calling an async method is synchronous until the first await on an incomplete task, but I would have thought that calling await Task.Run from the main thread always awaits an incomplete task, so a Task should be created. I'm confused why MethodToCheck is currently executing, but the main thread is still in CheckFreeThreaded's await line. But it doesn't bother me, maybe it's just how async state machines for async methods work when calling Task.Run.

Letting the debugger continue, VS just hangs. Breakpoints in CheckFreeThreaded's catch and finally block, or the return statement, never get hit. Maybe it's a bug or limitation in the debugger and/or compiler and the state machine it generated, I don't know.

But pressing the pause button shows that the main thread's call stack is now RunButton_Click->[waiting on async operation]->NoMessagePumpSyncContext.Wait, so CheckFreeThreaded is no longer on the main call stack. The Parallel Stacks window doesn't show any task with CheckFreeThreaded in the call stack, so I'm confused how it could have completed without hitting any of my breakpoints. But at the same time, if there is no incomplete task for CheckFreeThreaded, then the task that RunButton_Click was waiting on should be finished, and execution continues. As mentioned, the threads window says that the main thread is waiting on async operation before saying NoMessagePumpSyncContext.Wait, so I would expect to see a task in the Parallel Stacks task view with NoMessagePumpSyncContext in the call stack, but there isn't.

I'm way outside of my area of expertise, so I have no idea what else might help.

  • Version used: 16.8.55
  • Application (if applicable): Visual Studio 2019 16.9 preview
Additional context

I didn't try older versions of VS, but a team mate used this in an extension to test APIs in VS, so I assume this worked at some point.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the free-threaded validation sample in doc/cookbook_vs.md and compare it with the repro in IVsTestingExtension/src/Xaml/ApiInvoker/ApiInvokeView.xaml.cs. Trace jtf.Run, CheckFreeThreaded, Task.Run, and NoMessagePumpSyncContext.Wait to determine why the simplified method does not return. Done means the sample completes and displays "worked" instead of hanging.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.