Skip to content
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

Migrate to Microsoft.VisualStudio.Threading #28781

Merged
merged 20 commits into from
Aug 13, 2018

Conversation

sharwell
Copy link
Member

@sharwell sharwell commented Jul 23, 2018

This change implements the first step towards Roslyn working in applications using vs-threading with reduced risk of concurrency bugs. Recommend reviewing by commit after looking at the full list to get an idea for the overall direction.

Most of the work in this change involves internal propagation of the IThreadingContext instance, which provides access to the JoinableTaskFactory used in Roslyn code.

Following this pull request, several diagnostics remain disabled.

  • VSTHRD002 (Avoid problematic synchronous waits): Many of the violations reported by this can be fixed by a code fix, but the code fix is a bit buggy and I'm waiting for Preserve assertion when fixing VSTHRD103 microsoft/vs-threading#338 so it's easier to prepare the fixes.
  • VSTHRD103 (Call async methods when in an async method): This will be sent as a follow-up pull request. The analysis will be improved by Treat 'Synchronously' as a suffix for synchronous methods microsoft/vs-threading#331, but I didn't see any reason why it would block our use of the analyzer.
  • VSTHRD110 (Observe result of async calls): We need to establish the "Roslyn desired practice for FileAndForget" before enabling this diagnostic.
  • VSTHRD200 (Use "Async" suffix for async methods): Will send a follow-up PR to enable this and fix all cases that aren't exposed (public API and/or IVT). It affected too many files to include here.
  • VSTHRD010 (Invoke single-threaded types on Main thread): This diagnostic is enabled, but its accuracy relies heavily on configuration files provided separately. The configuration for VSSDK types ships with the VSSDK analyzers, which are not yet enabled for the full Roslyn build.

Known follow-up issues (GitHub issues will be linked here once they are available):

Customer scenario

This is an internal code change that allows dotnet/roslyn to be more easily incorporated into applications that involve user interfaces and leverage the Microsoft.VisualStudio.Threading library for deadlock and reentrancy mitigation and overall best practices.

Bugs this fixes

N/A

Workarounds, if any

Fixing bugs as they occur.

Risk

This change has moderate risk, but I believe it's relatively low risk considering its scope since existing behaviors were preserved where possible. The primary points where I see risk are the following:

  • Not all portions of code were updated to follow the Three threading rules, and incremental adoption of vs-threading runs a risk of deadlocks when code does not follow these rules.
  • While the documented precondition of WaitAndGetResult was weakened, the debug assertion it contained was strengthened substantially. This code does not affect release builds, but despite the work in this pull request it may continue to affect debug pull request builds.

Performance impact

Performance should not change substantially as a direct result of this pull request. Over time, performance should improve by allowing more code to use asynchronous operations and more efficient task continuations.

Is this a regression from a previous update?

N/A

Root cause analysis

N/A

How was the bug found?

N/A

Test documentation updated?

N/A

@sharwell sharwell added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Jul 23, 2018
@sharwell sharwell requested a review from a team as a code owner July 23, 2018 16:57
@jasonmalinowski
Copy link
Member

@sharwell Just to note, #27027 is also moving us to some of these packages as well, with a ruleset file. Just so you're aware.

@sharwell sharwell requested review from a team as code owners July 23, 2018 18:58
@@ -149,7 +149,7 @@ private void StartSearch(INavigateToCallback callback, string searchValue, IImmu
kinds,
_cancellationTokenSource.Token);

searcher.Search();
_ = searcher.SearchAsync();
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't this have an effect of silencing all exceptions?

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ It's an intermediate step. VSTHRD110 deals with observing the result of asynchronous operations.

Copy link
Member

Choose a reason for hiding this comment

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

@sharwell intermediate step still needs fixing further?

Copy link
Member Author

Choose a reason for hiding this comment

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

@jasonmalinowski Covered under the VSTHRD110 follow-up issue.

@sharwell sharwell force-pushed the vs-threading-analyzers branch 4 times, most recently from 4e99053 to fb1e11b Compare July 25, 2018 20:59
@sharwell sharwell force-pushed the vs-threading-analyzers branch from fb1e11b to dc48c9b Compare July 25, 2018 21:39
@sharwell sharwell force-pushed the vs-threading-analyzers branch from 2a93c17 to 059cd2f Compare July 26, 2018 23:59
@sharwell sharwell force-pushed the vs-threading-analyzers branch 3 times, most recently from b0cdbbc to 71c6f80 Compare July 28, 2018 15:55
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();

This comment was marked as resolved.

{
await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();

This comment was marked as resolved.

allRenameLocationsTask.SafeContinueWithFromAsync(
async t =>
{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_cancellationTokenSource.Token);

This comment was marked as resolved.

Dispatcher.CurrentDispatcher.BeginInvoke(propagateEditAction, DispatcherPriority.Send, null);
ThreadingContext.JoinableTaskFactory.WithPriority(Dispatcher.CurrentDispatcher, DispatcherPriority.Send).RunAsync(async () =>
{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();

This comment was marked as resolved.

.SafeContinueWithFromAsync(
async t =>
{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_conflictResolutionTaskCancellationSource.Token);

This comment was marked as resolved.

{
AssertIsForeground();
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_cancellationToken);

This comment was marked as resolved.

{
AssertIsForeground();
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_cancellationTokenSource.Token);

This comment was marked as resolved.

@@ -144,7 +143,13 @@ public void RemoveProject(ProjectId projectId)

// CPS projects do not support designer attributes. So we just skip these projects entirely.
var vsWorkspace = project.Solution.Workspace as VisualStudioWorkspaceImpl;
var cps = await Task.Factory.StartNew(() => vsWorkspace?.IsCPSProject(project) == true, cancellationToken, TaskCreationOptions.None, this.ForegroundTaskScheduler).ConfigureAwait(false);

await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

This comment was marked as resolved.

.ContinueWith(
async _ =>
{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

This comment was marked as resolved.

{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();

This comment was marked as resolved.

@sharwell sharwell force-pushed the vs-threading-analyzers branch 2 times, most recently from 6401c77 to c302b71 Compare July 28, 2018 20:59
@sharwell sharwell force-pushed the vs-threading-analyzers branch from c302b71 to 23c08dd Compare July 28, 2018 23:38
// since execution is now technically asynchronous
// only execute action if project is not disconnected and currently being tracked.
if (!_disconnected && this.ProjectTracker.ContainsProject(this))
{
action();
}
},
ForegroundTaskScheduler);
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
Copy link
Member

Choose a reason for hiding this comment

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

different options than usual. intentional?

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ Situation is most similar to #28781 (comment), but has a dedicated follow-up item due to #28781 (comment)

@CyrusNajmabadi
Copy link
Member

My brain really hurts.

@jasonmalinowski
Copy link
Member

My brain really hurts.

And this is the easy change. 😄

@sharwell
Copy link
Member Author

sharwell commented Aug 8, 2018

@heejaechang @jaredpar @jasonmalinowski @CyrusNajmabadi @DustinCampbell I replied to the easy items, and will work on making requested changes and finishing my replies tomorrow. Thank you for the detailed reviews. 🏅

* Remove unused test constructors in QuickInfoCommandHandlerAndSourceProvider
* Document `IThreadingContext`, `ThreadingContext`, and `DenyExecutionSynchronizationContext`
* Ensure `DenyExecutionSynchronizationContext.ThrowIfSwitchOccurred` gets called during test cleanup
* Clean up project layering by moving `StubVsEditorAdaptersFactoryService`
@sharwell sharwell force-pushed the vs-threading-analyzers branch from 9d8b071 to bb01d0e Compare August 8, 2018 15:40
Copy link
Member

@jaredpar jaredpar left a comment

Choose a reason for hiding this comment

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

The change looks good. I'd probably wait for one more IDE sign off though as I'm a bit out of my depth in a few places.

My biggest complaint is the lack of ConfigureAwait in places. I get that we cant add it because it's custom awaitable structs. But I feel like the readibility goes down a bit. Perhaps once we get a bit more used to the MS.VS.Threading this will go away.

Copy link
Member

@DustinCampbell DustinCampbell left a comment

Choose a reason for hiding this comment

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

Looks pretty good. I'll be interested in the follow ups that come later.

Note that this will almost certainly break insertions due to components that have IVT.

@@ -35,7 +36,7 @@ public async Task TestTagsChangedForEntireFile()
workspace.GetService<IForegroundNotificationService>(),
AsynchronousOperationListenerProvider.NullListener,
null,
new SyntacticClassificationTaggerProvider(null, null, null));
new SyntacticClassificationTaggerProvider(workspace.ExportProvider.GetExportedValue<IThreadingContext>(), null, null, null));
Copy link
Member

Choose a reason for hiding this comment

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

nit: would be nice to name the null arguments.

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ #29182

@@ -34,6 +35,7 @@ private IEnumerable<T> Enumerable<T>(params T[] array)
{
var view = new Mock<ITextView>();
var producer = new BraceHighlightingViewTaggerProvider(
workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
workspace.GetService<IBraceMatchingService>(),
Copy link
Member

Choose a reason for hiding this comment

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

TestWorkspace.GetService<TServiceInterface> is just a shortcut to call ExportProvider.GetExportedValue. Should we call one or the other consistently?

Copy link
Member Author

@sharwell sharwell Aug 9, 2018

Choose a reason for hiding this comment

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

➡️ 😦 I certainly should have used workspace.GetService<IThreadingContext>() here for consistency. I'll file a follow-up item related to the confusing pattern(s) we see today with these methods.

_cancellationTokenSource.Token,
TaskContinuationOptions.OnlyOnRanToCompletion,
ForegroundTaskScheduler).CompletesAsyncOperation(asyncToken);
TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously,
Copy link
Member

Choose a reason for hiding this comment

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

It'd be good if this were documented somewhere other than a comment buried in this PR (which has already "unicorned" on me twice! 😄). Since two very smart people have already asked about this pattern here, it'll almost certainly be stumbling block to anyone who happens upon the code. Maybe just add a comment in the code where the pattern is used? E.g. "ExecuteSynchronously is passed to avoid a situation where the continuation to be scheduled on a background thread before immediately switching to the main thread."

_cancellationTokenSource.Token,
TaskContinuationOptions.OnlyOnRanToCompletion,
ForegroundTaskScheduler).CompletesAsyncOperation(asyncToken);
TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously,
Copy link
Member

Choose a reason for hiding this comment

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

Or, maybe add a helper method with a good name to handle this situation rather than passing the same flags everytime? The exact same pattern is used below.

@@ -24,7 +24,8 @@ internal partial class QuickInfoPresenter : ForegroundThreadAffinitizedObject, I
private readonly DeferredContentFrameworkElementFactory _elementFactory;

[ImportingConstructor]
public QuickInfoPresenter(IQuickInfoBroker quickInfoBroker, DeferredContentFrameworkElementFactory elementFactory)
public QuickInfoPresenter(IThreadingContext threadingContext, IQuickInfoBroker quickInfoBroker, DeferredContentFrameworkElementFactory elementFactory)
Copy link
Member

Choose a reason for hiding this comment

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

Sounds good. Is there an issue tracking future work to update old code?

@@ -36,7 +34,8 @@ internal abstract class AbstractSnippetCommandHandler :

public string DisplayName => FeaturesResources.Snippets;

public AbstractSnippetCommandHandler(IVsEditorAdaptersFactoryService editorAdaptersFactoryService, SVsServiceProvider serviceProvider)
public AbstractSnippetCommandHandler(IThreadingContext threadingContext, IVsEditorAdaptersFactoryService editorAdaptersFactoryService, SVsServiceProvider serviceProvider)
Copy link
Member

Choose a reason for hiding this comment

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

minor: Shouldn't this constructor be protected?

@@ -46,7 +44,8 @@ internal abstract class AbstractSnippetExpansionClient : ForegroundThreadAffinit

internal IVsExpansionSession ExpansionSession;

public AbstractSnippetExpansionClient(Guid languageServiceGuid, ITextView textView, ITextBuffer subjectBuffer, IVsEditorAdaptersFactoryService editorAdaptersFactoryService)
public AbstractSnippetExpansionClient(IThreadingContext threadingContext, Guid languageServiceGuid, ITextView textView, ITextBuffer subjectBuffer, IVsEditorAdaptersFactoryService editorAdaptersFactoryService)
Copy link
Member

Choose a reason for hiding this comment

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

minor: protected constructor?

Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -43,9 +43,11 @@ internal abstract class AbstractSnippetInfoService : ForegroundThreadAffinitized
private readonly IAsynchronousOperationListener _waiter;

public AbstractSnippetInfoService(
Copy link
Member

Choose a reason for hiding this comment

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

minor: should this constructor on an abstract class be protected?

Copy link
Member Author

@sharwell sharwell Aug 9, 2018

Choose a reason for hiding this comment

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

➡️ Also covered by analyzer limitation

Public Sub New(startActiveSession As Boolean)
MyBase.New(Nothing, Nothing, Nothing, Nothing)
Public Sub New(threadingContext As IThreadingContext, startActiveSession As Boolean)
MyBase.New(threadingContext, Nothing, Nothing, Nothing, Nothing)
Copy link
Member

Choose a reason for hiding this comment

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

minor: Named arguments for the trailing Nothing literals would be nice here.

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ I would fix this if I was making other changes, but for now I'll consider it covered by #19186

@@ -3252,7 +3252,7 @@ public async Task TestAddRemoveMetadataReference_GAC()

[ConditionalFact(typeof(VisualStudioMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
public async void TestAddRemoveMetadataReference_ReferenceAssembly()
public async Task TestAddRemoveMetadataReference_ReferenceAssembly()
Copy link
Member

Choose a reason for hiding this comment

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

good catch! I missed that one.

@sharwell
Copy link
Member Author

@dotnet/roslyn-infrastructure windows_debug_vs-integration_prtest is failing, but not due to this pull request. Merging and will file follow-up issues first thing tomorrow and start a deeper investigation into the failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants