Skip to content

Remove strong reference to AnalyzerOptions in the analyzer manager ex…#20096

Merged
mavasani merged 2 commits intodotnet:masterfrom
mavasani:FixLeak
Jun 9, 2017
Merged

Remove strong reference to AnalyzerOptions in the analyzer manager ex…#20096
mavasani merged 2 commits intodotnet:masterfrom
mavasani:FixLeak

Conversation

@mavasani
Copy link
Contributor

@mavasani mavasani commented Jun 8, 2017

…ecution context

Fixes #20065

Customer scenario

Performance: We are leaking solution instances in preview workspace (which is created every time we invoke a lightbulb preview). See #20065 (comment) for details.

Bugs this fixes:

Fixes #20065

Workarounds, if any

N/A

Risk

Low, as we are not changing any functionality here. Instead of having a Dictionary<AnalyzerOptions, ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>> with strong reference to AnalyzerOptions, we now use a ConditionalWeakTable<Compilation, Dictionary<AnalyzerOptions, Task<HostCompilationStartAnalysisScope>>>, which has a weak reference and will let go AnalyzerOptions instance once the compilation is GC'ed.

Performance impact

This should improve performance as we are preventing a leak of solutions in preview workspace.

Is this a regression from a previous update?

Yes, we recently refactored the WorkspaceAnalyzerOptions to strongly hold onto the solution snapshot. This was needed to fix an IDE race. This in turn is causing the map in question here to leak these solution instances.

Root cause analysis:

We don't have tests to catch and prevent leaks. We are going to further improve the design in this area when we tackle #2830 in 15.6, so we prevent such regressions in future.

How was the bug found?

Dogfooding.

@mavasani
Copy link
Contributor Author

mavasani commented Jun 8, 2017

Test failure in https://ci.dot.net/job/dotnet_roslyn/job/master/job/windows_debug_vs-integration_prtest/5272/ with output: xunit produced no error output but had exit code 1

@mavasani
Copy link
Contributor Author

mavasani commented Jun 8, 2017

@dotnet-bot retest windows_debug_vs-integration_prtest please

@mavasani
Copy link
Contributor Author

mavasani commented Jun 8, 2017

Tagging @dotnet/roslyn-compiler @dotnet/analyzer-ioperation for review.

@mavasani
Copy link
Contributor Author

mavasani commented Jun 8, 2017

@heejaechang Can you please buddy test this change and double check analyzer execution in OOP with FSA turned on? I can share the locally built VSIX if you need.

@heejaechang
Copy link
Contributor

👍 sure, I will do the buddy testing.

@@ -20,7 +20,7 @@ private sealed class AnalyzerExecutionContext

// This map stores the tasks to compute HostCompilationStartAnalysisScope for per-compilation analyzer actions, i.e. AnalyzerActions registered by analyzer's CompilationStartActions.
// Compilation start actions will get executed once per-each AnalyzerAndOptions as user might want to return different set of custom actions for each compilation/analyzer options.
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if the comment needs an update here.

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 am going to delete a bunch of code around this in dev15.6 branch soon, we are going to get rid of the singleton.

@jasonmalinowski
Copy link
Member

Yes, we recently refactored the WorkspaceAnalyzerOptions to strongly hold onto the solution snapshot. This was needed to fix an IDE race.

I'm still not convinced this is what regressed it. Previously we were holding onto the Workspace, which would have still rooted the Solution anyways.

@mavasani
Copy link
Contributor Author

mavasani commented Jun 8, 2017

I'm still not convinced this is what regressed it. Previously we were holding onto the Workspace, which would have still rooted the Solution anyways.

Probably something else regressed it before that change, but nevertheless, we shouldn't be leaking anymore after this change.

@mavasani
Copy link
Contributor Author

mavasani commented Jun 8, 2017

Tagging @MattGertz for approval

@heejaechang
Copy link
Contributor

