From 37b2fb0d528e8d52674c562b5d3bb67dcce8bf41 Mon Sep 17 00:00:00 2001 From: Tomas Ekeli Date: Thu, 2 Dec 2021 17:53:14 +0100 Subject: [PATCH 01/10] run analyzers on multiple threads if allowed to there is now a new option in the roslyn-extensions-option: ThreadsToUseForAnalyzers - which defaults to half the number of processors on the machine. there are two workers started, one for background and one for foreground. so this *might* actually use all the cores? in my usage it seems not to. --- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 27 +++++++++++++++---- .../Options/RoslynExtensionsOptions.cs | 3 +++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 1c4f9954b5..52329d8c32 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -123,16 +123,33 @@ private async Task Worker(AnalyzerWorkType workType) .GroupBy(x => x.projectId, x => x.documentId) .ToImmutableArray(); + var analyzerTasks = new List(); + var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.ThreadsToUseForAnalyzers); foreach (var projectGroup in currentWorkGroupedByProjects) { var projectPath = solution.GetProject(projectGroup.Key).FilePath; - EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Started); - - await AnalyzeAndReport(solution, projectGroup); - - EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Ready); + await throttler.WaitAsync(); + analyzerTasks.Add( + Task.Run(async () => + { + try + { + EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Started); + + await AnalyzeAndReport(solution, projectGroup); + + EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Ready); + } + finally + { + throttler.Release(); + } + } + ) + ); } + await Task.WhenAll(analyzerTasks); _workQueue.WorkComplete(workType); diff --git a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs index a35e49bd02..47b56e6d89 100644 --- a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs +++ b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs @@ -12,6 +12,9 @@ public class RoslynExtensionsOptions : OmniSharpExtensionsOptions public bool EnableImportCompletion { get; set; } public bool EnableAsyncCompletion { get; set; } public int DocumentAnalysisTimeoutMs { get; set; } = 10 * 1000; + public int ThreadsToUseForAnalyzers { get; set; } = Environment.ProcessorCount == 1 + ? 1 + : Environment.ProcessorCount / 2; } public class OmniSharpExtensionsOptions From f44591540014ebfde95c324770e5d53c7ab0109f Mon Sep 17 00:00:00 2001 From: Tomas Ekeli Date: Fri, 3 Dec 2021 08:09:16 +0100 Subject: [PATCH 02/10] Update src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs Co-authored-by: David Zidar <381720+DavidZidar@users.noreply.github.com> --- src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs index 47b56e6d89..f89a535be8 100644 --- a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs +++ b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs @@ -12,9 +12,7 @@ public class RoslynExtensionsOptions : OmniSharpExtensionsOptions public bool EnableImportCompletion { get; set; } public bool EnableAsyncCompletion { get; set; } public int DocumentAnalysisTimeoutMs { get; set; } = 10 * 1000; - public int ThreadsToUseForAnalyzers { get; set; } = Environment.ProcessorCount == 1 - ? 1 - : Environment.ProcessorCount / 2; + public int AnalyzerThreadCount { get; set; } = Math.Max(1, Environment.ProcessorCount / 2); } public class OmniSharpExtensionsOptions From fbfd7c0612f6a0285afa5e68462d1fb4c528716d Mon Sep 17 00:00:00 2001 From: Tomas Ekeli Date: Fri, 3 Dec 2021 09:28:30 +0100 Subject: [PATCH 03/10] use the new name of the option --- .../Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 52329d8c32..c745732b98 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -124,7 +124,7 @@ private async Task Worker(AnalyzerWorkType workType) .ToImmutableArray(); var analyzerTasks = new List(); - var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.ThreadsToUseForAnalyzers); + var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.AnalyzerThreadCount); foreach (var projectGroup in currentWorkGroupedByProjects) { var projectPath = solution.GetProject(projectGroup.Key).FilePath; From cb3a4fc9d5e496860128c8c1db09bfb557ea0752 Mon Sep 17 00:00:00 2001 From: Tomas Ekeli Date: Fri, 3 Dec 2021 09:30:03 +0100 Subject: [PATCH 04/10] parallelize the csharp diagnostic worker same logic as for the diagnostic worker with analyzers. --- .../Diagnostics/CSharpDiagnosticWorker.cs | 26 ++++++++++++++++--- .../CsharpDiagnosticWorkerComposer.cs | 4 ++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 87c0368dac..9a5f9a73d5 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging; using OmniSharp.Helpers; using OmniSharp.Models.Diagnostics; +using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics @@ -22,14 +23,16 @@ public class CSharpDiagnosticWorker : ICsDiagnosticWorker, IDisposable private readonly ILogger _logger; private readonly OmniSharpWorkspace _workspace; private readonly DiagnosticEventForwarder _forwarder; + private readonly OmniSharpOptions _options; private readonly IObserver _openDocuments; private readonly IDisposable _disposable; - public CSharpDiagnosticWorker(OmniSharpWorkspace workspace, DiagnosticEventForwarder forwarder, ILoggerFactory loggerFactory) + public CSharpDiagnosticWorker(OmniSharpWorkspace workspace, DiagnosticEventForwarder forwarder, ILoggerFactory loggerFactory, OmniSharpOptions options) { _workspace = workspace; _forwarder = forwarder; _logger = loggerFactory.CreateLogger(); + _options = options; var openDocumentsSubject = new Subject(); _openDocuments = openDocumentsSubject; @@ -146,15 +149,32 @@ public async Task> GetDiagnostics(ImmutableA .Select(docPath => _workspace.GetDocumentsFromFullProjectModelAsync(docPath))) ).SelectMany(s => s); + var diagnosticTasks = new List(); + var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.AnalyzerThreadCount); foreach (var document in documents) { if (document?.Project?.Name == null) continue; var projectName = document.Project.Name; - var diagnostics = await GetDiagnosticsForDocument(document, projectName); - results.Add(new DocumentDiagnostics(document.Id, document.FilePath, document.Project.Id, document.Project.Name, diagnostics)); + await throttler.WaitAsync(); + diagnosticTasks.Add( + Task.Run(async () => + { + try + { + var diagnostics = await GetDiagnosticsForDocument(document, projectName); + results.Add(new DocumentDiagnostics(document.Id, document.FilePath, document.Project.Id, document.Project.Name, diagnostics)); + } + finally + { + throttler.Release(); + } + } + ) + ); } + await Task.WhenAll(diagnosticTasks); return results.ToImmutableArray(); } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs index 51cba8358f..a459a203b1 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs @@ -22,6 +22,7 @@ public class CsharpDiagnosticWorkerComposer : ICsDiagnosticWorker, IDisposable private readonly IEnumerable _providers; private readonly ILoggerFactory _loggerFactory; private readonly DiagnosticEventForwarder _forwarder; + private readonly IOptionsMonitor _options; private ICsDiagnosticWorker _implementation; private readonly IDisposable _onChange; @@ -37,6 +38,7 @@ public CsharpDiagnosticWorkerComposer( _providers = providers; _loggerFactory = loggerFactory; _forwarder = forwarder; + _options = options; _onChange = options.OnChange(UpdateImplementation); UpdateImplementation(options.CurrentValue); } @@ -54,7 +56,7 @@ private void UpdateImplementation(OmniSharpOptions options) } else if (!options.RoslynExtensionsOptions.EnableAnalyzersSupport && (firstRun || _implementation is CSharpDiagnosticWorkerWithAnalyzers)) { - var old = Interlocked.Exchange(ref _implementation, new CSharpDiagnosticWorker(_workspace, _forwarder, _loggerFactory)); + var old = Interlocked.Exchange(ref _implementation, new CSharpDiagnosticWorker(_workspace, _forwarder, _loggerFactory, _options.CurrentValue)); if (old is IDisposable disposable) { disposable.Dispose(); From a7c8c0472daed756891d5b13bdeaa5bc5c7445e3 Mon Sep 17 00:00:00 2001 From: Tomas Ekeli Date: Fri, 3 Dec 2021 09:32:22 +0100 Subject: [PATCH 05/10] rename option since it's used for diagnostic with and without analyzers the name should reflect this --- .../Workers/Diagnostics/CSharpDiagnosticWorker.cs | 2 +- .../Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 9a5f9a73d5..0bab751d15 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -150,7 +150,7 @@ public async Task> GetDiagnostics(ImmutableA ).SelectMany(s => s); var diagnosticTasks = new List(); - var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.AnalyzerThreadCount); + var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.DiagnosticWorkersThreadCount); foreach (var document in documents) { if (document?.Project?.Name == null) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index c745732b98..c8732ee85f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -124,7 +124,7 @@ private async Task Worker(AnalyzerWorkType workType) .ToImmutableArray(); var analyzerTasks = new List(); - var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.AnalyzerThreadCount); + var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.DiagnosticWorkersThreadCount); foreach (var projectGroup in currentWorkGroupedByProjects) { var projectPath = solution.GetProject(projectGroup.Key).FilePath; diff --git a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs index f89a535be8..fa66a6d89a 100644 --- a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs +++ b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs @@ -12,7 +12,7 @@ public class RoslynExtensionsOptions : OmniSharpExtensionsOptions public bool EnableImportCompletion { get; set; } public bool EnableAsyncCompletion { get; set; } public int DocumentAnalysisTimeoutMs { get; set; } = 10 * 1000; - public int AnalyzerThreadCount { get; set; } = Math.Max(1, Environment.ProcessorCount / 2); + public int DiagnosticWorkersThreadCount { get; set; } = Math.Max(1, Environment.ProcessorCount / 2); } public class OmniSharpExtensionsOptions From 145a90434aa447e01e435445d373b890846330f4 Mon Sep 17 00:00:00 2001 From: Tomas Ekeli Date: Fri, 3 Dec 2021 17:28:16 +0100 Subject: [PATCH 06/10] set default timeout wait time for analysis to 30s --- src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs index fa66a6d89a..09d3a1e0b0 100644 --- a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs +++ b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs @@ -11,7 +11,7 @@ public class RoslynExtensionsOptions : OmniSharpExtensionsOptions public bool EnableAnalyzersSupport { get; set; } public bool EnableImportCompletion { get; set; } public bool EnableAsyncCompletion { get; set; } - public int DocumentAnalysisTimeoutMs { get; set; } = 10 * 1000; + public int DocumentAnalysisTimeoutMs { get; set; } = 30 * 1000; public int DiagnosticWorkersThreadCount { get; set; } = Math.Max(1, Environment.ProcessorCount / 2); } From 01010e5f515a61e1006e5ac7ff009e1a35a50c87 Mon Sep 17 00:00:00 2001 From: Tomas Ekeli Date: Mon, 6 Dec 2021 23:52:03 +0100 Subject: [PATCH 07/10] really fix the build-error i should never try to do this stuff from github --- .../Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 6787330c54..9e7fd25fbb 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -138,7 +138,7 @@ private async Task Worker(AnalyzerWorkType workType) { EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Started); - await AnalyzeAndReport(solution, projectGroup); + await AnalyzeProject(solution, projectGroup); EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Ready); } From 92e1769f7c78cef1cd8ecb0666b19ea1680fab6b Mon Sep 17 00:00:00 2001 From: Daniel Rosenberg Date: Sun, 19 Dec 2021 02:40:53 +0100 Subject: [PATCH 08/10] Improves concurrent execution of background Roslys analyzers - Parallelize at the document level instead of only at the project level (so user benefits also with few projects but many files) - Since currently analyzing project no longer has any real meaning, add new status events to convey "analyzing n remaining files", updates every 50 documents - Retain old status events for backward compat with clients - Use 75% of available cores instead of half --- .../Events/BackgroundDiagnosticStatus.cs | 9 ++ .../BackgroundDiagnosticStatusMessage.cs | 10 +++ .../Models/Events/EventTypes.cs | 4 +- ...e.cs => ProjectDiagnosticStatusMessage.cs} | 2 +- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 87 ++++++++++++------- .../DiagnosticEventForwarder.cs | 22 ++++- .../Options/RoslynExtensionsOptions.cs | 2 +- .../FixAllFacts.cs | 4 +- .../ReAnalysisFacts.cs | 16 ++-- 9 files changed, 107 insertions(+), 49 deletions(-) create mode 100644 src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatus.cs create mode 100644 src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatusMessage.cs rename src/OmniSharp.Abstractions/Models/Events/{ProjectAnalyzeStatusMessage.cs => ProjectDiagnosticStatusMessage.cs} (99%) diff --git a/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatus.cs b/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatus.cs new file mode 100644 index 0000000000..1cdf5f2d28 --- /dev/null +++ b/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatus.cs @@ -0,0 +1,9 @@ +namespace OmniSharp.Models.Events +{ + public enum BackgroundDiagnosticStatus + { + Started = 0, + Update = 1, + Finished = 2 + } +} diff --git a/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatusMessage.cs b/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatusMessage.cs new file mode 100644 index 0000000000..6dad34ba50 --- /dev/null +++ b/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatusMessage.cs @@ -0,0 +1,10 @@ +namespace OmniSharp.Models.Events +{ + public class BackgroundDiagnosticStatusMessage + { + public BackgroundDiagnosticStatus Status { get; set; } + public int NumberProjects { get; set; } + public int NumberFiles { get; set; } + public int NumberFilesRemaining { get; set; } + } +} diff --git a/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs b/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs index 38da098cbb..021439eade 100644 --- a/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs +++ b/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs @@ -11,7 +11,7 @@ public static class EventTypes public const string PackageRestoreFinished = nameof(PackageRestoreFinished); public const string UnresolvedDependencies = nameof(UnresolvedDependencies); public const string ProjectConfiguration = nameof(ProjectConfiguration); - public const string ProjectDiagnosticStatus = nameof(ProjectDiagnosticStatus); - + public const string ProjectDiagnosticStatus = nameof(ProjectDiagnosticStatus); // Obsolete, retained for compatibility with older clients + public const string BackgroundDiagnosticStatus = nameof(BackgroundDiagnosticStatus); } } diff --git a/src/OmniSharp.Abstractions/Models/Events/ProjectAnalyzeStatusMessage.cs b/src/OmniSharp.Abstractions/Models/Events/ProjectDiagnosticStatusMessage.cs similarity index 99% rename from src/OmniSharp.Abstractions/Models/Events/ProjectAnalyzeStatusMessage.cs rename to src/OmniSharp.Abstractions/Models/Events/ProjectDiagnosticStatusMessage.cs index bd84bf6cd9..c1e47a7153 100644 --- a/src/OmniSharp.Abstractions/Models/Events/ProjectAnalyzeStatusMessage.cs +++ b/src/OmniSharp.Abstractions/Models/Events/ProjectDiagnosticStatusMessage.cs @@ -6,4 +6,4 @@ public class ProjectDiagnosticStatusMessage public string ProjectFilePath { get; set; } public string Type = "background"; } -} \ No newline at end of file +} diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 9e7fd25fbb..ef44699f15 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -23,10 +23,10 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class CSharpDiagnosticWorkerWithAnalyzers : ICsDiagnosticWorker, IDisposable { private readonly AnalyzerWorkQueue _workQueue; + private readonly SemaphoreSlim _throttler; private readonly ILogger _logger; - private readonly ConcurrentDictionary _currentDiagnosticResultLookup = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _currentDiagnosticResultLookup = new ConcurrentDictionary(); private readonly ImmutableArray _providers; private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpOptions _options; @@ -47,6 +47,7 @@ public CSharpDiagnosticWorkerWithAnalyzers( _logger = loggerFactory.CreateLogger(); _providers = providers.ToImmutableArray(); _workQueue = new AnalyzerWorkQueue(loggerFactory, timeoutForPendingWorkMs: options.RoslynExtensionsOptions.DocumentAnalysisTimeoutMs * 3); + _throttler = new SemaphoreSlim(options.RoslynExtensionsOptions.DiagnosticWorkersThreadCount); _forwarder = forwarder; _options = options; @@ -117,40 +118,44 @@ private async Task Worker(AnalyzerWorkType workType) { var solution = _workspace.CurrentSolution; - var currentWorkGroupedByProjects = _workQueue + var documents = _workQueue .TakeWork(workType) .Select(documentId => (projectId: solution.GetDocument(documentId)?.Project?.Id, documentId)) .Where(x => x.projectId != null) + .ToImmutableArray(); + var documentCount = documents.Length; + var documentCountRemaining = documentCount; + var documentsGroupedByProjects = documents .GroupBy(x => x.projectId, x => x.documentId) .ToImmutableArray(); + var projectCount = documentsGroupedByProjects.Length; + + EventIfBackgroundWork(workType, BackgroundDiagnosticStatus.Started, projectCount, documentCount, documentCountRemaining); - var analyzerTasks = new List(); - var throttler = new SemaphoreSlim(_options.RoslynExtensionsOptions.DiagnosticWorkersThreadCount); - foreach (var projectGroup in currentWorkGroupedByProjects) + void decrementDocumentCountRemaining() { - var projectPath = solution.GetProject(projectGroup.Key).FilePath; + var remaining = Interlocked.Decrement(ref documentCountRemaining); + if (remaining % 50 == 0) + EventIfBackgroundWork(workType, BackgroundDiagnosticStatus.Update, projectCount, documentCount, remaining); + } - await throttler.WaitAsync(); - analyzerTasks.Add( - Task.Run(async () => + try + { + var projectAnalyzerTasks = + documentsGroupedByProjects + .Select(projectGroup => Task.Run(async () => { - try - { - EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Started); - - await AnalyzeProject(solution, projectGroup); - - EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Ready); - } - finally - { - throttler.Release(); - } - } - ) - ); + var projectPath = solution.GetProject(projectGroup.Key).FilePath; + await AnalyzeProject(solution, projectGroup, decrementDocumentCountRemaining); + })) + .ToImmutableArray(); + + await Task.WhenAll(projectAnalyzerTasks); + } + finally + { + EventIfBackgroundWork(workType, BackgroundDiagnosticStatus.Finished, projectCount, documentCount, documentCountRemaining); } - await Task.WhenAll(analyzerTasks); _workQueue.WorkComplete(workType); @@ -163,10 +168,10 @@ private async Task Worker(AnalyzerWorkType workType) } } - private void EventIfBackgroundWork(AnalyzerWorkType workType, string projectPath, ProjectDiagnosticStatus status) + private void EventIfBackgroundWork(AnalyzerWorkType workType, BackgroundDiagnosticStatus status, int numberProjects, int numberFiles, int numberFilesRemaining) { if (workType == AnalyzerWorkType.Background) - _forwarder.ProjectAnalyzedInBackground(projectPath, status); + _forwarder.BackgroundDiagnosticsStatus(status, numberProjects, numberFiles, numberFilesRemaining); } private void QueueForAnalysis(ImmutableArray documentIds, AnalyzerWorkType workType) @@ -234,6 +239,7 @@ public async Task> AnalyzeProjectsAsync(Project project, var compilation = await project.GetCompilationAsync(); var workspaceAnalyzerOptions = (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution }); + var analyzerTasks = new List(); foreach (var document in project.Documents) { @@ -244,7 +250,7 @@ public async Task> AnalyzeProjectsAsync(Project project, return diagnostics; } - private async Task AnalyzeProject(Solution solution, IGrouping documentsGroupedByProject) + private async Task AnalyzeProject(Solution solution, IGrouping documentsGroupedByProject, Action decrementRemaining) { try { @@ -256,15 +262,30 @@ private async Task AnalyzeProject(Solution solution, IGrouping(); foreach (var documentId in documentsGroupedByProject) { - var document = project.GetDocument(documentId); - var diagnostics = await AnalyzeDocument(project, allAnalyzers, compilation, workspaceAnalyzerOptions, document); - UpdateCurrentDiagnostics(project, document, diagnostics); + await _throttler.WaitAsync(); + + documentAnalyzerTasks.Add(Task.Run(async () => + { + try + { + var document = project.GetDocument(documentId); + var diagnostics = await AnalyzeDocument(project, allAnalyzers, compilation, workspaceAnalyzerOptions, document); + UpdateCurrentDiagnostics(project, document, diagnostics); + decrementRemaining(); + } + finally + { + _throttler.Release(); + } + })); } + + await Task.WhenAll(documentAnalyzerTasks); } catch (Exception ex) { diff --git a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs index f4f3f090cd..5e7f600e03 100644 --- a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs +++ b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs @@ -23,9 +23,27 @@ public void Forward(DiagnosticMessage message) _emitter.Emit(EventTypes.Diagnostic, message); } - public void ProjectAnalyzedInBackground(string projectFileName, ProjectDiagnosticStatus status) + public void BackgroundDiagnosticsStatus(BackgroundDiagnosticStatus status, int numberProjects, int numberFiles, int numberFilesRemaining) { - _emitter.Emit(EventTypes.ProjectDiagnosticStatus, new ProjectDiagnosticStatusMessage { ProjectFilePath = projectFileName, Status = status }); + // New type of background diagnostic event, allows for more control of visualization in clients: + _emitter.Emit(EventTypes.BackgroundDiagnosticStatus, new BackgroundDiagnosticStatusMessage + { + Status = status, + NumberProjects = numberProjects, + NumberFiles = numberFiles, + NumberFilesRemaining = numberFilesRemaining + }); + + // Old type of event emitted as a shim for older clients: + _emitter.Emit(EventTypes.ProjectDiagnosticStatus, new ProjectDiagnosticStatusMessage + { + // There is no current project file being analyzed anymore since all the analysis + // executes concurrently, but we have to supply some value for the ProjectFilePath + // property for clients that only know about this event. In VS Code the following + // displays nicely as "Analyzing n1 files in n2 projects". + ProjectFilePath = $"{numberFilesRemaining} files in {numberProjects} projects", + Status = status == BackgroundDiagnosticStatus.Finished ? ProjectDiagnosticStatus.Ready : ProjectDiagnosticStatus.Started + }); } } } diff --git a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs index 09d3a1e0b0..9744c833b9 100644 --- a/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs +++ b/src/OmniSharp.Shared/Options/RoslynExtensionsOptions.cs @@ -12,7 +12,7 @@ public class RoslynExtensionsOptions : OmniSharpExtensionsOptions public bool EnableImportCompletion { get; set; } public bool EnableAsyncCompletion { get; set; } public int DocumentAnalysisTimeoutMs { get; set; } = 30 * 1000; - public int DiagnosticWorkersThreadCount { get; set; } = Math.Max(1, Environment.ProcessorCount / 2); + public int DiagnosticWorkersThreadCount { get; set; } = Math.Max(1, (int)(Environment.ProcessorCount * 0.75)); // Use 75% of available processors by default (but at least one) } public class OmniSharpExtensionsOptions diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/FixAllFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/FixAllFacts.cs index 545132bed9..81a26e43e6 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/FixAllFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/FixAllFacts.cs @@ -15,12 +15,12 @@ namespace OmniSharp.Roslyn.CSharp.Tests public class FixAllFacts { private readonly ITestOutputHelper _testOutput; - private readonly TestEventEmitter _analysisEventListener; + private readonly TestEventEmitter _analysisEventListener; public FixAllFacts(ITestOutputHelper testOutput) { _testOutput = testOutput; - _analysisEventListener = new TestEventEmitter(); + _analysisEventListener = new TestEventEmitter(); } [Fact] diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs index f302430cb0..ff225a4e7f 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -15,12 +15,12 @@ namespace OmniSharp.Roslyn.CSharp.Tests public class ReAnalysisFacts { private readonly ITestOutputHelper _testOutput; - private readonly TestEventEmitter _eventListener; + private readonly TestEventEmitter _eventListener; public ReAnalysisFacts(ITestOutputHelper testOutput) { _testOutput = testOutput; - _eventListener = new TestEventEmitter(); + _eventListener = new TestEventEmitter(); } @@ -74,8 +74,8 @@ public async Task WhenReanalyzeIsExecuted_ThenSendEventWhenAnalysisOfProjectIsRe await reAnalyzeHandler.Handle(new ReAnalyzeRequest()); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == project.FilePath && x.Status == ProjectDiagnosticStatus.Started); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == project.FilePath && x.Status == ProjectDiagnosticStatus.Ready); + await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Started); + await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Finished); } } @@ -96,8 +96,8 @@ await reAnalyzeHandler.Handle(new ReAnalyzeRequest FileName = projectA.Documents.Single(x => x.FilePath.EndsWith("a.cs")).FilePath }); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == projectA.FilePath && x.Status == ProjectDiagnosticStatus.Started); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == projectA.FilePath && x.Status == ProjectDiagnosticStatus.Ready); + await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Started); + await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Finished); } } @@ -118,8 +118,8 @@ await reAnalyzeHandler.Handle(new ReAnalyzeRequest FileName = project.FilePath }); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == project.FilePath && x.Status == ProjectDiagnosticStatus.Started); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == project.FilePath && x.Status == ProjectDiagnosticStatus.Ready); + await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Started); + await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Finished); } } From 1108e4cdb5707c8c521070b8d7c0820fa61204e0 Mon Sep 17 00:00:00 2001 From: Daniel Rosenberg Date: Sun, 19 Dec 2021 14:19:32 +0100 Subject: [PATCH 09/10] Improved progress event code --- .../Models/Events/BackgroundDiagnosticStatus.cs | 2 +- .../Events/BackgroundDiagnosticStatusMessage.cs | 2 +- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 5 +++-- .../DiagnosticEventForwarder.cs | 17 +++++++++++------ .../ReAnalysisFacts.cs | 12 ++++++------ 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatus.cs b/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatus.cs index 1cdf5f2d28..8b5e180618 100644 --- a/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatus.cs +++ b/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatus.cs @@ -3,7 +3,7 @@ namespace OmniSharp.Models.Events public enum BackgroundDiagnosticStatus { Started = 0, - Update = 1, + Progress = 1, Finished = 2 } } diff --git a/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatusMessage.cs b/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatusMessage.cs index 6dad34ba50..40fe2b6927 100644 --- a/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatusMessage.cs +++ b/src/OmniSharp.Abstractions/Models/Events/BackgroundDiagnosticStatusMessage.cs @@ -4,7 +4,7 @@ public class BackgroundDiagnosticStatusMessage { public BackgroundDiagnosticStatus Status { get; set; } public int NumberProjects { get; set; } - public int NumberFiles { get; set; } + public int NumberFilesTotal { get; set; } public int NumberFilesRemaining { get; set; } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index ef44699f15..fbf8a919aa 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -135,8 +135,9 @@ private async Task Worker(AnalyzerWorkType workType) void decrementDocumentCountRemaining() { var remaining = Interlocked.Decrement(ref documentCountRemaining); - if (remaining % 50 == 0) - EventIfBackgroundWork(workType, BackgroundDiagnosticStatus.Update, projectCount, documentCount, remaining); + var done = documentCount - remaining; + if (done % 24 == 0) // Update progress every 24 documents + EventIfBackgroundWork(workType, BackgroundDiagnosticStatus.Progress, projectCount, documentCount, remaining); } try diff --git a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs index 5e7f600e03..dc98a010b5 100644 --- a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs +++ b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs @@ -25,24 +25,29 @@ public void Forward(DiagnosticMessage message) public void BackgroundDiagnosticsStatus(BackgroundDiagnosticStatus status, int numberProjects, int numberFiles, int numberFilesRemaining) { - // New type of background diagnostic event, allows for more control of visualization in clients: + // New type of background diagnostic event, allows more control of visualization in clients: _emitter.Emit(EventTypes.BackgroundDiagnosticStatus, new BackgroundDiagnosticStatusMessage { Status = status, NumberProjects = numberProjects, - NumberFiles = numberFiles, + NumberFilesTotal = numberFiles, NumberFilesRemaining = numberFilesRemaining }); // Old type of event emitted as a shim for older clients: - _emitter.Emit(EventTypes.ProjectDiagnosticStatus, new ProjectDiagnosticStatusMessage + double percentComplete = 0; + if (numberFiles > 0 && numberFiles > numberFilesRemaining) + percentComplete = (numberFiles - numberFilesRemaining) / (double)numberFiles; + _emitter.Emit(EventTypes.ProjectDiagnosticStatus, new ProjectDiagnosticStatusMessage { // There is no current project file being analyzed anymore since all the analysis // executes concurrently, but we have to supply some value for the ProjectFilePath // property for clients that only know about this event. In VS Code the following - // displays nicely as "Analyzing n1 files in n2 projects". - ProjectFilePath = $"{numberFilesRemaining} files in {numberProjects} projects", - Status = status == BackgroundDiagnosticStatus.Finished ? ProjectDiagnosticStatus.Ready : ProjectDiagnosticStatus.Started + // displays nicely as "Analyzing (24%)". + ProjectFilePath = $"({percentComplete:P0})", + Status = status == BackgroundDiagnosticStatus.Finished ? + ProjectDiagnosticStatus.Ready : + ProjectDiagnosticStatus.Started }); } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs index ff225a4e7f..d05663fc68 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -74,8 +74,8 @@ public async Task WhenReanalyzeIsExecuted_ThenSendEventWhenAnalysisOfProjectIsRe await reAnalyzeHandler.Handle(new ReAnalyzeRequest()); - await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Started); - await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Finished); + await _eventListener.ExpectForEmitted(x => x.NumberFilesTotal == 1 && x.Status == BackgroundDiagnosticStatus.Started); + await _eventListener.ExpectForEmitted(x => x.NumberFilesTotal == 1 && x.Status == BackgroundDiagnosticStatus.Finished); } } @@ -96,8 +96,8 @@ await reAnalyzeHandler.Handle(new ReAnalyzeRequest FileName = projectA.Documents.Single(x => x.FilePath.EndsWith("a.cs")).FilePath }); - await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Started); - await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Finished); + await _eventListener.ExpectForEmitted(x => x.NumberFilesTotal == 1 && x.Status == BackgroundDiagnosticStatus.Started); + await _eventListener.ExpectForEmitted(x => x.NumberFilesTotal == 1 && x.Status == BackgroundDiagnosticStatus.Finished); } } @@ -118,8 +118,8 @@ await reAnalyzeHandler.Handle(new ReAnalyzeRequest FileName = project.FilePath }); - await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Started); - await _eventListener.ExpectForEmitted(x => x.NumberFiles == 1 && x.Status == BackgroundDiagnosticStatus.Finished); + await _eventListener.ExpectForEmitted(x => x.NumberFilesTotal == 1 && x.Status == BackgroundDiagnosticStatus.Started); + await _eventListener.ExpectForEmitted(x => x.NumberFilesTotal == 1 && x.Status == BackgroundDiagnosticStatus.Finished); } } From fb6556044b6f8976718daeadf5fcde6740debbf3 Mon Sep 17 00:00:00 2001 From: Tomas Ekeli Date: Sun, 19 Dec 2021 20:33:52 +0100 Subject: [PATCH 10/10] send analyzer event on every percentage increase hard-coding to a number of documents (e.g. 50 or 24) will either send updates very rarely giving large jumps, or very frequently (i.e. many times within one percentage-point). this change calculates how many documents correspond to 1%, and send an event every time that has been reached. if that is less than every 10 documents, it events on every tenth document. this avoids unnecessary updates and updates as often as relevant. --- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 27 ++++++++++--------- .../DiagnosticEventForwarder.cs | 7 ++++- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index fbf8a919aa..a9597d0ec1 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; using Microsoft.Extensions.Logging; using OmniSharp.Helpers; using OmniSharp.Models.Diagnostics; @@ -26,7 +25,7 @@ public class CSharpDiagnosticWorkerWithAnalyzers : ICsDiagnosticWorker, IDisposa private readonly SemaphoreSlim _throttler; private readonly ILogger _logger; - private readonly ConcurrentDictionary _currentDiagnosticResultLookup = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _currentDiagnosticResultLookup = new(); private readonly ImmutableArray _providers; private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpOptions _options; @@ -123,8 +122,13 @@ private async Task Worker(AnalyzerWorkType workType) .Select(documentId => (projectId: solution.GetDocument(documentId)?.Project?.Id, documentId)) .Where(x => x.projectId != null) .ToImmutableArray(); + var documentCount = documents.Length; var documentCountRemaining = documentCount; + + // event every percentage increase, or every 10th if there are fewer than 1000 + var eventEvery = Math.Max(10, documentCount / 100); + var documentsGroupedByProjects = documents .GroupBy(x => x.projectId, x => x.documentId) .ToImmutableArray(); @@ -136,8 +140,10 @@ void decrementDocumentCountRemaining() { var remaining = Interlocked.Decrement(ref documentCountRemaining); var done = documentCount - remaining; - if (done % 24 == 0) // Update progress every 24 documents + if (done % eventEvery == 0) + { EventIfBackgroundWork(workType, BackgroundDiagnosticStatus.Progress, projectCount, documentCount, remaining); + } } try @@ -211,7 +217,6 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv case WorkspaceChangeKind.SolutionReloaded: QueueDocumentsForDiagnostics(); break; - } } @@ -238,7 +243,7 @@ public async Task> AnalyzeProjectsAsync(Project project, .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language))) .ToImmutableArray(); - var compilation = await project.GetCompilationAsync(); + var compilation = await project.GetCompilationAsync(cancellationToken); var workspaceAnalyzerOptions = (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution }); var analyzerTasks = new List(); @@ -304,14 +309,12 @@ private async Task> AnalyzeDocument(Project project, var documentSemanticModel = await document.GetSemanticModelAsync(perDocumentTimeout.Token); - var diagnostics = ImmutableArray.Empty; - // Only basic syntax check is available if file is miscellanous like orphan .cs file. // Those projects are on hard coded virtual project if (project.Name == $"{Configuration.OmniSharpMiscProjectName}.csproj") { var syntaxTree = await document.GetSyntaxTreeAsync(); - diagnostics = syntaxTree.GetDiagnostics().ToImmutableArray(); + return syntaxTree.GetDiagnostics().ToImmutableArray(); } else if (allAnalyzers.Any()) // Analyzers cannot be called with empty analyzer list. { @@ -328,7 +331,7 @@ private async Task> AnalyzeDocument(Project project, var syntaxDiagnosticsWithAnalyzers = await compilationWithAnalyzers .GetAnalyzerSyntaxDiagnosticsAsync(documentSemanticModel.SyntaxTree, perDocumentTimeout.Token); - diagnostics = semanticDiagnosticsWithAnalyzers + return semanticDiagnosticsWithAnalyzers .Concat(syntaxDiagnosticsWithAnalyzers) .Where(d => !d.IsSuppressed) .Concat(documentSemanticModel.GetDiagnostics()) @@ -336,10 +339,8 @@ private async Task> AnalyzeDocument(Project project, } else { - diagnostics = documentSemanticModel.GetDiagnostics(); + return documentSemanticModel.GetDiagnostics(); } - - return diagnostics; } catch (Exception ex) { @@ -350,7 +351,7 @@ private async Task> AnalyzeDocument(Project project, private void OnAnalyzerException(Exception ex, DiagnosticAnalyzer analyzer, Diagnostic diagnostic) { - _logger.LogDebug($"Exception in diagnostic analyzer." + + _logger.LogDebug("Exception in diagnostic analyzer." + $"\n analyzer: {analyzer}" + $"\n diagnostic: {diagnostic}" + $"\n exception: {ex.Message}"); diff --git a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs index dc98a010b5..23cd93566d 100644 --- a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs +++ b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs @@ -37,7 +37,12 @@ public void BackgroundDiagnosticsStatus(BackgroundDiagnosticStatus status, int n // Old type of event emitted as a shim for older clients: double percentComplete = 0; if (numberFiles > 0 && numberFiles > numberFilesRemaining) - percentComplete = (numberFiles - numberFilesRemaining) / (double)numberFiles; + { + percentComplete = numberFiles <= 0 + ? 100 + : (numberFiles - numberFilesRemaining) / (double)numberFiles; + } + _emitter.Emit(EventTypes.ProjectDiagnosticStatus, new ProjectDiagnosticStatusMessage { // There is no current project file being analyzed anymore since all the analysis