-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Simplify the computation and caching of 'CompilationWithAnalyzers' for a particular project in the diagnostics layer. #77113
Simplify the computation and caching of 'CompilationWithAnalyzers' for a particular project in the diagnostics layer. #77113
Conversation
@@ -127,84 +128,6 @@ static string GetLanguageSpecificId(string? language, string noLanguageId, strin | |||
language: language); | |||
} | |||
|
|||
public static async Task<CompilationWithAnalyzersPair?> CreateCompilationWithAnalyzersAsync( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to local function in single place it is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i also lifted the 'compilation == null' check below out of it. but it is otherwise unchanged.
/// passed along with the project. As such, we might not be able to use a prior cached value if the set of state | ||
/// sets changes. In that case, a new instance will be created and will be cached for the next caller. | ||
/// </summary> | ||
private static readonly ConditionalWeakTable<Project, CompilationWithAnalyzersPair?> s_projectToCompilationWithAnalyzers = new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed from WeakRef to CWT. that way we can reuse when asking about other projects as well. and, as long as the project is alive we keep the CompPair (instead of dumping on first GC).
{ | ||
private static Task<CompilationWithAnalyzersPair?> CreateCompilationWithAnalyzersAsync(Project project, ImmutableArray<StateSet> stateSets, bool crashOnAnalyzerException, CancellationToken cancellationToken) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i wanted to hide this method. no one should just make the CompPair. They shoudl go through the helper that returns if we have a cached one in the CWT.
private sealed class ProjectAndCompilationWithAnalyzers | ||
{ | ||
public Project Project { get; } | ||
public CompilationWithAnalyzersPair? CompilationWithAnalyzers { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of a weakref to one of these, we have a CWT from a Project to the last CompPair we computed for it.
{ | ||
if (projectAndCompilationWithAnalyzers.CompilationWithAnalyzers == null) | ||
{ | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: i'm virtually certain this is a bug. that's because a prior call with a DIFFERENT set of 'state sets' could have caused this to be computed and cached as 'null', while THIS set of 'state sets' should have produced a legal CompPair. But because of this check we would bail out. I've fixed this in the new code.
return box.Value.diagnosticAnalysisResults; | ||
|
||
// Otherwise, just compute for the state sets we care about. | ||
var compilation = await GetOrCreateCompilationWithAnalyzersAsync(project, stateSets, Owner.AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calls GetOrCreateCompilationWithAnalyzersAsync instead of CreateCompilationXXX. We want this to reuse a cached value if applicable.
static (stateSet, arg) => arg.self.IsCandidateForFullSolutionAnalysis(stateSet.Analyzer, stateSet.IsHostAnalyzer, arg.project), | ||
(self: this, project)); | ||
|
||
var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calls GetOrCreateCompilationWithAnalyzersAsync instead of CreateCompilationXXX. We want this to reuse a cached value if applicable.
CancellationToken cancellationToken) | ||
{ | ||
if (!project.SupportsCompilation) | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lifted this up from CreateCompilationWithAnalyzersAsync
static bool HasAllAnalyzers(ImmutableArray<StateSet> stateSets, CompilationWithAnalyzersPair? compilationWithAnalyzers) | ||
{ | ||
if (compilationWithAnalyzers is null) | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this check is new, but is also a bug fix. we might have stored 'null' before because we decided to not produce a compilation-pair for a particular set of state-sets. taht doesn't mean we should fail from trying to produce a compilation with our current set.
// Make sure the cached pair was computed with at least the same state sets we're asking about. if not, | ||
// recompute and cache with the new state sets. | ||
if (!s_projectToCompilationWithAnalyzers.TryGetValue(project, out var tupleBox) || | ||
!stateSets.IsSubsetOf(tupleBox.Value.stateSets)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to use same subset logic used elsewhere in this component.
s_lastProjectAndCompilationWithAnalyzers.SetTarget(new ProjectAndCompilationWithAnalyzers(project, compilationWithAnalyzers)); | ||
return compilationWithAnalyzers; | ||
|
||
static bool HasAllAnalyzers(IEnumerable<StateSet> stateSets, CompilationWithAnalyzersPair compilationWithAnalyzers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed this. instead of checking that the compWithAnalyzers has all the analyzers we're caring about. we just check that the state sets we're asking about are a subset of the last state sets we computed the CompWithAnalyzers for.
@JoeRobich @dibarbet this is ready for review. |
|
||
// in IDE, we always set concurrentAnalysis == false otherwise, we can get into thread starvation due to | ||
// async being used with synchronous blocking concurrency. | ||
var projectAnalyzerOptions = new CompilationWithAnalyzersOptions( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be worthwhile to only construct the options if we knew we were going to use them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. that would make sense. this logic exists from before. but i can make that change later!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made in 3c5c686
...r/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs
Show resolved
Hide resolved
async Task<CompilationWithAnalyzersPair?> CreateCompilationWithAnalyzersAsync() | ||
{ | ||
var projectAnalyzers = stateSets.SelectAsArray(s => !s.IsHostAnalyzer, s => s.Analyzer); | ||
var hostAnalyzers = stateSets.SelectAsArray(s => s.IsHostAnalyzer, s => s.Analyzer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both these arrays look to just be intermediaries used only once. Could they be removed and the filtered arrays populated something like:
var filteredProjectAnalyzers = stateSets.SelectAsArray(s => !s.IsHostAnalyzer && !s.Analyzer.IsWorkspaceDiagnosticAnalyzer(), s => s.Analyzer);
var filteredHostAnalyzers = stateSets.SelectAsArray(s => s.IsHostAnalyzer && !s.Analyzer.IsWorkspaceDiagnosticAnalyzer(), s => s.Analyzer);
|
||
// Intentionally ignore the result of this. We still want to use the value we computed above, even if | ||
// another thread interleaves and sets a different value. | ||
s_projectToCompilationWithAnalyzers.GetValue(project, _ => tupleBox); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there an AddOrUpdate on CWT?
/// passed along with the project. As such, we might not be able to use a prior cached value if the set of state | ||
/// sets changes. In that case, a new instance will be created and will be cached for the next caller. | ||
/// </summary> | ||
private static readonly ConditionalWeakTable<Project, StrongBox<(ImmutableArray<StateSet> stateSets, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -7,6 +7,7 @@ | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Runtime.CompilerServices; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this needed?
Followup to #77111