confirmed that there is no PreviewWorkspace leaked with this change.

@mavasani
Copy link
Contributor Author

mavasani commented Jun 8, 2017

@heejaechang - Thank you very much!

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

The code change looks good to me.

I would expect this to be testable using a weak reference in the test - does a proper use of these APIs keep the solution alive? Is such a test feasible? Or would it naturally be a flaky test due to nondeterministic behavior of GC?

@gafter
Copy link
Member

gafter commented Jun 8, 2017

(I'm not signing off yet because I'd like to see if this is testable, and we need another sign-off from the compiler team)

@gafter gafter self-assigned this Jun 8, 2017
@gafter gafter added this to the 15.3 milestone Jun 8, 2017
@jasonmalinowski
Copy link
Member

We do have https://github.com/dotnet/roslyn/blob/6a98a959cbd296991411e3e9d124aeeb4827ae1f/src/Test/Utilities/Portable/ObjectReference.cs as a tool you can use for asserting that objects are no longer held. It holds our current "GC until it goes away" pattern.

@heejaechang
Copy link
Contributor

@jasonmalinowski I see you meant this kind of lifetime check test. to explicitly see whether something left alive after certain operation. ya. I guess we could try to add this kind of test.

@natidea
Copy link
Contributor

natidea commented Jun 9, 2017

Director Approved to Merge

@mavasani
Copy link
Contributor Author

mavasani commented Jun 9, 2017

@gafter @jasonmalinowski @heejaechang - Added a unit test

@mavasani
Copy link
Contributor Author

mavasani commented Jun 9, 2017

@dotnet/roslyn-compiler Can I get another review? FYI that this fixes an Urgency-Now bug #20093 which already has director approval to merge.


public void ClearCompilationScopeMap(AnalyzerOptions analyzerOptions, Compilation compilation)
public void ClearCompilationScopeMap(Compilation compilation)
{
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 This lock is now technically unnecessary (ConditionalWeakTable provides synchronization), but I think it would be confusing to some readers to remove 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.

Yes, leaving unchanged for now. I am going to remove a whole bunch of code in this file in dev15.6 soon.

// This map stores the tasks to compute HostCompilationStartAnalysisScope for per-compilation analyzer actions, i.e. AnalyzerActions registered by analyzer's CompilationStartActions.
// Compilation start actions will get executed once per-each AnalyzerAndOptions as user might want to return different set of custom actions for each compilation/analyzer options.
private Dictionary<AnalyzerOptions, ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>> _lazyCompilationScopeMap;
private ConditionalWeakTable<Compilation, Dictionary<AnalyzerOptions, Task<HostCompilationStartAnalysisScope>>> _lazyCompilationScopeCache;
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Is there any reason why this can't be private static readonly? More specifically, is it possible for two different AnalyzerExecutionContext instances to have the same Compilation instance with different maps associated?

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 am going to remove this map in a refactoring in dev15.6 very soon.

Copy link
Member

Choose a reason for hiding this comment

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

Is there a bug tracking this we can reference here?

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 have a PR out: #20109

Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

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

LGTM

ExecuteAnalyzers(previewWorkspace, analyzers);

previewWorkspace.Dispose();
solutionObjectReference.AssertReleased();
Copy link
Member

Choose a reason for hiding this comment

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

Consider asserting that the workspace is also disposed, just to make sure that's covered.

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 am explicitly disposing the workspace in the previous line. I am not sure what your recommendation is over here?

@mavasani
Copy link
Contributor Author

mavasani commented Jun 9, 2017

@gafter Any other feedback?

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jun 9, 2017

Was Neal's feedback about tests addressed?

@mavasani
Copy link
Contributor Author

mavasani commented Jun 9, 2017

@AlekseyTs - yes, I have added a unit test.

@AlekseyTs
Copy link
Contributor

I sign-off on Neal's behalf.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Previews for light bulbs leaks solutions