diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 13061f5a2c956..271a3b34249bc 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -693,7 +693,7 @@ internal async Task TestOnlyRequiredAnalyzerExecutedDuringDiagnosticComputation( var diagnosticsMapResults = await DiagnosticComputer.GetDiagnosticsAsync( document, project, Checksum.Null, span: null, projectAnalyzerIds: [], analyzerIdsToRequestDiagnostics, AnalysisKind.Semantic, new DiagnosticAnalyzerInfoCache(), workspace.Services, - isExplicit: false, logPerformanceInfo: false, getTelemetryInfo: false, + logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); Assert.False(analyzer2.ReceivedSymbolCallback); @@ -762,7 +762,7 @@ async Task VerifyCallbackSpanAsync(TextSpan? filterSpan) _ = await DiagnosticComputer.GetDiagnosticsAsync( documentToAnalyze, project, Checksum.Null, filterSpan, analyzerIdsToRequestDiagnostics, hostAnalyzerIds: [], analysisKind, new DiagnosticAnalyzerInfoCache(), workspace.Services, - isExplicit: false, logPerformanceInfo: false, getTelemetryInfo: false, + logPerformanceInfo: false, getTelemetryInfo: false, CancellationToken.None); Assert.Equal(filterSpan, analyzer.CallbackFilterSpan); if (kind == FilterSpanTestAnalyzer.AnalysisKind.AdditionalFile) @@ -817,7 +817,7 @@ void M() try { _ = await DiagnosticComputer.GetDiagnosticsAsync(document, project, Checksum.Null, span: null, - projectAnalyzerIds: [], analyzerIds, kind, diagnosticAnalyzerInfoCache, workspace.Services, isExplicit: false, + projectAnalyzerIds: [], analyzerIds, kind, diagnosticAnalyzerInfoCache, workspace.Services, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: analyzer.CancellationToken); throw ExceptionUtilities.Unreachable(); @@ -830,7 +830,7 @@ void M() // Then invoke analysis without cancellation token, and verify non-cancelled diagnostic. var diagnosticsMap = await DiagnosticComputer.GetDiagnosticsAsync(document, project, Checksum.Null, span: null, - projectAnalyzerIds: [], analyzerIds, kind, diagnosticAnalyzerInfoCache, workspace.Services, isExplicit: false, + projectAnalyzerIds: [], analyzerIds, kind, diagnosticAnalyzerInfoCache, workspace.Services, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); var builder = diagnosticsMap.Diagnostics.Single().diagnosticMap; var diagnostic = kind == AnalysisKind.Syntax ? builder.Syntax.Single().Item2.Single() : builder.Semantic.Single().Item2.Single(); diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticArguments.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticArguments.cs index 4108a704423ae..a90abd145ae43 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticArguments.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticArguments.cs @@ -69,13 +69,6 @@ internal sealed class DiagnosticArguments [DataMember(Order = 7)] public ImmutableArray HostAnalyzerIds; - /// - /// Indicates diagnostic computation for an explicit user-invoked request, - /// such as a user-invoked Ctrl + Dot operation to bring up the light bulb. - /// - [DataMember(Order = 8)] - public bool IsExplicit; - public DiagnosticArguments( bool logPerformanceInfo, bool getTelemetryInfo, @@ -84,8 +77,7 @@ public DiagnosticArguments( AnalysisKind? documentAnalysisKind, ProjectId projectId, ImmutableArray projectAnalyzerIds, - ImmutableArray hostAnalyzerIds, - bool isExplicit) + ImmutableArray hostAnalyzerIds) { Debug.Assert(documentId != null || documentSpan == null); Debug.Assert(documentId != null || documentAnalysisKind == null); @@ -101,6 +93,5 @@ public DiagnosticArguments( ProjectId = projectId; ProjectAnalyzerIds = projectAnalyzerIds; HostAnalyzerIds = hostAnalyzerIds; - IsExplicit = isExplicit; } } diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 3ce287befb209..f3bea05e7e868 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -80,7 +80,6 @@ Task> GetDiagnosticsForSpanAsync( TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKind, - bool isExplicit, CancellationToken cancellationToken); } @@ -99,7 +98,8 @@ public static Task> GetDiagnosticsForSpanAsync(th document, range, diagnosticId: null, priorityProvider: new DefaultCodeActionRequestPriorityProvider(), - diagnosticKind, isExplicit: false, cancellationToken); + diagnosticKind, + cancellationToken); /// /// Return up to date diagnostics for the given and parameters for the given . @@ -113,11 +113,10 @@ public static Task> GetDiagnosticsForSpanAsync(th TextDocument document, TextSpan? range, string? diagnosticId, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKind, - bool isExplicit, CancellationToken cancellationToken) { Func? shouldIncludeDiagnostic = diagnosticId != null ? id => id == diagnosticId : null; return service.GetDiagnosticsForSpanAsync(document, range, shouldIncludeDiagnostic, - priorityProvider, diagnosticKind, isExplicit, cancellationToken); + priorityProvider, diagnosticKind, cancellationToken); } } diff --git a/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs b/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs index 7dcdeeaa0baa1..886cfa811f36a 100644 --- a/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs +++ b/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs @@ -203,7 +203,7 @@ private async Task ApplyCodeFixesForSpecificDiagnosticIdAsync( var diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, shouldIncludeDiagnostic: static diagnosticId => !(IDEDiagnosticIdToOptionMappingHelper.IsKnownIDEDiagnosticId(diagnosticId)), priorityProvider: new DefaultCodeActionRequestPriorityProvider(), - DiagnosticKind.All, isExplicit: false, + DiagnosticKind.All, cancellationToken).ConfigureAwait(false); // We don't want code cleanup automatically cleaning suppressed diagnostics. diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs index 73ace12f10c5d..147c4bd50893c 100644 --- a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs +++ b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs @@ -58,7 +58,7 @@ public override async Task> GetDocumentSpanDiagnosticsAs var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForSpanAsync( document, fixAllSpan, shouldIncludeDiagnostic, priorityProvider: new DefaultCodeActionRequestPriorityProvider(), - DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false)); + DiagnosticKind.All, cancellationToken).ConfigureAwait(false)); Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); return await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); } diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs index 592ba4a8b6d8a..00affbc269193 100644 --- a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs @@ -109,7 +109,7 @@ public CodeFixService( { allDiagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priorityProvider), - priorityProvider, DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false); + priorityProvider, DiagnosticKind.All, cancellationToken).ConfigureAwait(false); // NOTE(cyrusn): We do not include suppressed diagnostics here as they are effectively hidden from the // user in the editor. As far as the user is concerned, there is no squiggle for it and no lightbulb @@ -199,7 +199,7 @@ public async IAsyncEnumerable StreamFixesAsync( { diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priorityProvider), - priorityProvider, DiagnosticKind.All, isExplicit: true, cancellationToken).ConfigureAwait(false); + priorityProvider, DiagnosticKind.All, cancellationToken).ConfigureAwait(false); if (!includeSuppressionFixes) diagnostics = diagnostics.WhereAsArray(d => !d.IsSuppressed); } @@ -299,7 +299,7 @@ private static SortedDictionary> ConvertToMap( { diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( document, range, diagnosticId, priorityProvider: new DefaultCodeActionRequestPriorityProvider(), - DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false); + DiagnosticKind.All, cancellationToken).ConfigureAwait(false); // NOTE(cyrusn): We do not include suppressed diagnostics here as they are effectively hidden from the // user in the editor. As far as the user is concerned, there is no squiggle for it and no lightbulb diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index 38eb504562c1a..2857cac3ba513 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -83,7 +83,6 @@ public async Task> GetDiagnosticsForSpanAsync( Func? shouldIncludeDiagnostic, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKinds, - bool isExplicit, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(document.Project.Solution.Workspace); @@ -93,7 +92,7 @@ public async Task> GetDiagnosticsForSpanAsync( priorityProvider ??= new DefaultCodeActionRequestPriorityProvider(); return await analyzer.GetDiagnosticsForSpanAsync( - document, range, shouldIncludeDiagnostic, priorityProvider, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); + document, range, shouldIncludeDiagnostic, priorityProvider, diagnosticKinds, cancellationToken).ConfigureAwait(false); } public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs b/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs index cc2c7c2aac677..3c29bf1c41a52 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs @@ -27,7 +27,6 @@ internal sealed partial class DocumentAnalysisExecutor { private readonly CompilationWithAnalyzersPair? _compilationWithAnalyzers; private readonly InProcOrRemoteHostAnalyzerRunner _diagnosticAnalyzerRunner; - private readonly bool _isExplicit; private readonly bool _logPerformanceInfo; private readonly Action? _onAnalysisException; @@ -41,14 +40,12 @@ public DocumentAnalysisExecutor( DocumentAnalysisScope analysisScope, CompilationWithAnalyzersPair? compilationWithAnalyzers, InProcOrRemoteHostAnalyzerRunner diagnosticAnalyzerRunner, - bool isExplicit, bool logPerformanceInfo, Action? onAnalysisException = null) { AnalysisScope = analysisScope; _compilationWithAnalyzers = compilationWithAnalyzers; _diagnosticAnalyzerRunner = diagnosticAnalyzerRunner; - _isExplicit = isExplicit; _logPerformanceInfo = logPerformanceInfo; _onAnalysisException = onAnalysisException; @@ -66,7 +63,7 @@ public DocumentAnalysisExecutor( public DocumentAnalysisScope AnalysisScope { get; } public DocumentAnalysisExecutor With(DocumentAnalysisScope analysisScope) - => new(analysisScope, _compilationWithAnalyzers, _diagnosticAnalyzerRunner, _isExplicit, _logPerformanceInfo, _onAnalysisException); + => new(analysisScope, _compilationWithAnalyzers, _diagnosticAnalyzerRunner, _logPerformanceInfo, _onAnalysisException); /// /// Return all local diagnostics (syntax, semantic) that belong to given document for the given analyzer by calculating them. @@ -185,8 +182,8 @@ private async Task> GetDiagnosticsForSpanAsync( Func? shouldIncludeDiagnostic, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKind, - bool isExplicit, CancellationToken cancellationToken) { var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); @@ -236,7 +235,7 @@ async Task ComputeDocumentDiagnosticsAsync( var projectAnalyzers = analyzers.WhereAsArray(static (a, info) => !info.IsHostAnalyzer(a), hostAnalyzerInfo); var hostAnalyzers = analyzers.WhereAsArray(static (a, info) => info.IsHostAnalyzer(a), hostAnalyzerInfo); var analysisScope = new DocumentAnalysisScope(document, span, projectAnalyzers, hostAnalyzers, kind); - var executor = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, _diagnosticAnalyzerRunner, isExplicit, logPerformanceInfo); + var executor = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, _diagnosticAnalyzerRunner, logPerformanceInfo); var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); ImmutableDictionary> diagnosticsMap; diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs index f3242e25264b4..7d829586678f2 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs @@ -39,12 +39,10 @@ public InProcOrRemoteHostAnalyzerRunner( public Task> AnalyzeDocumentAsync( DocumentAnalysisScope documentAnalysisScope, CompilationWithAnalyzersPair compilationWithAnalyzers, - bool isExplicit, bool logPerformanceInfo, bool getTelemetryInfo, CancellationToken cancellationToken) - => AnalyzeAsync(documentAnalysisScope, documentAnalysisScope.TextDocument.Project, compilationWithAnalyzers, - isExplicit, logPerformanceInfo, getTelemetryInfo, cancellationToken); + => AnalyzeAsync(documentAnalysisScope, documentAnalysisScope.TextDocument.Project, compilationWithAnalyzers, logPerformanceInfo, getTelemetryInfo, cancellationToken); public Task> AnalyzeProjectAsync( Project project, @@ -52,14 +50,12 @@ public Task AnalyzeAsync(documentAnalysisScope: null, project, compilationWithAnalyzers, - isExplicit: false, logPerformanceInfo, getTelemetryInfo, cancellationToken); + => AnalyzeAsync(documentAnalysisScope: null, project, compilationWithAnalyzers, logPerformanceInfo, getTelemetryInfo, cancellationToken); private async Task> AnalyzeAsync( DocumentAnalysisScope? documentAnalysisScope, Project project, CompilationWithAnalyzersPair compilationWithAnalyzers, - bool isExplicit, bool logPerformanceInfo, bool getTelemetryInfo, CancellationToken cancellationToken) @@ -79,7 +75,7 @@ async Task( project.Solution, diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 9476f840de109..f6e7a31817755 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -312,7 +312,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Sub RequestDiagnosticRefresh() Implements IDiagnosticAnalyzerService.RequestDiagnosticRefresh End Sub - Public Function GetDiagnosticsForSpanAsync(document As TextDocument, range As TextSpan?, shouldIncludeDiagnostic As Func(Of String, Boolean), priority As ICodeActionRequestPriorityProvider, diagnosticKinds As DiagnosticKind, isExplicit As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync + Public Function GetDiagnosticsForSpanAsync(document As TextDocument, range As TextSpan?, shouldIncludeDiagnostic As Func(Of String, Boolean), priority As ICodeActionRequestPriorityProvider, diagnosticKinds As DiagnosticKind, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData) End Function diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs index 1cff6cb98f208..9f3b0470ee343 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs @@ -27,48 +27,23 @@ internal sealed class DiagnosticComputer { /// /// Cache of and a map from analyzer IDs to s - /// for all analyzers for the last project to be analyzed. - /// The instance is shared between all the following document analyses modes for the project: - /// 1. Span-based analysis for active document (lightbulb) - /// 2. Background analysis for active and open documents. - /// - /// NOTE: We do not re-use this cache for project analysis as it leads to significant memory increase in the OOP process. - /// Additionally, we only store the cache entry for the last project to be analyzed instead of maintaining a CWT keyed off - /// each project in the solution, as the CWT does not seem to drop entries until ForceGC happens, leading to significant memory - /// pressure when there are large number of open documents across different projects to be analyzed by background analysis. + /// for all analyzers for the last project to be analyzed. The instance is + /// shared between all the following document analyses modes for the project: + /// + /// Span-based analysis for active document (lightbulb) + /// Background analysis for active and open documents. + /// + /// NOTE: We do not re-use this cache for project analysis as it leads to significant memory increase in the OOP + /// process. Additionally, we only store the cache entry for the last project to be analyzed instead of maintaining + /// a CWT keyed off each project in the solution, as the CWT does not seem to drop entries until ForceGC happens, + /// leading to significant memory pressure when there are large number of open documents across different projects + /// to be analyzed by background analysis. /// private static CompilationWithAnalyzersCacheEntry? s_compilationWithAnalyzersCache = null; - /// - /// Set of high priority diagnostic computation tasks which are currently executing. - /// Any new high priority diagnostic request is added to this set before the core diagnostics - /// compute call is performed, and removed from this list after the computation finishes. - /// Any new normal priority diagnostic request first waits for all the high priority tasks in this set - /// to complete, and moves ahead only after this list becomes empty. - /// - /// - /// Read/write access to this field is guarded by . - /// - private static ImmutableHashSet s_highPriorityComputeTasks = []; - - /// - /// Set of cancellation token sources for normal priority diagnostic computation tasks which are currently executing. - /// For any new normal priority diagnostic request, a new cancellation token source is created and added to this set - /// before the core diagnostics compute call is performed, and removed from this set after the computation finishes. - /// Any new high priority diagnostic request first fires cancellation on all the cancellation token sources in this set - /// to avoid resource contention between normal and high priority requests. - /// Canceled normal priority diagnostic requests are re-attempted from scratch after all the high priority requests complete. - /// - /// - /// Read/write access to this field is guarded by . - /// - private static ImmutableHashSet s_normalPriorityCancellationTokenSources = []; - /// /// Static gate controlling access to following static fields: /// - - /// - - /// - /// private static readonly object s_gate = new(); @@ -116,7 +91,6 @@ public static Task GetDiagnosticsAsync( AnalysisKind? analysisKind, DiagnosticAnalyzerInfoCache analyzerInfoCache, HostWorkspaceServices hostWorkspaceServices, - bool isExplicit, bool logPerformanceInfo, bool getTelemetryInfo, CancellationToken cancellationToken) @@ -144,174 +118,8 @@ public static Task GetDiagnosticsAsync( // We execute explicit, user-invoked diagnostics requests with higher priority compared to implicit requests // from clients such as editor diagnostic tagger to show squiggles, background analysis to populate the error list, etc. var diagnosticsComputer = new DiagnosticComputer(document, project, solutionChecksum, span, analysisKind, analyzerInfoCache, hostWorkspaceServices); - return isExplicit - ? diagnosticsComputer.GetHighPriorityDiagnosticsAsync(projectAnalyzerIds, hostAnalyzerIds, logPerformanceInfo, getTelemetryInfo, cancellationToken) - : diagnosticsComputer.GetNormalPriorityDiagnosticsAsync(projectAnalyzerIds, hostAnalyzerIds, logPerformanceInfo, getTelemetryInfo, cancellationToken); - } - - private async Task GetHighPriorityDiagnosticsAsync( - ImmutableArray projectAnalyzerIds, - ImmutableArray hostAnalyzerIds, - bool logPerformanceInfo, - bool getTelemetryInfo, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Step 1: - // - Create the core 'computeTask' for computing diagnostics. - var computeTask = GetDiagnosticsAsync(projectAnalyzerIds, hostAnalyzerIds, logPerformanceInfo, getTelemetryInfo, cancellationToken); - - // Step 2: - // - Add this computeTask to the set of currently executing high priority tasks. - // This set of high priority tasks is used in 'GetNormalPriorityDiagnosticsAsync' - // method to ensure that any new or cancelled normal priority task waits for all - // the executing high priority tasks before starting its execution. - // - Note that it is critical to do this step prior to Step 3 below to ensure that - // any canceled normal priority tasks in Step 3 do not resume execution prior to - // completion of this high priority computeTask. - lock (s_gate) - { - Debug.Assert(!s_highPriorityComputeTasks.Contains(computeTask)); - s_highPriorityComputeTasks = s_highPriorityComputeTasks.Add(computeTask); - } - - try - { - // Step 3: - // - Force cancellation of all the executing normal priority tasks - // to minimize resource and CPU contention between normal priority tasks - // and the high priority computeTask in Step 4 below. - CancelNormalPriorityTasks(); - - // Step 4: - // - Execute the core 'computeTask' for diagnostic computation. - return await computeTask.ConfigureAwait(false); - } - finally - { - // Step 5: - // - Remove the 'computeTask' from the set of current executing high priority tasks. - lock (s_gate) - { - Debug.Assert(s_highPriorityComputeTasks.Contains(computeTask)); - s_highPriorityComputeTasks = s_highPriorityComputeTasks.Remove(computeTask); - } - } - - static void CancelNormalPriorityTasks() - { - ImmutableHashSet cancellationTokenSources; - lock (s_gate) - { - cancellationTokenSources = s_normalPriorityCancellationTokenSources; - } - - foreach (var cancellationTokenSource in cancellationTokenSources) - { - try - { - cancellationTokenSource.Cancel(); - } - catch (ObjectDisposedException) - { - // CancellationTokenSource might get disposed if the normal priority - // task completes while we were executing this foreach loop. - // Gracefully handle this case and ignore this exception. - } - } - } - } - - private async Task GetNormalPriorityDiagnosticsAsync( - ImmutableArray projectAnalyzerIds, - ImmutableArray hostAnalyzerIds, - bool logPerformanceInfo, - bool getTelemetryInfo, - CancellationToken cancellationToken) - { - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Step 1: - // - Normal priority task must wait for all the executing high priority tasks to complete - // before beginning execution. - await WaitForHighPriorityTasksAsync(cancellationToken).ConfigureAwait(false); - - // Step 2: - // - Create a custom 'cancellationTokenSource' associated with the current normal priority - // request and add it to the tracked set of normal priority cancellation token sources. - // This token source allows normal priority computeTasks to be cancelled when - // a subsequent high priority diagnostic request is received. - using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - lock (s_gate) - { - s_normalPriorityCancellationTokenSources = s_normalPriorityCancellationTokenSources.Add(cancellationTokenSource); - } - - try - { - // Step 3: - // - Execute the core compute task for diagnostic computation. - return await GetDiagnosticsAsync(projectAnalyzerIds, hostAnalyzerIds, logPerformanceInfo, getTelemetryInfo, - cancellationTokenSource.Token).ConfigureAwait(false); - } - catch (OperationCanceledException ex) when (ex.CancellationToken == cancellationTokenSource.Token) - { - // Step 4: - // - Attempt to re-execute this cancelled normal priority task by running the loop again. - continue; - } - finally - { - // Step 5: - // - Remove the 'cancellationTokenSource' for completed or cancelled task. - // For the case where the computeTask was cancelled, we will create a new - // 'cancellationTokenSource' for the retry. - lock (s_gate) - { - Debug.Assert(s_normalPriorityCancellationTokenSources.Contains(cancellationTokenSource)); - s_normalPriorityCancellationTokenSources = s_normalPriorityCancellationTokenSources.Remove(cancellationTokenSource); - } - } - } - - static async Task WaitForHighPriorityTasksAsync(CancellationToken cancellationToken) - { - // We loop continuously until we have an empty high priority task queue. - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); - - ImmutableHashSet highPriorityTasksToAwait; - lock (s_gate) - { - highPriorityTasksToAwait = s_highPriorityComputeTasks; - } - - if (highPriorityTasksToAwait.IsEmpty) - { - return; - } - - // Wait for all the high priority tasks, ignoring all exceptions from it. Loop directly to avoid - // expensive allocations in Task.WhenAll. - foreach (var task in highPriorityTasksToAwait) - { - cancellationToken.ThrowIfCancellationRequested(); - if (task.IsCompleted) - { - // Make sure to yield so continuations of 'task' can make progress. - await TaskScheduler.Default.SwitchTo(alwaysYield: true); - } - else - { - await task.WithCancellation(cancellationToken).NoThrowAwaitable(false); - } - } - } - } + return diagnosticsComputer.GetDiagnosticsAsync( + projectAnalyzerIds, hostAnalyzerIds, logPerformanceInfo, getTelemetryInfo, cancellationToken); } private async Task GetDiagnosticsAsync( diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs index 84341edad9d2a..7e9def0c0449c 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs @@ -63,7 +63,6 @@ public async ValueTask CalculateDiagnosti documentSpan, arguments.ProjectAnalyzerIds, arguments.HostAnalyzerIds, documentAnalysisKind, _analyzerInfoCache, hostWorkspaceServices, - isExplicit: arguments.IsExplicit, logPerformanceInfo: arguments.LogPerformanceInfo, getTelemetryInfo: arguments.GetTelemetryInfo, cancellationToken).ConfigureAwait(false);