Skip to content

Move VisualStudioTaskSchedulerProvider to the EditorFeatures layer#48662

Merged
sharwell merged 3 commits intodotnet:masterfrom
sharwell:jtf-scheduler
Oct 21, 2020
Merged

Move VisualStudioTaskSchedulerProvider to the EditorFeatures layer#48662
sharwell merged 3 commits intodotnet:masterfrom
sharwell:jtf-scheduler

Conversation

@sharwell
Copy link
Contributor

Prior to this change, tests in the EditorFeatures layer were using JTF through IThreadingContext, but didn't pull in a JTF-aware task scheduler provider because it was up in the Visual Studio layer. Moving the JTF-aware scheduler down to the EditorFeatures layer causes UseExportProviderAttribute.After to dispatch through JTF instead of trying to dispatch through XUnit's test synchronization context, which doesn't end up running on the WPF dispatcher.

Fixes #48644

@sharwell sharwell requested a review from a team as a code owner October 16, 2020 04:36
@jasonmalinowski
Copy link
Member

@sharwell: it's unclear to me why this actually fixes it. Specifically, what's causing this relationship:

Moving the JTF-aware scheduler down to the EditorFeatures layer causes UseExportProviderAttribute.After to dispatch through JTF instead of trying to dispatch through XUnit's test synchronization context, which doesn't end up running on the WPF dispatcher.

I have a guess, but I'd rather the PR was clear. 😄

More generally, the change in general makes me hesitate too: if I could remove the VisualStudioWorkspace task scheduler without breaking people I'd do so in an instant; us putting this on a UI thread is something we absolutely regret. Moving this to EditorFeatures seems to be doubling down on it, and also could potentially break other hosts that didn't want this. For example, what is the impact here for VS for Mac?

@sharwell
Copy link
Contributor Author

it's unclear to me why this actually fixes it. Specifically, what's causing this relationship:

The default task scheduler prior to this change is obtained here:

public TaskScheduler CurrentContextScheduler
=> (SynchronizationContext.Current != null) ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Default;

For XUnit tests, the current context scheduler becomes AsyncTestSyncContext. Unfortunately, work posted to that synchronization context isn't running like we'd expect when we push a DispatcherFrame here:

There are three primary options for correcting this:

  1. Use a custom task scheduler for test code which dispatches through Dispatcher (or through JoinableTaskFactory since that works here too)
  2. Use a custom task scheduler for test code which dispatches to TaskScheduler.Default
  3. Move VisualStudioTaskSchedulerProvider down to the layer where IThreadingContext is defined, which ensures that all consumers that pull the EditorFeatures layer have a task scheduler which works with the context.

Other applications will either fall into a bucket where the default task scheduler provider worked as-is (and the new one will also work), or they were already using one of the above workarounds.

@jasonmalinowski
Copy link
Member

Ah, so xUnit will wait for work scheduled to the AsyncTestSyncContext, but it does that after our UseExportProviderAttribute.After runs? Even though I'm not a fan of tests being async void, but wouldn't those be broken too, which is why I understand xUnit has the special sync context in the first place?

@jasonmalinowski
Copy link
Member

Other applications will either fall into a bucket where the default task scheduler provider worked as-is (and the new one will also work)

I guess I could totally imagine a host where the Workspace gets created on a background thread, and this is changing behavior now. I guess of your options presented I'd do the first -- move the code down but still leave the exporting explicit for tests and the VS layer. But my earlier question still stands -- something seems more fundamentally off here first.

@jasonmalinowski
Copy link
Member

Although I guess you could argue that if we break a host with this change, they were already on pretty thin ice if they were that sensitive for which thread they were on in the first place. I'm also not sure whether we care about cases where they are MEF importing editor features but don't export IThreadingContext as a host like CodeFlow. I guess your provider would MEF reject, but I'm not sure to what end precisely.

@sharwell
Copy link
Contributor Author

sharwell commented Oct 16, 2020

but don't export IThreadingContext

IThreadingContext (and JTF) is required for the EditorFeatures layer, and any host using this layer already supports it. There is no main thread requirement though, and this provider handles both cases.

Note that the service default layer for this service was changed to Editor, so any host providing their own implementation of this service would not be impacted (as it would be using Host).

@jasonmalinowski
Copy link
Member

IThreadingContext (and JTF) is required for the EditorFeatures layer, and any host using this layer already supports it.

You're right, since at this point even the editor itself more or less requires it. Not sure what I was thinking there.

So I agree this fix doesn't really break anything or change behavior, but my other question is unanswered: if work is being scheduled to AsyncTestSyncContext, why is xUnit not waiting for that prior to running the after test handler? Am I misunderstanding the xUnit design here?

namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities
{
[ExportWorkspaceService(typeof(ITaskSchedulerProvider), ServiceLayer.Host), Shared]
[ExportWorkspaceService(typeof(ITaskSchedulerProvider), ServiceLayer.Editor), Shared]
Copy link
Member

Choose a reason for hiding this comment

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

I admit I wish we had a comment here that this is the default being provided for back compat, but we encourage new hosts who can do so to override this behavior and run WorkspaceChanged off the main thread.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If new hosts are out-of-process, they wouldn't have a main thread and this is automatic. 😄

Copy link
Member

Choose a reason for hiding this comment

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

Well, out of proc hosts aren't importing this layer in the first place. I was thinking more for things like VS for Mac or something. But yeah, not sure how many other ones we might still have of that.

@sharwell
Copy link
Contributor Author

if work is being scheduled to AsyncTestSyncContext, why is xUnit not waiting for that prior to running the after test handler?

If the test method returns Task, it uses that instead of waiting for the synchronization context to report completion.
https://github.com/xunit/xunit/blob/a8614d34999c889e1c8014f679de95d20eae1304/src/xunit.execution/Sdk/Frameworks/Runners/TestInvoker.cs#L257-L271

@jasonmalinowski
Copy link
Member

@sharwell: for defense in depth should we also be updating our code to wait on that as well, or fail the test if there's still pending work to catch other issues like this? I'm also a bit confused then looking at it because it seems AsyncTestSyncContext itself just either runs things to the thread pool or simply pushes them to the underlying context. So:

Unfortunately, work posted to that synchronization context isn't running like we'd expect when we push a DispatcherFrame here:

Makes me wonder what the underlying context was and why it was interfering as well.

@sharwell
Copy link
Contributor Author

@jasonmalinowski The root cause of the test failures was a processing delay for things queued to the main thread with lower priority than a yield operation also running on the main thread. The yield is being fixed by #44514.

@sharwell sharwell merged commit c8eecdb into dotnet:master Oct 21, 2020
@sharwell sharwell deleted the jtf-scheduler branch October 21, 2020 23:07
@ghost ghost added this to the Next milestone Oct 21, 2020
@allisonchou allisonchou modified the milestones: Next, 16.9.P2 Nov 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Roslyn Test CompletionInLinkedFiles

4 participants