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

Add "Active file" analysis scope for background analysis in the IDE #39699

Merged
merged 21 commits into from
Nov 20, 2019

Conversation

mavasani
Copy link
Contributor

@mavasani mavasani commented Nov 5, 2019

BackgroundAnalysisScope

Replaces the existing "Full Solution Analysis" option, with a background analysis scope to allow users to control the analysis scope for all solution crawler based background analysis (except for Test explorer's unit testing incremental analyzer, which will always execute for full solution).

Fixes #38429. Active file scope minimizes all the background analysis to the active files, saving on CPU consumption from background analysis on remaining files in the solution.

I have also updated up the low virtual memory listener which detects low VM and used to turn off just the full solution analysis to now set the background analysis scope to "Active file" for the current session.

TODOs:

  1. Add unit tests and integration tests for new analysis scope
  2. Update DesignerAttributeIncrementalAnalyzer to work at single file level for active file analysis.

Replaces the existing "Full Solution Analysis" option, with a background analysis scope to allow users to control the analysis scope for all solution crawler based background analysis.

Fixes dotnet#38429. Active file scope minimizes all the background analysis to the active files, saving on CPU consumption from background analysis on remaining files in the solution.

I have also updated up the low virtual memory listener which detects low VM and used to turn off just the full solution analysis to now set the background analysis scope to "Active file" for the current session.
if (incrementalAnalyzer.GetType().FullName == "Microsoft.CodeAnalysis.UnitTesting.SourceBasedTestDiscovery.SourceBasedTestDiscoveryIncrementalAnalyzer")
{
return BackgroundAnalysisScope.FullSolution;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@Cosifne After we merge the change to consume the unit testing IVTs, we should remove the above block of code. Perhaps this can be done as part of the second cleanup PR you were planning for the wrappers...

@CyrusNajmabadi
Copy link
Member

Overall, i like your OP! A few questions:

I have also updated up the low virtual memory listener which detects low VM and used to turn off just the full solution analysis to now set the background analysis scope to "Active file" for the current session.

Is there any notification to the user that this has happened? perhaps a yellow bar stating that low-resource conditions were encountered and some services have been throttled to help out?

Update DesignerAttributeIncrementalAnalyzer to work at single file level for active file analysis.

What doe that mean overall? Say i'm opening a project for the first time and tehre are designable items in it... how does VS learn about this? What's the flow here? (Feel free to provide gif if that will be clearer). Thanks!

(except for Test explorer's unit testing incremental analyzer, which will always execute for full solution).

Out of curiosity, does this at least run OOP?

@@ -170,12 +247,12 @@ private void ReportPendingWorkItemCount()
var reasons = workItem.InvocationReasons;
if (workItem.MustRefresh || reasons.Contains(PredefinedInvocationReasons.SyntaxChanged))
{
await RunAnalyzersAsync(analyzers, document, (a, d, c) => a.AnalyzeSyntaxAsync(d, reasons, c), cancellationToken).ConfigureAwait(false);
await RunAnalyzersAsync(analyzers, document, workItem, (a, d, c) => a.AnalyzeSyntaxAsync(d, reasons, c), cancellationToken).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

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

nit: we can calculate applicable analyzers up front and reuse it for calls to RunAnalyzersAsync

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I thought about it, but then felt it is much cleaner and reliable to keep the computation of applicable analyzers at one place just prior to the core execution. If we see any perf issues, I will follow your suggestion.

{
get
{
lock (_gate)
Copy link
Member

Choose a reason for hiding this comment

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

Is the lock necessary for the read? i.e. are we protecting read from write, or just concurrent reads (which can trigger initialization)?

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 copied most of this from existing code in AbstractPriorityProcessor. I will keep it the same way and be defensive and consistent.

// Now update the task list if there are any changes from the previously computed TODO comments, if any.
// NOTE: We don't check for cancellation here to ensure the task list view and our latest
// computed state are identical.
if (existingData == null || existingData.Items.Length != newData.Items.Length)
Copy link
Member

Choose a reason for hiding this comment

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

Can existingData and newData has different items but same length?

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. However, this hasn't change as part of this PR, I am just refactoring the code in this PR. I will play around a bit with this and see if I can get a repro to drive this change.

Copy link
Member

@genlu genlu 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 to the core logic looks good to me, other than a few minor comments. But I skipped the UI part and didn't pay very close attention to the parts involves old/new options.

@mavasani mavasani added this to the 16.5 milestone Nov 15, 2019
@mavasani
Copy link
Contributor Author

Marking as blocked on testing signoff from the unit testing team.

…tch the error list terms in its scope filter combo box

`Active file` -> `Current Document`
`Open files and projects -> `Open Documents and Projects`
`Full Solution` -> `Entire Solution`
@heejaechang
Copy link
Contributor

I didn't read whole PR so it might be already handled. just curious since it looks like it reuse options for diagnostics to general background work..

so when user move to different tab (editor), do you enqueue reanalysis to old doc to remove errors? or you just leave them as they are?

asking since for "open files", when user close file, we explicitly remove errors for that file. (except errors from build)

it was done that way since, at the beginning, option said "open files only" and some people wanted things to work literally as option said. so we actively removed errors that don't belong to open files when that option is on.

now, we re-worded the option to describe scope in general sense rather than what it does literally.

so I am +1 on not touching errors already reported (even for open files case), but want to let you know the implication of that.

if user fixes a semantic data, (unknown name "A"), you open new file, create new type "A", the error - unknown name "A" in other file, will not go away until user switch back to that file.

my opinion is that is fine since that is the behavior of "build error" and with "active file or open file" scope, one is basically mixing "live and build" error behavior and get "build error" behavior for out of scope errors?

@heejaechang
Copy link
Contributor

another approach is putting high priority on removing errors but not on adding errors.

basically, always consider any file that has errors as [files to analyze], and always analyze those when snapshot changed. that will make errors go away without users opening a file, but it won't make errors show up unless it is opened or active.

doesn't matter for full solution analysis. it will show up at some point.

@mavasani
Copy link
Contributor Author

asking since for "open files", when user close file, we explicitly remove errors for that file. (except errors from build)

I tried to repro this on latest 16.5 builds (prior to my change) and this behavior seems to have changed. Closing the files does not remove its diagnostics from the error list. Lets see if anyone complains about it, and if so we can first try to ask them to change their error list scope combo box to open files. If they still disagree, we can fix it to get the old behavior back.

so when user move to different tab (editor), do you enqueue reanalysis to old doc to remove errors? or you just leave them as they are?

Yes, I do enqueue a work item with "OnDocumentClosed" invocation reasons for previous active document, but that does not seem to be removing the diagnostics for that document. I presume it will also start working if we fix the open files case above.

if user fixes a semantic data, (unknown name "A"), you open new file, create new type "A", the error - unknown name "A" in other file, will not go away until user switch back to that file.

Yes, that is true for active file scope, and confirms the reduced background analysis scope. I think that is acceptable behavior for this scope.

basically, always consider any file that has errors as [files to analyze], and always analyze those when snapshot changed. that will make errors go away without users opening a file, but it won't make errors show up unless it is opened or active.

That is a good suggestion. I will wait for some more customer feedback on whether stale diagnostics in non-active file bother folks. I am also thinking on programmatically syncing the error list scope combo box when user switches their background scope in Tools Option. That might avoid us to add any additional removal logic.

…cted background analysis scope in Tools Options."

This reverts commit be5bd27.
… test team has moved to IUnitTestingIncrementalAnalyzer
@mavasani mavasani removed the Blocked label Nov 20, 2019
@mavasani
Copy link
Contributor Author

Removing the "Blocked" label as @shyamnamboodiripad verified the test explorer functionality is unchanged on this branch. Thanks Shyam!

Copy link
Contributor

@shyamnamboodiripad shyamnamboodiripad left a comment

Choose a reason for hiding this comment

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

I've verified that unit test discovery is not impacted by this change. Thanks for addressing all the concerns around unit test discovery!

@mavasani mavasani closed this Nov 20, 2019
@mavasani mavasani reopened this Nov 20, 2019
public static BackgroundAnalysisScope GetOverriddenBackgroundAnalysisScope(this IIncrementalAnalyzer incrementalAnalyzer, OptionSet options, BackgroundAnalysisScope defaultBackgroundAnalysisScope)
{
// Unit testing analyzer has special semantics for analysis scope.
if (incrementalAnalyzer is UnitTestingIncrementalAnalyzer unitTestingAnalyzer)
Copy link
Contributor

Choose a reason for hiding this comment

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

@mavasani As I'd mentioned offline, I think it would be better if this were controlled in a way that does not require special casing the unit testing analyzer.

One thought is that perhaps a setting could be added via IncrementalAnalyzerProviderMetadata (similar to HighPriorityForActiveFile). The metadata can also be provided via MEF if required (ExportIncrementalAnalyzerProviderAttribute). However, the unit testing analyzer is not exposed via MEF - so if you wanted to limit this to just the unit testing analyzer, you could make the opt out setting only available to analyzers that register via ISolutionCrawlerRegistrationService and supply IncrementalAnalyzerProviderMetadata...

Not urgent - but hopefully we can look into this at some point in the future...

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 @shyamnamboodiripad. I will try to get to this in a follow-up PR.

@mavasani mavasani merged commit 6ad7923 into dotnet:master Nov 20, 2019
@mavasani mavasani deleted the ActiveFileAnalysisScope branch November 20, 2019 21:05
mavasani added a commit to mavasani/roslyn that referenced this pull request Nov 22, 2019
…ak for TS and F#

Recent PR dotnet#39699 introduced a breaking API and functionality change for TypeScript and F# as they are using our internal option ClosedFileDiagnostics for controlling their full solution analysis experience. This PR restores the ClosedFileDiagnostics option and functionality for non C#/VB languages. Additionally, the newly added option SolutionCrawlerOptions.BackgroundAnalysisScope has been made a per-language option so that all languages can have different UI/setting for this option.
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.

Restrict background analysis scope to reduce CPU consumption in live analysis
5 participants