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

Don't register duplicate CPS subscriptions for same project configura… #1572

Merged
merged 4 commits into from
Feb 17, 2017

Conversation

mavasani
Copy link
Contributor

@mavasani mavasani commented Feb 16, 2017

…tion.

Customer scenario

VS crashes with ArgumentException on opening dotnet core solutions with projects that have additional files.

Bugs this fixes:

Fixes #1563

Workarounds, if any

None.

Risk

Low risk.
We sometimes ended up with duplicate CPS subscriptions for same configured project, causing us to attempt to add duplicate items to the same workspace project in the langauge service and throwing an ArgumentException. Fix is to keep track of project configurations which have already subscribed.

Performance impact

Low

Is this a regression from a previous update?

Yes. Even though we always registered duplicate subscriptions and I can see the same ArgumentException under the debugger on older builds, the exception was getting swallowed up the stack and not crashing VS. My recent PR to move the Language service to JTF causes this code to now execute in a joinable task, and the exception goes unhandled, crashing VS.

Root cause analysis:

Noted above.

How was the bug found?

Dogfooding.

…tion.

We ended up with duplicate subscriptions, causing us to attempt to add duplicate items to the same workspace project in the langauge service and throwing an ArgumentException.
Fix is to keep track of the project configurations for which we have registered subscriptions.

Fixes dotnet#1563
@@ -113,9 +117,6 @@ private Task OnProjectFactoryCompletedAsync()

private async Task OnProjectChangedCoreAsync(IProjectVersionedValue<IProjectSubscriptionUpdate> e, RuleHandlerType handlerType)
{
// TODO: https://github.com/dotnet/roslyn-project-system/issues/353
await _commonServices.ThreadingService.SwitchToUIThread();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved this call inside the HandleAsync as we now execute the core handler callback inside joinable tasks, and that needs to force UI thread anyways.

@mavasani
Copy link
Contributor Author

Tagging @dotnet/project-system for review

if (previousProjectContext != newProjectContext)
{
// Dispose existing subscriptions.
DisposeAndClearSubscriptions();
Copy link
Contributor

Choose a reason for hiding this comment

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

DisposeAndClearSubscriptions [](start = 16, length = 28)

Why is this dispose no longer needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding the email conversation with @lifengl for more context.

From: Manish Vasani 
Sent: Wednesday, February 15, 2017 7:18 PM
To: Lifeng Lu <lifengl@microsoft.com>; Srivatsn Narayanan <srivatsn@microsoft.com>; Jeremy Meng <Jeremy.Meng@microsoft.com>; Jacob Viau <javia@microsoft.com>; Viktor Veis <viveis@microsoft.com>
Cc: David Kean <David.Kean@microsoft.com>
Subject: RE: https://github.com/dotnet/roslyn-project-system/issues/1563

That seems to be indeed the case – I receive one additional update on the disposed subscription, and from then on the updates are only on the newly registered subscription.
Thanks for your help Lifeng.

From: Lifeng Lu 
Sent: Wednesday, February 15, 2017 4:25 PM
To: Manish Vasani <mavasani@microsoft.com>; Srivatsn Narayanan <srivatsn@microsoft.com>; Jeremy Meng <Jeremy.Meng@microsoft.com>; Jacob Viau <javia@microsoft.com>; Viktor Veis <viveis@microsoft.com>
Cc: David Kean <David.Kean@microsoft.com>
Subject: RE: https://github.com/dotnet/roslyn-project-system/issues/1563

Do you only receive a few updates after that, or you will receive new updates forever?

The problem is that dataflow blocks has an internal queue, so even if you remove an subscription, you will still receive data, which has already been added to the queue.
Because your code to handle the dataflow change requires to switch to the UI thread immediately, your handler can be blocked for quite some time, and quite some data will be queued in the action block queue.

From: Manish Vasani 
Sent: Wednesday, February 15, 2017 4:20 PM
To: Lifeng Lu <lifengl@microsoft.com>; Srivatsn Narayanan <srivatsn@microsoft.com>; Jeremy Meng <Jeremy.Meng@microsoft.com>; Jacob Viau <javia@microsoft.com>; Viktor Veis <viveis@microsoft.com>
Cc: David Kean <David.Kean@microsoft.com>
Subject: RE: https://github.com/dotnet/roslyn-project-system/issues/1563

So, my fix is now preventing us from creating duplicate subscriptions for this case.
However, I still have an unanswered CPS question: invoking Dispose on the subscription links doesn’t seem to stop the original subscription handler from receiving CPS updates.
We always dispose all the existing subscriptions here before adding new subscriptions for new set of configured projects, but as I mentioned earlier we still got updates on the original subscription handler.
Am I missing something around how subscription dispose is supposed to work?

To summarize, I was assuming that disposing all existing subscriptions and then registering new ones based on new set of active configured projects would avoid duplicate subscriptions. This is not completely true due to the way dataflow queue works, and this caused duplicate updates on the old and new subscription.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@natidea Could we run into similar issues for NuGet restorer? Due to handle duplicate updates?

Copy link
Contributor

Choose a reason for hiding this comment

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

In NuGetRestorer we ignore updates that do not contain any changes, here: ProjectRestoreInfoBuilder.cs#L33. Also, NuGet's solution restorer is robust to handle multiple restore calls with identical information. I'm not sure about the way we are disposing -- we use ProjectDataSources.SyncLinkTo to create one subscription which is disposed when target framework changes. We can revisit this when we investigate #1503

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for checking. I think regardless, NuGet restorer should be fine handling duplicate updates as you are just kicking off restore when the update happens, you are not building any state based on design time builds.

@srivatsn
Copy link
Contributor

@jinujoseph can you please buddy test this change?

@mavasani
Copy link
Contributor Author

@dotnet/project-system can someone please review the fix? This seems to be causing crash on open solution for large number of customer solutions.

@srivatsn @MattGertz I think we should take this to shiproom today - this is a dogfood blocker.

@MattGertz
Copy link

Agreed; I've been following the thread.

@@ -132,12 +133,10 @@ private Task OnProjectFactoryCompletedAsync()
{
Copy link
Contributor

Choose a reason for hiding this comment

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

By moving SwitchToUIThread from OnProjectChangedCoreAsync into SwitchToUIThread, now UpdateProjectContextAndSubscriptionsAsync() is running on a background thread, so you may run into race conditions that multiple calls happen in the same time. It looks like we don't protect the code to be thread-safe, for example the Hash table _projectConfigurationsWithSubscriptions is not protected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Let me add switching to UI thread in AddSubscriptions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Where is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, saw that now... I didn't get to the latest change.

@@ -340,6 +349,7 @@ private void DisposeAndClearSubscriptions()

_evaluationSubscriptionLinks.Clear();
Copy link
Contributor

Choose a reason for hiding this comment

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

We only remove subscriptions when the project is closed. What happens if one configuration is removed? I think the subscription will hold many configuration related objects in the memory. It is not a high priority for this release, but eventually we need address it too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We also need to think about the fact where user has multi-TFM project, we remove one TF from this list and then add it back. Will everything work fine if we remove the subscription in first step and add it back again later? I'll file a separate issue to track this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Filed #1583

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, that is a potential problem... fine to track it separately.

Copy link
Contributor

@lifengl lifengl left a comment

Choose a reason for hiding this comment

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

Looks good to me.

@mavasani
Copy link
Contributor Author

Thanks a lot Lifeng!

@mavasani mavasani merged commit 90f82d6 into dotnet:master Feb 17, 2017
@mavasani mavasani deleted the DuplicateSubscriptions branch February 17, 2017 01:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants