Skip to content

Commit

Permalink
Merge pull request #72942 from CyrusNajmabadi/diagnosticQueue
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Apr 9, 2024
2 parents efdb565 + be0760f commit 3397730
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,107 +6,156 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics;

[ExportWorkspaceServiceFactory(typeof(IBuildOnlyDiagnosticsService), ServiceLayer.Default), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class BuildOnlyDiagnosticsServiceFactory() : IWorkspaceServiceFactory
internal sealed class BuildOnlyDiagnosticsServiceFactory(
IAsynchronousOperationListenerProvider asynchronousOperationProvider) : IWorkspaceServiceFactory
{
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
=> new BuildOnlyDiagnosticsService(workspaceServices.Workspace);
=> new BuildOnlyDiagnosticsService(workspaceServices.Workspace, asynchronousOperationProvider.GetListener(FeatureAttribute.Workspace));

private sealed class BuildOnlyDiagnosticsService : IBuildOnlyDiagnosticsService
private sealed class BuildOnlyDiagnosticsService : IBuildOnlyDiagnosticsService, IDisposable
{
private readonly object _gate = new();
private readonly CancellationTokenSource _disposalTokenSource = new();
private readonly AsyncBatchingWorkQueue<WorkspaceChangeEventArgs> _workQueue;

private readonly SemaphoreSlim _gate = new(initialCount: 1);
private readonly Dictionary<DocumentId, ImmutableArray<DiagnosticData>> _documentDiagnostics = [];

public BuildOnlyDiagnosticsService(Workspace workspace)
public BuildOnlyDiagnosticsService(
Workspace workspace,
IAsynchronousOperationListener asyncListener)
{
_workQueue = new AsyncBatchingWorkQueue<WorkspaceChangeEventArgs>(
TimeSpan.Zero,
ProcessWorkQueueAsync,
asyncListener,
_disposalTokenSource.Token);
workspace.WorkspaceChanged += OnWorkspaceChanged;
}

public void Dispose()
=> _disposalTokenSource.Dispose();

private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e)
{
// Keep this switch in sync with the switch in ProcessWorkQueueAsync
switch (e.Kind)
{
case WorkspaceChangeKind.SolutionAdded:
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
ClearAllDiagnostics();
// Cancel existing work as we're going to clear out everything anyways, so no point processing any
// document or project work.
_workQueue.AddWork(e, cancelExistingWork: true);
break;

case WorkspaceChangeKind.ProjectReloaded:
case WorkspaceChangeKind.ProjectRemoved:
ClearDiagnostics(e.OldSolution.GetProject(e.ProjectId));
break;

case WorkspaceChangeKind.DocumentRemoved:
case WorkspaceChangeKind.DocumentReloaded:
case WorkspaceChangeKind.AdditionalDocumentRemoved:
case WorkspaceChangeKind.AdditionalDocumentReloaded:
case WorkspaceChangeKind.AnalyzerConfigDocumentRemoved:
case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded:
ClearDiagnostics(e.DocumentId);
_workQueue.AddWork(e);
break;
}
}

public void AddBuildOnlyDiagnostics(DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics)
private async ValueTask ProcessWorkQueueAsync(ImmutableSegmentedList<WorkspaceChangeEventArgs> list, CancellationToken cancellationToken)
{
foreach (var e in list)
{
// Keep this switch in sync with the switch in OnWorkspaceChanged
switch (e.Kind)
{
case WorkspaceChangeKind.SolutionAdded:
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
await ClearAllDiagnosticsAsync(cancellationToken).ConfigureAwait(false);
break;

case WorkspaceChangeKind.ProjectReloaded:
case WorkspaceChangeKind.ProjectRemoved:
await ClearDiagnosticsAsync(e.OldSolution.GetProject(e.ProjectId), cancellationToken).ConfigureAwait(false);
break;

case WorkspaceChangeKind.DocumentRemoved:
case WorkspaceChangeKind.DocumentReloaded:
case WorkspaceChangeKind.AdditionalDocumentRemoved:
case WorkspaceChangeKind.AdditionalDocumentReloaded:
case WorkspaceChangeKind.AnalyzerConfigDocumentRemoved:
case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded:
await ClearDiagnosticsAsync(e.DocumentId, cancellationToken).ConfigureAwait(false);
break;
}
}
}

public async Task AddBuildOnlyDiagnosticsAsync(DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics, CancellationToken cancellationToken)
{
lock (_gate)
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
if (documentId != null)
_documentDiagnostics[documentId] = diagnostics;
}
}

private void ClearAllDiagnostics()
private async Task ClearAllDiagnosticsAsync(CancellationToken cancellationToken)
{
lock (_gate)
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
_documentDiagnostics.Clear();
}
}

private void ClearDiagnostics(DocumentId? documentId)
private async Task ClearDiagnosticsAsync(DocumentId? documentId, CancellationToken cancellationToken)
{
if (documentId == null)
return;

lock (_gate)
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
_documentDiagnostics.Remove(documentId);
}
}

private void ClearDiagnostics(Project? project)
private async Task ClearDiagnosticsAsync(Project? project, CancellationToken cancellationToken)
{
if (project == null)
return;

lock (_gate)
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
foreach (var documentId in project.DocumentIds)
_documentDiagnostics.Remove(documentId);
}
}

public void ClearBuildOnlyDiagnostics(Project project, DocumentId? documentId)
public Task ClearBuildOnlyDiagnosticsAsync(Project project, DocumentId? documentId, CancellationToken cancellationToken)
{
if (documentId != null)
ClearDiagnostics(documentId);
return ClearDiagnosticsAsync(documentId, cancellationToken);
else
ClearDiagnostics(project);
return ClearDiagnosticsAsync(project, cancellationToken);
}

public ImmutableArray<DiagnosticData> GetBuildOnlyDiagnostics(DocumentId documentId)
public async ValueTask<ImmutableArray<DiagnosticData>> GetBuildOnlyDiagnosticsAsync(DocumentId documentId, CancellationToken cancellationToken)
{
lock (_gate)
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
return _documentDiagnostics.TryGetValue(documentId, out var diagnostics) ? diagnostics : [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;

namespace Microsoft.CodeAnalysis.Diagnostics;
Expand All @@ -13,9 +15,9 @@ namespace Microsoft.CodeAnalysis.Diagnostics;
/// </summary>
internal interface IBuildOnlyDiagnosticsService : IWorkspaceService
{
void AddBuildOnlyDiagnostics(DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics);
Task AddBuildOnlyDiagnosticsAsync(DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics, CancellationToken cancellationToken);

void ClearBuildOnlyDiagnostics(Project project, DocumentId? documentId);
Task ClearBuildOnlyDiagnosticsAsync(Project project, DocumentId? documentId, CancellationToken cancellationToken);

ImmutableArray<DiagnosticData> GetBuildOnlyDiagnostics(DocumentId documentId);
ValueTask<ImmutableArray<DiagnosticData>> GetBuildOnlyDiagnosticsAsync(DocumentId documentId, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ public async Task<FirstFixResult> GetMostSevereFixAsync(
allDiagnostics = allDiagnostics.AddRange(copilotDiagnostics);

var buildOnlyDiagnosticsService = document.Project.Solution.Services.GetRequiredService<IBuildOnlyDiagnosticsService>();
allDiagnostics = allDiagnostics.AddRange(buildOnlyDiagnosticsService.GetBuildOnlyDiagnostics(document.Id));
allDiagnostics = allDiagnostics.AddRange(
await buildOnlyDiagnosticsService.GetBuildOnlyDiagnosticsAsync(document.Id, cancellationToken).ConfigureAwait(false));

var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
var spanToDiagnostics = ConvertToMap(text, allDiagnostics);
Expand Down Expand Up @@ -205,7 +206,7 @@ public async IAsyncEnumerable<CodeFixCollection> StreamFixesAsync(
diagnostics = diagnostics.AddRange(copilotDiagnostics);

var buildOnlyDiagnosticsService = document.Project.Solution.Services.GetRequiredService<IBuildOnlyDiagnosticsService>();
var buildOnlyDiagnostics = buildOnlyDiagnosticsService.GetBuildOnlyDiagnostics(document.Id);
var buildOnlyDiagnostics = await buildOnlyDiagnosticsService.GetBuildOnlyDiagnosticsAsync(document.Id, cancellationToken).ConfigureAwait(false);

if (diagnostics.IsEmpty && buildOnlyDiagnostics.IsEmpty)
yield break;
Expand Down
Loading

0 comments on commit 3397730

Please sign in to comment.