From c0a15503266e53b0603fc8335563d3e61fa0220b Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 16 May 2019 21:00:24 +0300 Subject: [PATCH 01/26] Refactored work queue to only wait for single document. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 21 ++++++++----------- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 3 ++- .../AnalyzerWorkerQueueFacts.cs | 10 ++++----- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index a27af877fe..cc5f496dc0 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -77,30 +77,27 @@ public void MarkWorkAsCompleteForDocumentId(DocumentId documentId) // Omnisharp V2 api expects that it can request current information of diagnostics any time, // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. - public async Task WaitForResultsAsync(ImmutableArray documentIds) + public async Task WaitForResultsAsync(DocumentId documentId) { var items = new List<(DateTime modified, CancellationTokenSource workDoneSource)>(); - foreach (var documentId in documentIds) + if (_currentWork.ContainsKey(documentId)) { - if (_currentWork.ContainsKey(documentId)) - { - items.Add(_currentWork[documentId]); - } - else if (_workQueue.ContainsKey(documentId)) - { - items.Add(_workQueue[documentId]); - } + items.Add(_currentWork[documentId]); + } + else if (_workQueue.ContainsKey(documentId)) + { + items.Add(_workQueue[documentId]); } await Task.WhenAll(items.Select(item => Task.Delay(_maximumDelayWhenWaitingForResults, item.workDoneSource.Token) - .ContinueWith(task => LogTimeouts(task, documentIds)))); + .ContinueWith(task => LogTimeouts(task, documentId)))); } // This logs wait's for documentId diagnostics that continue without getting current version from analyzer. // This happens on larger solutions during initial load or situations where analysis slows down remarkably. - private void LogTimeouts(Task task, IEnumerable documentIds) + private void LogTimeouts(Task task, DocumentId documentIds) { if (!task.IsCanceled) _logger.LogDebug($"Timeout before work got ready for one of documents {string.Join(",", documentIds)}."); } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index d287cf4ac7..e94377df26 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -99,7 +99,8 @@ public ImmutableArray QueueForDiagnosis(ImmutableArray docum private async Task> GetDiagnosticsByDocumentIds(ImmutableArray documentIds) { - await _workQueue.WaitForResultsAsync(documentIds); + if(documentIds.Length == 1) + await _workQueue.WaitForResultsAsync(documentIds[0]); return _currentDiagnosticResults .Where(x => documentIds.Any(docId => docId == x.Key)) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index ce69ed174c..ac55be9fd7 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -104,7 +104,7 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() queue.PutWork(document); - var pendingTask = queue.WaitForResultsAsync(new [] { document }.ToImmutableArray()); + var pendingTask = queue.WaitForResultsAsync(document); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -130,7 +130,7 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR var work = queue.TakeWork(); - var pendingTask = queue.WaitForResultsAsync(work); + var pendingTask = queue.WaitForResultsAsync(work.First()); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -152,7 +152,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitForResultsAsync(work); + var pendingTask = queue.WaitForResultsAsync(work.First()); var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(5)); pendingTask.Wait(cts.Token); @@ -179,7 +179,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitForResultsAsync(work); + var pendingTask = queue.WaitForResultsAsync(work.First()); foreach (var workDoc in work) { @@ -208,7 +208,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var waitingCall = Task.Run(async () => await queue.WaitForResultsAsync(work)); + var waitingCall = Task.Run(async () => await queue.WaitForResultsAsync(work.First())); await Task.Delay(50); // User updates code -> document is queued again during period when theres already api call waiting From d508c4b2ca6c972b0484b541a8c95a70b2c64ead Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 18 May 2019 08:52:52 +0300 Subject: [PATCH 02/26] Prototype of new queue. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 127 ++++++++++-------- .../Workers/Diagnostics/AnalyzerWorkType.cs | 8 ++ .../CSharpDiagnosticWorkerWithAnalyzers.cs | 17 +-- .../AnalyzerWorkerQueueFacts.cs | 10 +- 4 files changed, 94 insertions(+), 68 deletions(-) create mode 100644 src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkType.cs diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index cc5f496dc0..c5f2b65991 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -12,13 +12,14 @@ namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics { public class AnalyzerWorkQueue { - private readonly int _throttlingMs = 300; + private readonly TimeSpan _foregroundThrottling = TimeSpan.FromMilliseconds(300); + private readonly TimeSpan _backgroundThrottling = TimeSpan.FromMilliseconds(2000); - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); - - private readonly ConcurrentDictionary _currentWork = - new ConcurrentDictionary(); + private ImmutableHashSet _backgroundWork = ImmutableHashSet.Empty; + private ImmutableHashSet _foregroundWork = ImmutableHashSet.Empty; + private DateTime _foregroundWorkThrottlingBeginStamp = DateTime.UtcNow; + private DateTime _backgroundThrottlingBeginStamp = DateTime.UtcNow; + private CancellationTokenSource _foregroundWorkPending = new CancellationTokenSource(); private readonly Func _utcNow; private readonly int _maximumDelayWhenWaitingForResults; @@ -26,80 +27,100 @@ public class AnalyzerWorkQueue public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 15*1000) { - utcNow = utcNow ?? (() => DateTime.UtcNow); + _utcNow = utcNow ?? (() => DateTime.UtcNow); _logger = loggerFactory.CreateLogger(); - _utcNow = utcNow; _maximumDelayWhenWaitingForResults = timeoutForPendingWorkMs; } - public void PutWork(DocumentId documentId) + public void PutWork(IReadOnlyCollection documentIds, AnalyzerWorkType workType) { - _workQueue.AddOrUpdate(documentId, - (modified: DateTime.UtcNow, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, oldValue.workDoneSource)); - } + if(workType == AnalyzerWorkType.Background) + { + if(_backgroundWork.IsEmpty) + _backgroundThrottlingBeginStamp = _utcNow(); - public ImmutableArray TakeWork() - { - lock (_workQueue) + _backgroundWork = _backgroundWork.Union(documentIds); + } + else { - var now = _utcNow(); - var currentWork = _workQueue - .Where(x => ThrottlingPeriodNotActive(x.Value.modified, now)) - .OrderByDescending(x => x.Value.modified) - .Take(50) - .ToImmutableArray(); - - foreach (var work in currentWork) - { - _workQueue.TryRemove(work.Key, out _); - _currentWork.TryAdd(work.Key, work.Value); - } - - return currentWork.Select(x => x.Key).ToImmutableArray(); + if(_foregroundWork.IsEmpty) + _foregroundWorkThrottlingBeginStamp = _utcNow(); + + if(_foregroundWorkPending == null) + _foregroundWorkPending = new CancellationTokenSource(); + + _foregroundWork = _foregroundWork.Union(documentIds); } } - private bool ThrottlingPeriodNotActive(DateTime modified, DateTime now) + public IReadOnlyCollection TakeWork(AnalyzerWorkType workType) { - return (now - modified).TotalMilliseconds >= _throttlingMs; + if(workType == AnalyzerWorkType.Foreground) + { + return TakeForegroundWork(); + } + else + { + return TakeBackgroundWork(); + } } - public void MarkWorkAsCompleteForDocumentId(DocumentId documentId) + private IReadOnlyCollection TakeForegroundWork() { - if(_currentWork.TryGetValue(documentId, out var work)) + if (IsForegroundThrottlingActive() || _foregroundWork.IsEmpty) + return ImmutableHashSet.Empty; + + lock (_foregroundWork) { - work.workDoneSource.Cancel(); - _currentWork.TryRemove(documentId, out _); + var currentWork = _foregroundWork; + _foregroundWork = ImmutableHashSet.Empty; + return currentWork; } } - // Omnisharp V2 api expects that it can request current information of diagnostics any time, - // however analysis is worker based and is eventually ready. This method is used to make api look - // like it's syncronous even that actual analysis may take a while. - public async Task WaitForResultsAsync(DocumentId documentId) + private IReadOnlyCollection TakeBackgroundWork() { - var items = new List<(DateTime modified, CancellationTokenSource workDoneSource)>(); + if (IsBackgroundThrottlineActive() || _backgroundWork.IsEmpty) + return ImmutableHashSet.Empty; - if (_currentWork.ContainsKey(documentId)) + lock (_backgroundWork) { - items.Add(_currentWork[documentId]); + var currentWork = _foregroundWork; + _backgroundWork = ImmutableHashSet.Empty; + return currentWork; } - else if (_workQueue.ContainsKey(documentId)) + } + + private bool IsForegroundThrottlingActive() + { + return (_utcNow() - _foregroundWorkThrottlingBeginStamp).TotalMilliseconds <= _foregroundThrottling.TotalMilliseconds; + } + + private bool IsBackgroundThrottlineActive() + { + return (_utcNow() - _backgroundThrottlingBeginStamp).TotalMilliseconds <= _backgroundThrottling.TotalMilliseconds; + } + + public void ForegroundWorkComplete() + { + lock(_foregroundWork) { - items.Add(_workQueue[documentId]); - } + if(_foregroundWorkPending == null) + return; - await Task.WhenAll(items.Select(item => - Task.Delay(_maximumDelayWhenWaitingForResults, item.workDoneSource.Token) - .ContinueWith(task => LogTimeouts(task, documentId)))); + _foregroundWorkPending.Cancel(); + } } - // This logs wait's for documentId diagnostics that continue without getting current version from analyzer. - // This happens on larger solutions during initial load or situations where analysis slows down remarkably. - private void LogTimeouts(Task task, DocumentId documentIds) + // Omnisharp V2 api expects that it can request current information of diagnostics any time, + // however analysis is worker based and is eventually ready. This method is used to make api look + // like it's syncronous even that actual analysis may take a while. + public Task WaitForegroundWorkComplete() { - if (!task.IsCanceled) _logger.LogDebug($"Timeout before work got ready for one of documents {string.Join(",", documentIds)}."); + if(_foregroundWorkPending == null && _foregroundWork.IsEmpty) + return Task.CompletedTask; + + return Task.Delay(_maximumDelayWhenWaitingForResults, _foregroundWorkPending.Token); } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkType.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkType.cs new file mode 100644 index 0000000000..deea63426a --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkType.cs @@ -0,0 +1,8 @@ +namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics +{ + public enum AnalyzerWorkType + { + Background, Foreground + } + +} diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index e94377df26..b7da42bd74 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -100,7 +100,7 @@ public ImmutableArray QueueForDiagnosis(ImmutableArray docum private async Task> GetDiagnosticsByDocumentIds(ImmutableArray documentIds) { if(documentIds.Length == 1) - await _workQueue.WaitForResultsAsync(documentIds[0]); + await _workQueue.WaitForegroundWorkComplete(); return _currentDiagnosticResults .Where(x => documentIds.Any(docId => docId == x.Key)) @@ -144,12 +144,9 @@ private async Task Worker() } } - private void QueueForAnalysis(ImmutableArray documentIds) + private void QueueForAnalysis(ImmutableArray documentIds, AnalyzerWorkType workType) { - foreach (var document in documentIds) - { - _workQueue.PutWork(document); - } + _workQueue.PutWork(documentIds, workType); } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) @@ -159,7 +156,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv || changeEvent.Kind == WorkspaceChangeKind.DocumentReloaded || changeEvent.Kind == WorkspaceChangeKind.DocumentInfoChanged ) { - QueueForAnalysis(ImmutableArray.Create(changeEvent.DocumentId)); + QueueForAnalysis(ImmutableArray.Create(changeEvent.DocumentId), AnalyzerWorkType.Foreground); } else if(changeEvent.Kind == WorkspaceChangeKind.DocumentRemoved) { @@ -245,14 +242,14 @@ private async Task AnalyzeDocument(Project project, ImmutableArray diagnosticsWithAnalyzers) { _currentDiagnosticResults[document.Id] = (project.Name, diagnosticsWithAnalyzers); - _workQueue.MarkWorkAsCompleteForDocumentId(document.Id); + _workQueue.ForegroundWorkComplete(); EmitDiagnostics(_currentDiagnosticResults[document.Id].diagnostics); } @@ -274,7 +271,7 @@ private void EmitDiagnostics(ImmutableArray results) public ImmutableArray QueueAllDocumentsForDiagnostics() { var documentIds = _workspace.CurrentSolution.Projects.SelectMany(x => x.DocumentIds).ToImmutableArray(); - QueueForAnalysis(documentIds); + QueueForAnalysis(documentIds, AnalyzerWorkType.Background); return documentIds; } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index ac55be9fd7..b665133d45 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -104,7 +104,7 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() queue.PutWork(document); - var pendingTask = queue.WaitForResultsAsync(document); + var pendingTask = queue.WaitForegroundWorkDone(document); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -130,7 +130,7 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR var work = queue.TakeWork(); - var pendingTask = queue.WaitForResultsAsync(work.First()); + var pendingTask = queue.WaitForegroundWorkDone(work.First()); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -152,7 +152,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitForResultsAsync(work.First()); + var pendingTask = queue.WaitForegroundWorkDone(work.First()); var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(5)); pendingTask.Wait(cts.Token); @@ -179,7 +179,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitForResultsAsync(work.First()); + var pendingTask = queue.WaitForegroundWorkDone(work.First()); foreach (var workDoc in work) { @@ -208,7 +208,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var waitingCall = Task.Run(async () => await queue.WaitForResultsAsync(work.First())); + var waitingCall = Task.Run(async () => await queue.WaitForegroundWorkDone(work.First())); await Task.Delay(50); // User updates code -> document is queued again during period when theres already api call waiting From 357a395dddc97f6f4f50e67dea292cf7afbc3dcf Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 18 May 2019 09:09:19 +0300 Subject: [PATCH 03/26] Initial prototype of foreground/background where only foreground is supported. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 4 +- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 9 +- .../CsharpDiagnosticWorkerComposer.cs | 5 - .../Diagnostics/ICsDiagnosticWorker.cs | 1 - .../AnalyzerWorkerQueueFacts.cs | 416 +++++++++--------- 5 files changed, 211 insertions(+), 224 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index c5f2b65991..ac0bb1cb92 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -85,7 +85,7 @@ private IReadOnlyCollection TakeBackgroundWork() lock (_backgroundWork) { - var currentWork = _foregroundWork; + var currentWork = _backgroundWork; _backgroundWork = ImmutableHashSet.Empty; return currentWork; } @@ -117,7 +117,7 @@ public void ForegroundWorkComplete() // like it's syncronous even that actual analysis may take a while. public Task WaitForegroundWorkComplete() { - if(_foregroundWorkPending == null && _foregroundWork.IsEmpty) + if(_foregroundWorkPending == null || _foregroundWork.IsEmpty) return Task.CompletedTask; return Task.Delay(_maximumDelayWhenWaitingForResults, _foregroundWorkPending.Token); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index b7da42bd74..dd396499bf 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -81,13 +81,6 @@ private Task InitializeWithWorkspaceDocumentsIfNotYetDone() }); } - public ImmutableArray QueueForDiagnosis(ImmutableArray documentPaths) - { - var documentIds = GetDocumentIdsFromPaths(documentPaths); - QueueForAnalysis(documentIds); - return documentIds; - } - public async Task> GetDiagnostics(ImmutableArray documentPaths) { await InitializeWithWorkspaceDocumentsIfNotYetDone(); @@ -124,7 +117,7 @@ private async Task Worker() var solution = _workspace.CurrentSolution; var currentWorkGroupedByProjects = _workQueue - .TakeWork() + .TakeWork(AnalyzerWorkType.Foreground) .Select(documentId => (projectId: solution.GetDocument(documentId)?.Project?.Id, documentId)) .Where(x => x.projectId != null) .GroupBy(x => x.projectId, x => x.documentId) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs index 5930a2c125..f138d4da93 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs @@ -63,10 +63,5 @@ public ImmutableArray QueueAllDocumentsForDiagnostics() { return _implementation.QueueAllDocumentsForDiagnostics(); } - - public ImmutableArray QueueForDiagnosis(ImmutableArray documentPaths) - { - return _implementation.QueueForDiagnosis(documentPaths); - } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs index 3295efa68b..07f6046823 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs @@ -8,7 +8,6 @@ public interface ICsDiagnosticWorker { Task> GetDiagnostics(ImmutableArray documentPaths); Task> GetAllDiagnosticsAsync(); - ImmutableArray QueueForDiagnosis(ImmutableArray documentsPaths); ImmutableArray QueueAllDocumentsForDiagnostics(); } } \ No newline at end of file diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index b665133d45..4da0cfa7e6 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -1,241 +1,241 @@ -using System; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.Extensions.Logging; -using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; -using Xunit; - -namespace OmniSharp.Roslyn.CSharp.Tests -{ - public class AnalyzerWorkerQueueFacts - { - private class Logger : ILogger - { - public IDisposable BeginScope(TState state) - { - return null; - } - - public bool IsEnabled(LogLevel logLevel) => true; - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - RecordedMessages = RecordedMessages.Add(state.ToString()); - } - - public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); - } - - private class LoggerFactory : ILoggerFactory - { - public Logger Logger { get; } = new Logger(); - - public void AddProvider(ILoggerProvider provider) - { - } - - public ILogger CreateLogger(string categoryName) - { - return Logger; - } - - public void Dispose() - { - } - } - - [Fact] - public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocumentId(); - - queue.PutWork(document); - Assert.Empty(queue.TakeWork()); - } - - [Fact] - public void WhenWorksIsAddedToQueueThenTheyWillBeReturned() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocumentId(); - - queue.PutWork(document); - - now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); - - Assert.Contains(document, work); - Assert.Empty(queue.TakeWork()); - } - - [Fact] - public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocumentId(); - - queue.PutWork(document); - queue.PutWork(document); - queue.PutWork(document); - - Assert.Empty(queue.TakeWork()); - - now = PassOverThrotlingPeriod(now); - - Assert.Contains(document, queue.TakeWork()); - Assert.Empty(queue.TakeWork()); - } - - private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(30); - - [Fact] - public void WhenWorkIsAddedThenWaitNextIterationOfItReady() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); - var document = CreateTestDocumentId(); - - queue.PutWork(document); - - var pendingTask = queue.WaitForegroundWorkDone(document); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +// using System; +// using System.Collections.Immutable; +// using System.Linq; +// using System.Threading; +// using System.Threading.Tasks; +// using Microsoft.CodeAnalysis; +// using Microsoft.Extensions.Logging; +// using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; +// using Xunit; + +// namespace OmniSharp.Roslyn.CSharp.Tests +// { +// public class AnalyzerWorkerQueueFacts +// { +// private class Logger : ILogger +// { +// public IDisposable BeginScope(TState state) +// { +// return null; +// } + +// public bool IsEnabled(LogLevel logLevel) => true; + +// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) +// { +// RecordedMessages = RecordedMessages.Add(state.ToString()); +// } + +// public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); +// } + +// private class LoggerFactory : ILoggerFactory +// { +// public Logger Logger { get; } = new Logger(); + +// public void AddProvider(ILoggerProvider provider) +// { +// } + +// public ILogger CreateLogger(string categoryName) +// { +// return Logger; +// } + +// public void Dispose() +// { +// } +// } + +// [Fact] +// public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); +// var document = CreateTestDocumentId(); + +// queue.PutWork(document); +// Assert.Empty(queue.TakeWork()); +// } + +// [Fact] +// public void WhenWorksIsAddedToQueueThenTheyWillBeReturned() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); +// var document = CreateTestDocumentId(); + +// queue.PutWork(document); + +// now = PassOverThrotlingPeriod(now); +// var work = queue.TakeWork(); + +// Assert.Contains(document, work); +// Assert.Empty(queue.TakeWork()); +// } + +// [Fact] +// public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); +// var document = CreateTestDocumentId(); + +// queue.PutWork(document); +// queue.PutWork(document); +// queue.PutWork(document); + +// Assert.Empty(queue.TakeWork()); + +// now = PassOverThrotlingPeriod(now); + +// Assert.Contains(document, queue.TakeWork()); +// Assert.Empty(queue.TakeWork()); +// } + +// private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(30); + +// [Fact] +// public void WhenWorkIsAddedThenWaitNextIterationOfItReady() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); +// var document = CreateTestDocumentId(); + +// queue.PutWork(document); + +// var pendingTask = queue.WaitForegroundWorkDone(document); +// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); - Assert.False(pendingTask.IsCompleted); +// Assert.False(pendingTask.IsCompleted); - now = PassOverThrotlingPeriod(now); +// now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); - queue.MarkWorkAsCompleteForDocumentId(document); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); - Assert.True(pendingTask.IsCompleted); - } +// var work = queue.TakeWork(); +// queue.MarkWorkAsCompleteForDocumentId(document); +// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +// Assert.True(pendingTask.IsCompleted); +// } - [Fact] - public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); - var document = CreateTestDocumentId(); +// [Fact] +// public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); +// var document = CreateTestDocumentId(); - queue.PutWork(document); +// queue.PutWork(document); - now = PassOverThrotlingPeriod(now); +// now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); +// var work = queue.TakeWork(); - var pendingTask = queue.WaitForegroundWorkDone(work.First()); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +// var pendingTask = queue.WaitForegroundWorkDone(work.First()); +// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); - Assert.False(pendingTask.IsCompleted); - queue.MarkWorkAsCompleteForDocumentId(document); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); - Assert.True(pendingTask.IsCompleted); - } +// Assert.False(pendingTask.IsCompleted); +// queue.MarkWorkAsCompleteForDocumentId(document); +// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +// Assert.True(pendingTask.IsCompleted); +// } - [Fact] - public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() - { - var now = DateTime.UtcNow; - var loggerFactory = new LoggerFactory(); - var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); - var document = CreateTestDocumentId(); +// [Fact] +// public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() +// { +// var now = DateTime.UtcNow; +// var loggerFactory = new LoggerFactory(); +// var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); +// var document = CreateTestDocumentId(); - queue.PutWork(document); +// queue.PutWork(document); - now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); +// now = PassOverThrotlingPeriod(now); +// var work = queue.TakeWork(); - var pendingTask = queue.WaitForegroundWorkDone(work.First()); - var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromSeconds(5)); - pendingTask.Wait(cts.Token); +// var pendingTask = queue.WaitForegroundWorkDone(work.First()); +// var cts = new CancellationTokenSource(); +// cts.CancelAfter(TimeSpan.FromSeconds(5)); +// pendingTask.Wait(cts.Token); - Assert.True(pendingTask.IsCompleted); - Assert.Contains("Timeout before work got ready", loggerFactory.Logger.RecordedMessages.Single()); - } +// Assert.True(pendingTask.IsCompleted); +// Assert.Contains("Timeout before work got ready", loggerFactory.Logger.RecordedMessages.Single()); +// } - [Fact] - public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() - { - var now = DateTime.UtcNow; +// [Fact] +// public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() +// { +// var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 1000); +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 1000); - var parallelQueues = - Enumerable.Range(0, 10) - .Select(_ => - Task.Run(() => { - var document = CreateTestDocumentId(); +// var parallelQueues = +// Enumerable.Range(0, 10) +// .Select(_ => +// Task.Run(() => { +// var document = CreateTestDocumentId(); - queue.PutWork(document); +// queue.PutWork(document); - now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); +// now = PassOverThrotlingPeriod(now); +// var work = queue.TakeWork(); - var pendingTask = queue.WaitForegroundWorkDone(work.First()); +// var pendingTask = queue.WaitForegroundWorkDone(work.First()); - foreach (var workDoc in work) - { - queue.MarkWorkAsCompleteForDocumentId(workDoc); - } +// foreach (var workDoc in work) +// { +// queue.MarkWorkAsCompleteForDocumentId(workDoc); +// } - pendingTask.Wait(TimeSpan.FromMilliseconds(300)); - })) - .ToArray(); +// pendingTask.Wait(TimeSpan.FromMilliseconds(300)); +// })) +// .ToArray(); - await Task.WhenAll(parallelQueues); +// await Task.WhenAll(parallelQueues); - Assert.Empty(queue.TakeWork()); - } +// Assert.Empty(queue.TakeWork()); +// } - [Fact] - public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() - { - var now = DateTime.UtcNow; - var loggerFactory = new LoggerFactory(); - var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); - var document = CreateTestDocumentId(); +// [Fact] +// public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() +// { +// var now = DateTime.UtcNow; +// var loggerFactory = new LoggerFactory(); +// var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); +// var document = CreateTestDocumentId(); - queue.PutWork(document); +// queue.PutWork(document); - now = PassOverThrotlingPeriod(now); +// now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); - var waitingCall = Task.Run(async () => await queue.WaitForegroundWorkDone(work.First())); - await Task.Delay(50); +// var work = queue.TakeWork(); +// var waitingCall = Task.Run(async () => await queue.WaitForegroundWorkDone(work.First())); +// await Task.Delay(50); - // User updates code -> document is queued again during period when theres already api call waiting - // to continue. - queue.PutWork(document); +// // User updates code -> document is queued again during period when theres already api call waiting +// // to continue. +// queue.PutWork(document); - // First iteration of work is done. - queue.MarkWorkAsCompleteForDocumentId(document); +// // First iteration of work is done. +// queue.MarkWorkAsCompleteForDocumentId(document); - // Waiting call continues because it's iteration of work is done, even when theres next - // already waiting. - await waitingCall; +// // Waiting call continues because it's iteration of work is done, even when theres next +// // already waiting. +// await waitingCall; - Assert.True(waitingCall.IsCompleted); - Assert.Empty(loggerFactory.Logger.RecordedMessages); - } +// Assert.True(waitingCall.IsCompleted); +// Assert.Empty(loggerFactory.Logger.RecordedMessages); +// } - private DocumentId CreateTestDocumentId() - { - var projectInfo = ProjectInfo.Create( - id: ProjectId.CreateNewId(), - version: VersionStamp.Create(), - name: "testProject", - assemblyName: "AssemblyName", - language: LanguageNames.CSharp); - - return DocumentId.CreateNewId(projectInfo.Id); - } - } -} +// private DocumentId CreateTestDocumentId() +// { +// var projectInfo = ProjectInfo.Create( +// id: ProjectId.CreateNewId(), +// version: VersionStamp.Create(), +// name: "testProject", +// assemblyName: "AssemblyName", +// language: LanguageNames.CSharp); + +// return DocumentId.CreateNewId(projectInfo.Id); +// } +// } +// } From 83c209c4c816e421569dca13defb5aa8e3a478cc Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 18 May 2019 20:23:54 +0300 Subject: [PATCH 04/26] Fixed queue tests to compile. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 5 +- .../AnalyzerWorkerQueueFacts.cs | 387 ++++++++---------- 2 files changed, 180 insertions(+), 212 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index ac0bb1cb92..954ef2617d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -23,12 +23,10 @@ public class AnalyzerWorkQueue private readonly Func _utcNow; private readonly int _maximumDelayWhenWaitingForResults; - private readonly ILogger _logger; - public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 15*1000) + public AnalyzerWorkQueue(Func utcNow = null, int timeoutForPendingWorkMs = 15*1000) { _utcNow = utcNow ?? (() => DateTime.UtcNow); - _logger = loggerFactory.CreateLogger(); _maximumDelayWhenWaitingForResults = timeoutForPendingWorkMs; } @@ -123,5 +121,4 @@ public Task WaitForegroundWorkComplete() return Task.Delay(_maximumDelayWhenWaitingForResults, _foregroundWorkPending.Token); } } - } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 4da0cfa7e6..76b0083585 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -1,241 +1,212 @@ -// using System; -// using System.Collections.Immutable; -// using System.Linq; -// using System.Threading; -// using System.Threading.Tasks; -// using Microsoft.CodeAnalysis; -// using Microsoft.Extensions.Logging; -// using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; -// using Xunit; - -// namespace OmniSharp.Roslyn.CSharp.Tests -// { -// public class AnalyzerWorkerQueueFacts -// { -// private class Logger : ILogger -// { -// public IDisposable BeginScope(TState state) -// { -// return null; -// } - -// public bool IsEnabled(LogLevel logLevel) => true; - -// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) -// { -// RecordedMessages = RecordedMessages.Add(state.ToString()); -// } - -// public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); -// } - -// private class LoggerFactory : ILoggerFactory -// { -// public Logger Logger { get; } = new Logger(); - -// public void AddProvider(ILoggerProvider provider) -// { -// } - -// public ILogger CreateLogger(string categoryName) -// { -// return Logger; -// } - -// public void Dispose() -// { -// } -// } - -// [Fact] -// public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); -// var document = CreateTestDocumentId(); - -// queue.PutWork(document); -// Assert.Empty(queue.TakeWork()); -// } - -// [Fact] -// public void WhenWorksIsAddedToQueueThenTheyWillBeReturned() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); -// var document = CreateTestDocumentId(); - -// queue.PutWork(document); - -// now = PassOverThrotlingPeriod(now); -// var work = queue.TakeWork(); - -// Assert.Contains(document, work); -// Assert.Empty(queue.TakeWork()); -// } - -// [Fact] -// public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); -// var document = CreateTestDocumentId(); - -// queue.PutWork(document); -// queue.PutWork(document); -// queue.PutWork(document); - -// Assert.Empty(queue.TakeWork()); - -// now = PassOverThrotlingPeriod(now); - -// Assert.Contains(document, queue.TakeWork()); -// Assert.Empty(queue.TakeWork()); -// } - -// private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(30); - -// [Fact] -// public void WhenWorkIsAddedThenWaitNextIterationOfItReady() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); -// var document = CreateTestDocumentId(); - -// queue.PutWork(document); - -// var pendingTask = queue.WaitForegroundWorkDone(document); -// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; +using Xunit; + +namespace OmniSharp.Roslyn.CSharp.Tests +{ + public class AnalyzerWorkerQueueFacts + { + [Theory] + [InlineData(AnalyzerWorkType.Background)] + [InlineData(AnalyzerWorkType.Foreground)] + public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned(AnalyzerWorkType workType) + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(utcNow: () => now); + var document = CreateTestDocumentId(); + + queue.PutWork(new [] { document }, workType); + Assert.Empty(queue.TakeWork(workType)); + } + + [Theory] + [InlineData(AnalyzerWorkType.Background)] + [InlineData(AnalyzerWorkType.Foreground)] + public void WhenWorksIsAddedToQueueThenTheyWillBeReturned(AnalyzerWorkType workType) + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(utcNow: () => now); + var document = CreateTestDocumentId(); + + queue.PutWork(new [] { document }, workType); + + now = PassOverThrotlingPeriod(now); + var work = queue.TakeWork(workType); + + Assert.Contains(document, work); + Assert.Empty(queue.TakeWork(workType)); + } + + [Theory] + [InlineData(AnalyzerWorkType.Background)] + [InlineData(AnalyzerWorkType.Foreground)] + public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne(AnalyzerWorkType workType) + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(utcNow: () => now); + var document = CreateTestDocumentId(); + + queue.PutWork(new [] { document }, workType); + queue.PutWork(new [] { document }, workType); + queue.PutWork(new [] { document }, workType); + + Assert.Empty(queue.TakeWork(workType)); + + now = PassOverThrotlingPeriod(now); -// Assert.False(pendingTask.IsCompleted); + Assert.Contains(document, queue.TakeWork(workType)); + Assert.Empty(queue.TakeWork(workType)); + } -// now = PassOverThrotlingPeriod(now); + private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(30); + + [Fact] + public void WhenForegroundWorkIsAddedThenWaitNextIterationOfItReady() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(utcNow: () => now, timeoutForPendingWorkMs: 500); + var document = CreateTestDocumentId(); -// var work = queue.TakeWork(); -// queue.MarkWorkAsCompleteForDocumentId(document); -// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); -// Assert.True(pendingTask.IsCompleted); -// } + queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); -// [Fact] -// public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); -// var document = CreateTestDocumentId(); + var pendingTask = queue.WaitForegroundWorkComplete(); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); -// queue.PutWork(document); + Assert.False(pendingTask.IsCompleted); -// now = PassOverThrotlingPeriod(now); + now = PassOverThrotlingPeriod(now); -// var work = queue.TakeWork(); + var work = queue.TakeWork(AnalyzerWorkType.Foreground); + queue.ForegroundWorkComplete(); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + Assert.True(pendingTask.IsCompleted); + } -// var pendingTask = queue.WaitForegroundWorkDone(work.First()); -// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + [Fact] + public void WhenForegroundWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(utcNow: () => now, timeoutForPendingWorkMs: 500); + var document = CreateTestDocumentId(); -// Assert.False(pendingTask.IsCompleted); -// queue.MarkWorkAsCompleteForDocumentId(document); -// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); -// Assert.True(pendingTask.IsCompleted); -// } + queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); -// [Fact] -// public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() -// { -// var now = DateTime.UtcNow; -// var loggerFactory = new LoggerFactory(); -// var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); -// var document = CreateTestDocumentId(); + now = PassOverThrotlingPeriod(now); -// queue.PutWork(document); + var work = queue.TakeWork(AnalyzerWorkType.Foreground); -// now = PassOverThrotlingPeriod(now); -// var work = queue.TakeWork(); + var pendingTask = queue.WaitForegroundWorkComplete(); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); -// var pendingTask = queue.WaitForegroundWorkDone(work.First()); -// var cts = new CancellationTokenSource(); -// cts.CancelAfter(TimeSpan.FromSeconds(5)); -// pendingTask.Wait(cts.Token); + Assert.False(pendingTask.IsCompleted); + queue.ForegroundWorkComplete(); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + Assert.True(pendingTask.IsCompleted); + } -// Assert.True(pendingTask.IsCompleted); -// Assert.Contains("Timeout before work got ready", loggerFactory.Logger.RecordedMessages.Single()); -// } + [Fact] + public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(utcNow: () => now, timeoutForPendingWorkMs: 20); + var document = CreateTestDocumentId(); -// [Fact] -// public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() -// { -// var now = DateTime.UtcNow; + queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 1000); + now = PassOverThrotlingPeriod(now); + var work = queue.TakeWork(AnalyzerWorkType.Foreground); -// var parallelQueues = -// Enumerable.Range(0, 10) -// .Select(_ => -// Task.Run(() => { -// var document = CreateTestDocumentId(); + var pendingTask = queue.WaitForegroundWorkComplete(); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + pendingTask.Wait(cts.Token); -// queue.PutWork(document); + Assert.True(pendingTask.IsCompleted); + } -// now = PassOverThrotlingPeriod(now); -// var work = queue.TakeWork(); + [Fact] + public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() + { + var now = DateTime.UtcNow; -// var pendingTask = queue.WaitForegroundWorkDone(work.First()); + var queue = new AnalyzerWorkQueue(utcNow: () => now, timeoutForPendingWorkMs: 1000); -// foreach (var workDoc in work) -// { -// queue.MarkWorkAsCompleteForDocumentId(workDoc); -// } + var parallelQueues = + Enumerable.Range(0, 10) + .Select(_ => + Task.Run(() => { + var document = CreateTestDocumentId(); -// pendingTask.Wait(TimeSpan.FromMilliseconds(300)); -// })) -// .ToArray(); + queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); -// await Task.WhenAll(parallelQueues); + now = PassOverThrotlingPeriod(now); -// Assert.Empty(queue.TakeWork()); -// } + var work = queue.TakeWork(AnalyzerWorkType.Foreground); -// [Fact] -// public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() -// { -// var now = DateTime.UtcNow; -// var loggerFactory = new LoggerFactory(); -// var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); -// var document = CreateTestDocumentId(); + var pendingTask = queue.WaitForegroundWorkComplete(); -// queue.PutWork(document); + queue.WaitForegroundWorkComplete(); -// now = PassOverThrotlingPeriod(now); + pendingTask.Wait(TimeSpan.FromMilliseconds(300)); + })) + .ToArray(); -// var work = queue.TakeWork(); -// var waitingCall = Task.Run(async () => await queue.WaitForegroundWorkDone(work.First())); -// await Task.Delay(50); + await Task.WhenAll(parallelQueues); -// // User updates code -> document is queued again during period when theres already api call waiting -// // to continue. -// queue.PutWork(document); + Assert.Empty(queue.TakeWork(AnalyzerWorkType.Foreground)); + } -// // First iteration of work is done. -// queue.MarkWorkAsCompleteForDocumentId(document); + [Fact] + public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(utcNow: () => now); + var document = CreateTestDocumentId(); -// // Waiting call continues because it's iteration of work is done, even when theres next -// // already waiting. -// await waitingCall; + queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); -// Assert.True(waitingCall.IsCompleted); -// Assert.Empty(loggerFactory.Logger.RecordedMessages); -// } + now = PassOverThrotlingPeriod(now); -// private DocumentId CreateTestDocumentId() -// { -// var projectInfo = ProjectInfo.Create( -// id: ProjectId.CreateNewId(), -// version: VersionStamp.Create(), -// name: "testProject", -// assemblyName: "AssemblyName", -// language: LanguageNames.CSharp); - -// return DocumentId.CreateNewId(projectInfo.Id); -// } -// } -// } + var work = queue.TakeWork(AnalyzerWorkType.Foreground); + var waitingCall = Task.Run(async () => await queue.WaitForegroundWorkComplete()); + await Task.Delay(50); + + // User updates code -> document is queued again during period when theres already api call waiting + // to continue. + queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); + + // First iteration of work is done. + queue.ForegroundWorkComplete(); + + // Waiting call continues because it's iteration of work is done, even when theres next + // already waiting. + await waitingCall; + + Assert.True(waitingCall.IsCompleted); + } + + [Fact] + public async Task WhenBackgroundWorkIsAdded_DontWaitIt() + { + throw new NotImplementedException(); + } + + private DocumentId CreateTestDocumentId() + { + var projectInfo = ProjectInfo.Create( + id: ProjectId.CreateNewId(), + version: VersionStamp.Create(), + name: "testProject", + assemblyName: "AssemblyName", + language: LanguageNames.CSharp); + + return DocumentId.CreateNewId(projectInfo.Id); + } + } +} From 0840a7d33c2f43f33a24ab2b54e21f059949718a Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 19 May 2019 20:28:54 +0300 Subject: [PATCH 05/26] Fixes for queue. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 69 ++++++++---- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 11 +- .../AnalyzerWorkerQueueFacts.cs | 100 ++++++++++++++---- 3 files changed, 132 insertions(+), 48 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 954ef2617d..398d201015 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -20,34 +20,39 @@ public class AnalyzerWorkQueue private DateTime _foregroundWorkThrottlingBeginStamp = DateTime.UtcNow; private DateTime _backgroundThrottlingBeginStamp = DateTime.UtcNow; private CancellationTokenSource _foregroundWorkPending = new CancellationTokenSource(); - + private readonly ILogger _logger; private readonly Func _utcNow; private readonly int _maximumDelayWhenWaitingForResults; + private readonly object _queueLock = new Object(); - public AnalyzerWorkQueue(Func utcNow = null, int timeoutForPendingWorkMs = 15*1000) + public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 15*1000) { + _logger = loggerFactory.CreateLogger(); _utcNow = utcNow ?? (() => DateTime.UtcNow); _maximumDelayWhenWaitingForResults = timeoutForPendingWorkMs; } public void PutWork(IReadOnlyCollection documentIds, AnalyzerWorkType workType) { - if(workType == AnalyzerWorkType.Background) + lock(_queueLock) { - if(_backgroundWork.IsEmpty) - _backgroundThrottlingBeginStamp = _utcNow(); - - _backgroundWork = _backgroundWork.Union(documentIds); - } - else - { - if(_foregroundWork.IsEmpty) - _foregroundWorkThrottlingBeginStamp = _utcNow(); - - if(_foregroundWorkPending == null) - _foregroundWorkPending = new CancellationTokenSource(); - - _foregroundWork = _foregroundWork.Union(documentIds); + if(workType == AnalyzerWorkType.Background) + { + if(_backgroundWork.IsEmpty) + _backgroundThrottlingBeginStamp = _utcNow(); + + _backgroundWork = _backgroundWork.Union(documentIds); + } + else + { + if(_foregroundWork.IsEmpty) + _foregroundWorkThrottlingBeginStamp = _utcNow(); + + if(_foregroundWorkPending == null) + _foregroundWorkPending = new CancellationTokenSource(); + + _foregroundWork = _foregroundWork.Union(documentIds); + } } } @@ -68,7 +73,7 @@ private IReadOnlyCollection TakeForegroundWork() if (IsForegroundThrottlingActive() || _foregroundWork.IsEmpty) return ImmutableHashSet.Empty; - lock (_foregroundWork) + lock (_queueLock) { var currentWork = _foregroundWork; _foregroundWork = ImmutableHashSet.Empty; @@ -81,7 +86,7 @@ private IReadOnlyCollection TakeBackgroundWork() if (IsBackgroundThrottlineActive() || _backgroundWork.IsEmpty) return ImmutableHashSet.Empty; - lock (_backgroundWork) + lock (_queueLock) { var currentWork = _backgroundWork; _backgroundWork = ImmutableHashSet.Empty; @@ -101,7 +106,7 @@ private bool IsBackgroundThrottlineActive() public void ForegroundWorkComplete() { - lock(_foregroundWork) + lock(_queueLock) { if(_foregroundWorkPending == null) return; @@ -115,10 +120,30 @@ public void ForegroundWorkComplete() // like it's syncronous even that actual analysis may take a while. public Task WaitForegroundWorkComplete() { - if(_foregroundWorkPending == null || _foregroundWork.IsEmpty) + if(_foregroundWorkPending == null && _foregroundWork.IsEmpty) return Task.CompletedTask; - return Task.Delay(_maximumDelayWhenWaitingForResults, _foregroundWorkPending.Token); + return Task.Delay(_maximumDelayWhenWaitingForResults, _foregroundWorkPending.Token) + .ContinueWith(task => LogTimeouts(task)); + } + + public bool TryPromote(DocumentId id) + { + lock(_queueLock) + { + if(_backgroundWork.Contains(id)) + { + _foregroundWork = _foregroundWork.Add(id); + return true; + } + } + + return false; + } + + private void LogTimeouts(Task task) + { + if (!task.IsCanceled) _logger.LogWarning($"Timeout before work got ready for foreground analysis queue."); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index dd396499bf..afdf323d35 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -59,7 +59,8 @@ public CSharpDiagnosticWorkerWithAnalyzers( _workspace.WorkspaceChanged += OnWorkspaceChanged; - Task.Factory.StartNew(Worker, TaskCreationOptions.LongRunning); + Task.Factory.StartNew(() => Worker(AnalyzerWorkType.Foreground), TaskCreationOptions.LongRunning); + Task.Factory.StartNew(() => Worker(AnalyzerWorkType.Background), TaskCreationOptions.LongRunning); } private Task InitializeWithWorkspaceDocumentsIfNotYetDone() @@ -93,8 +94,10 @@ private Task InitializeWithWorkspaceDocumentsIfNotYetDone() private async Task> GetDiagnosticsByDocumentIds(ImmutableArray documentIds) { if(documentIds.Length == 1) + { + _workQueue.TryPromote(documentIds.Single()); await _workQueue.WaitForegroundWorkComplete(); - + } return _currentDiagnosticResults .Where(x => documentIds.Any(docId => docId == x.Key)) .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.projectName, v))) @@ -108,7 +111,7 @@ private ImmutableArray GetDocumentIdsFromPaths(ImmutableArray (projectId: solution.GetDocument(documentId)?.Project?.Id, documentId)) .Where(x => x.projectId != null) .GroupBy(x => x.projectId, x => x.documentId) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 76b0083585..b87553f137 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -12,16 +12,51 @@ namespace OmniSharp.Roslyn.CSharp.Tests { public class AnalyzerWorkerQueueFacts { + private class Logger : ILogger + { + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + RecordedMessages = RecordedMessages.Add(state.ToString()); + } + + public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); + } + + private class LoggerFactory : ILoggerFactory + { + public Logger Logger { get; } = new Logger(); + + public void AddProvider(ILoggerProvider provider) + { + } + + public ILogger CreateLogger(string categoryName) + { + return Logger; + } + + public void Dispose() + { + } + } + [Theory] [InlineData(AnalyzerWorkType.Background)] [InlineData(AnalyzerWorkType.Foreground)] public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); var document = CreateTestDocumentId(); - queue.PutWork(new [] { document }, workType); + queue.PutWork(new[] { document }, workType); Assert.Empty(queue.TakeWork(workType)); } @@ -31,10 +66,10 @@ public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned(Analyzer public void WhenWorksIsAddedToQueueThenTheyWillBeReturned(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); var document = CreateTestDocumentId(); - queue.PutWork(new [] { document }, workType); + queue.PutWork(new[] { document }, workType); now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(workType); @@ -49,12 +84,12 @@ public void WhenWorksIsAddedToQueueThenTheyWillBeReturned(AnalyzerWorkType workT public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); var document = CreateTestDocumentId(); - queue.PutWork(new [] { document }, workType); - queue.PutWork(new [] { document }, workType); - queue.PutWork(new [] { document }, workType); + queue.PutWork(new[] { document }, workType); + queue.PutWork(new[] { document }, workType); + queue.PutWork(new[] { document }, workType); Assert.Empty(queue.TakeWork(workType)); @@ -70,10 +105,10 @@ public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne(AnalyzerW public void WhenForegroundWorkIsAddedThenWaitNextIterationOfItReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(utcNow: () => now, timeoutForPendingWorkMs: 500); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); var document = CreateTestDocumentId(); - queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); + queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); var pendingTask = queue.WaitForegroundWorkComplete(); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); @@ -92,10 +127,10 @@ public void WhenForegroundWorkIsAddedThenWaitNextIterationOfItReady() public void WhenForegroundWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(utcNow: () => now, timeoutForPendingWorkMs: 500); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); var document = CreateTestDocumentId(); - queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); + queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); now = PassOverThrotlingPeriod(now); @@ -114,10 +149,11 @@ public void WhenForegroundWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterat public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(utcNow: () => now, timeoutForPendingWorkMs: 20); + var loggerFactory = new LoggerFactory(); + var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); var document = CreateTestDocumentId(); - queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); + queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(AnalyzerWorkType.Foreground); @@ -128,6 +164,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() pendingTask.Wait(cts.Token); Assert.True(pendingTask.IsCompleted); + Assert.Contains("Timeout before work got ready", loggerFactory.Logger.RecordedMessages.Single()); } [Fact] @@ -135,15 +172,16 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(utcNow: () => now, timeoutForPendingWorkMs: 1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 1000); var parallelQueues = Enumerable.Range(0, 10) .Select(_ => - Task.Run(() => { + Task.Run(() => + { var document = CreateTestDocumentId(); - queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); + queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); now = PassOverThrotlingPeriod(now); @@ -154,7 +192,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp queue.WaitForegroundWorkComplete(); pendingTask.Wait(TimeSpan.FromMilliseconds(300)); - })) + })) .ToArray(); await Task.WhenAll(parallelQueues); @@ -166,10 +204,10 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); var document = CreateTestDocumentId(); - queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); + queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); now = PassOverThrotlingPeriod(now); @@ -179,7 +217,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe // User updates code -> document is queued again during period when theres already api call waiting // to continue. - queue.PutWork(new [] { document }, AnalyzerWorkType.Foreground); + queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); // First iteration of work is done. queue.ForegroundWorkComplete(); @@ -192,7 +230,25 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe } [Fact] - public async Task WhenBackgroundWorkIsAdded_DontWaitIt() + public Task WhenBackgroundWorkIsAdded_DontWaitIt() + { + throw new NotImplementedException(); + } + + [Fact] + public Task WhenSingleFileIsPromoted_ThenPromoteItFromBackgroundQueueToForeground() + { + throw new NotImplementedException(); + } + + [Fact] + public Task WhenFileIsntAtBackgroundQueueAndTriedToBePromoted_ThenDontDoNothing() + { + throw new NotImplementedException(); + } + + [Fact] + public Task WhenFileIsProcessingInBackgroundQueue_ThenPromoteItAsForeground() { throw new NotImplementedException(); } From f3a0a72dc9ab60c61ebc5d1b8172ccb2a157cf49 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 21 May 2019 20:05:21 +0300 Subject: [PATCH 06/26] Fixed issue with completing work when queue is empty. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 128 +++++++----------- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 5 +- .../AnalyzerWorkerQueueFacts.cs | 6 +- .../DiagnosticsFacts.cs | 26 +++- tests/TestUtility/TestHelpers.cs | 14 +- 5 files changed, 91 insertions(+), 88 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 398d201015..1052baeaee 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -12,21 +12,35 @@ namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics { public class AnalyzerWorkQueue { - private readonly TimeSpan _foregroundThrottling = TimeSpan.FromMilliseconds(300); - private readonly TimeSpan _backgroundThrottling = TimeSpan.FromMilliseconds(2000); - - private ImmutableHashSet _backgroundWork = ImmutableHashSet.Empty; - private ImmutableHashSet _foregroundWork = ImmutableHashSet.Empty; - private DateTime _foregroundWorkThrottlingBeginStamp = DateTime.UtcNow; - private DateTime _backgroundThrottlingBeginStamp = DateTime.UtcNow; - private CancellationTokenSource _foregroundWorkPending = new CancellationTokenSource(); + private class Queue + { + public Queue(TimeSpan throttling) + { + Throttling = throttling; + } + + public ImmutableHashSet WorkWaitingToExecute { get; set; } = ImmutableHashSet.Empty; + public ImmutableHashSet WorkExecuting { get; set; } = ImmutableHashSet.Empty; + public DateTime LastThrottlingBegan { get; set; } = DateTime.UtcNow; + public TimeSpan Throttling { get; } + public CancellationTokenSource WorkPendingToken { get; set; } + } + + private readonly Dictionary _queues = null; + private readonly ILogger _logger; private readonly Func _utcNow; private readonly int _maximumDelayWhenWaitingForResults; private readonly object _queueLock = new Object(); - public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 15*1000) + public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 15 * 1000) { + _queues = new Dictionary + { + { AnalyzerWorkType.Foreground, new Queue(TimeSpan.FromMilliseconds(300)) }, + { AnalyzerWorkType.Background, new Queue(TimeSpan.FromMilliseconds(2000)) } + }; + _logger = loggerFactory.CreateLogger(); _utcNow = utcNow ?? (() => DateTime.UtcNow); _maximumDelayWhenWaitingForResults = timeoutForPendingWorkMs; @@ -34,85 +48,45 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = n public void PutWork(IReadOnlyCollection documentIds, AnalyzerWorkType workType) { - lock(_queueLock) + lock (_queueLock) { - if(workType == AnalyzerWorkType.Background) - { - if(_backgroundWork.IsEmpty) - _backgroundThrottlingBeginStamp = _utcNow(); - - _backgroundWork = _backgroundWork.Union(documentIds); - } - else - { - if(_foregroundWork.IsEmpty) - _foregroundWorkThrottlingBeginStamp = _utcNow(); - - if(_foregroundWorkPending == null) - _foregroundWorkPending = new CancellationTokenSource(); - - _foregroundWork = _foregroundWork.Union(documentIds); - } - } - } + var queue = _queues[workType]; - public IReadOnlyCollection TakeWork(AnalyzerWorkType workType) - { - if(workType == AnalyzerWorkType.Foreground) - { - return TakeForegroundWork(); - } - else - { - return TakeBackgroundWork(); - } - } + if (queue.WorkWaitingToExecute.IsEmpty) + queue.LastThrottlingBegan = _utcNow(); - private IReadOnlyCollection TakeForegroundWork() - { - if (IsForegroundThrottlingActive() || _foregroundWork.IsEmpty) - return ImmutableHashSet.Empty; + if (queue.WorkPendingToken == null) + queue.WorkPendingToken = new CancellationTokenSource(); - lock (_queueLock) - { - var currentWork = _foregroundWork; - _foregroundWork = ImmutableHashSet.Empty; - return currentWork; + queue.WorkWaitingToExecute = queue.WorkWaitingToExecute.Union(documentIds); } } - private IReadOnlyCollection TakeBackgroundWork() + public IReadOnlyCollection TakeWork(AnalyzerWorkType workType) { - if (IsBackgroundThrottlineActive() || _backgroundWork.IsEmpty) + var queue = _queues[workType]; + + if (IsThrottlingActive(queue) || queue.WorkWaitingToExecute.IsEmpty) return ImmutableHashSet.Empty; lock (_queueLock) { - var currentWork = _backgroundWork; - _backgroundWork = ImmutableHashSet.Empty; - return currentWork; + queue.WorkExecuting = queue.WorkWaitingToExecute; + queue.WorkWaitingToExecute = ImmutableHashSet.Empty; + return queue.WorkExecuting; } } - private bool IsForegroundThrottlingActive() + private bool IsThrottlingActive(Queue queue) { - return (_utcNow() - _foregroundWorkThrottlingBeginStamp).TotalMilliseconds <= _foregroundThrottling.TotalMilliseconds; + return (_utcNow() - queue.LastThrottlingBegan).TotalMilliseconds <= queue.Throttling.TotalMilliseconds; } - private bool IsBackgroundThrottlineActive() + public void WorkComplete(AnalyzerWorkType workType) { - return (_utcNow() - _backgroundThrottlingBeginStamp).TotalMilliseconds <= _backgroundThrottling.TotalMilliseconds; - } - - public void ForegroundWorkComplete() - { - lock(_queueLock) - { - if(_foregroundWorkPending == null) - return; - - _foregroundWorkPending.Cancel(); - } + _queues[workType].WorkPendingToken?.Cancel(); + _queues[workType].WorkPendingToken = null; + _queues[workType].WorkExecuting = ImmutableHashSet.Empty; } // Omnisharp V2 api expects that it can request current information of diagnostics any time, @@ -120,22 +94,22 @@ public void ForegroundWorkComplete() // like it's syncronous even that actual analysis may take a while. public Task WaitForegroundWorkComplete() { - if(_foregroundWorkPending == null && _foregroundWork.IsEmpty) + var queue = _queues[AnalyzerWorkType.Foreground]; + + if (queue.WorkPendingToken == null || (queue.WorkPendingToken == null && queue.WorkWaitingToExecute.IsEmpty)) return Task.CompletedTask; - return Task.Delay(_maximumDelayWhenWaitingForResults, _foregroundWorkPending.Token) + return Task.Delay(_maximumDelayWhenWaitingForResults, queue.WorkPendingToken.Token) .ContinueWith(task => LogTimeouts(task)); } public bool TryPromote(DocumentId id) { - lock(_queueLock) + + if (_queues[AnalyzerWorkType.Background].WorkWaitingToExecute.Contains(id) || _queues[AnalyzerWorkType.Background].WorkExecuting.Contains(id)) { - if(_backgroundWork.Contains(id)) - { - _foregroundWork = _foregroundWork.Add(id); - return true; - } + PutWork(new [] { id }, AnalyzerWorkType.Foreground); + return true; } return false; diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index afdf323d35..88c38cc206 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -131,6 +131,9 @@ private async Task Worker(AnalyzerWorkType workType) await AnalyzeProject(solution, projectGroup); } + if(currentWorkGroupedByProjects.Any()) + _workQueue.WorkComplete(workType); + await Task.Delay(50); } catch (Exception ex) @@ -238,14 +241,12 @@ private async Task AnalyzeDocument(Project project, ImmutableArray diagnosticsWithAnalyzers) { _currentDiagnosticResults[document.Id] = (project.Name, diagnosticsWithAnalyzers); - _workQueue.ForegroundWorkComplete(); EmitDiagnostics(_currentDiagnosticResults[document.Id].diagnostics); } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index b87553f137..bf6f97ea17 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -118,7 +118,7 @@ public void WhenForegroundWorkIsAddedThenWaitNextIterationOfItReady() now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(AnalyzerWorkType.Foreground); - queue.ForegroundWorkComplete(); + queue.WorkComplete(AnalyzerWorkType.Foreground); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -140,7 +140,7 @@ public void WhenForegroundWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterat pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); - queue.ForegroundWorkComplete(); + queue.WorkComplete(AnalyzerWorkType.Foreground); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -220,7 +220,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); // First iteration of work is done. - queue.ForegroundWorkComplete(); + queue.WorkComplete(AnalyzerWorkType.Foreground); // Waiting call continues because it's iteration of work is done, even when theres next // already waiting. diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs index 3e4bffba8f..5bde9c4dba 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs @@ -41,17 +41,33 @@ private OmniSharpTestHost GetHost(bool roslynAnalyzersEnabled) return OmniSharpTestHost.Create(testOutput: _testOutput, configurationData: new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString() } }); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task CheckAllFiles(bool roslynAnalyzersEnabled) + [Fact] + public async Task CheckAllFilesOnNonAnalyzerReturnImmediatlyAllResults() { - using (var host = GetHost(roslynAnalyzersEnabled)) + using (var host = GetHost(roslynAnalyzersEnabled: false)) + { + host.AddFilesToWorkspace( + new TestFile("a.cs", "class C1 { int n = true; }"), + new TestFile("b.cs", "class C2 { int n = true; }")); + + var quickFixes = await host.RequestCodeCheckAsync(); + + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "a.cs"); + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "b.cs"); + } + } + + [Fact] + public async Task CheckAllFilesWithAnalyzersWillEventuallyReturnAllResults() + { + using (var host = GetHost(roslynAnalyzersEnabled: true)) { host.AddFilesToWorkspace( new TestFile("a.cs", "class C1 { int n = true; }"), new TestFile("b.cs", "class C2 { int n = true; }")); + await TestHelpers.WaitUntil(async () => (await host.RequestCodeCheckAsync()).QuickFixes.Any(), 50, timeout: 5000); + var quickFixes = await host.RequestCodeCheckAsync(); Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "a.cs"); diff --git a/tests/TestUtility/TestHelpers.cs b/tests/TestUtility/TestHelpers.cs index 260197aca3..73f3b75b5d 100644 --- a/tests/TestUtility/TestHelpers.cs +++ b/tests/TestUtility/TestHelpers.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Scripting.Hosting; @@ -121,7 +122,7 @@ public static MSBuildInstance AddDotNetCoreToFakeInstance(this MSBuildInstance i public static Dictionary GetConfigurationDataWithAnalyzerConfig(bool roslynAnalyzersEnabled = false, Dictionary existingConfiguration = null) { - if(existingConfiguration == null) + if (existingConfiguration == null) { return new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString() } }; } @@ -130,7 +131,18 @@ public static Dictionary GetConfigurationDataWithAnalyzerConfig( copyOfExistingConfigs.Add("RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString()); return copyOfExistingConfigs; + } + + public static async Task WaitUntil(Func> condition, int frequency = 25, int timeout = -1) + { + var waitTask = Task.Run(async () => + { + while (!await condition()) await Task.Delay(frequency); + }); + if (waitTask != await Task.WhenAny(waitTask, + Task.Delay(timeout))) + throw new TimeoutException(); } } } From 6eca1f55f067518d36dcbfe1725c0e71e5eb8602 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 21 May 2019 20:26:31 +0300 Subject: [PATCH 07/26] Testfixes. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 11 ++-- .../AnalyzerWorkerQueueFacts.cs | 56 ++++++++++++++++--- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 1052baeaee..96e4a71fbb 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -84,9 +84,12 @@ private bool IsThrottlingActive(Queue queue) public void WorkComplete(AnalyzerWorkType workType) { - _queues[workType].WorkPendingToken?.Cancel(); - _queues[workType].WorkPendingToken = null; - _queues[workType].WorkExecuting = ImmutableHashSet.Empty; + lock (_queueLock) + { + _queues[workType].WorkPendingToken?.Cancel(); + _queues[workType].WorkPendingToken = null; + _queues[workType].WorkExecuting = ImmutableHashSet.Empty; + } } // Omnisharp V2 api expects that it can request current information of diagnostics any time, @@ -108,7 +111,7 @@ public bool TryPromote(DocumentId id) if (_queues[AnalyzerWorkType.Background].WorkWaitingToExecute.Contains(id) || _queues[AnalyzerWorkType.Background].WorkExecuting.Contains(id)) { - PutWork(new [] { id }, AnalyzerWorkType.Foreground); + PutWork(new[] { id }, AnalyzerWorkType.Foreground); return true; } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index bf6f97ea17..b8818821f3 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -230,27 +230,67 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe } [Fact] - public Task WhenBackgroundWorkIsAdded_DontWaitIt() + public void WhenBackgroundWorkIsAdded_DontWaitIt() { - throw new NotImplementedException(); + var queue = new AnalyzerWorkQueue(new LoggerFactory()); + var document = CreateTestDocumentId(); + + queue.PutWork(new[] { document }, AnalyzerWorkType.Background); + + Assert.True(queue.WaitForegroundWorkComplete().IsCompleted); } [Fact] - public Task WhenSingleFileIsPromoted_ThenPromoteItFromBackgroundQueueToForeground() + public void WhenSingleFileIsPromoted_ThenPromoteItFromBackgroundQueueToForeground() { - throw new NotImplementedException(); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var document = CreateTestDocumentId(); + + queue.PutWork(new[] { document }, AnalyzerWorkType.Background); + + queue.TryPromote(document); + + now = PassOverThrotlingPeriod(now); + + Assert.NotEmpty(queue.TakeWork(AnalyzerWorkType.Foreground)); } [Fact] - public Task WhenFileIsntAtBackgroundQueueAndTriedToBePromoted_ThenDontDoNothing() + public void WhenFileIsntAtBackgroundQueueAndTriedToBePromoted_ThenDontDoNothing() { - throw new NotImplementedException(); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var document = CreateTestDocumentId(); + + queue.TryPromote(document); + + now = PassOverThrotlingPeriod(now); + + Assert.Empty(queue.TakeWork(AnalyzerWorkType.Foreground)); } [Fact] - public Task WhenFileIsProcessingInBackgroundQueue_ThenPromoteItAsForeground() + public void WhenFileIsProcessingInBackgroundQueue_ThenPromoteItAsForeground() { - throw new NotImplementedException(); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var document = CreateTestDocumentId(); + + queue.PutWork(new[] { document }, AnalyzerWorkType.Background); + + now = PassOverThrotlingPeriod(now); + + var activeWork = queue.TakeWork(AnalyzerWorkType.Background); + + queue.TryPromote(document); + + now = PassOverThrotlingPeriod(now); + + var foregroundWork = queue.TakeWork(AnalyzerWorkType.Foreground); + + Assert.NotEmpty(foregroundWork); + Assert.NotEmpty(activeWork); } private DocumentId CreateTestDocumentId() From 493f26065c326166fff102d3ad560238b52b0f0e Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 22 May 2019 19:01:33 +0300 Subject: [PATCH 08/26] Basic endpoint for reanalysis and test. --- .../Models/v1/ReAnalyze/ReanalyzeRequest.cs | 10 +++ .../Models/v1/ReAnalyze/ReanalyzeResponse.cs | 6 ++ .../OmniSharpEndpoints.cs | 2 + .../Services/Diagnostics/ReAnalyzeService.cs | 29 +++++++++ .../CustomRoslynAnalyzerFacts.cs | 4 +- .../ReAnalysisFacts.cs | 63 +++++++++++++++++++ 6 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs create mode 100644 src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeResponse.cs create mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs create mode 100644 tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs diff --git a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs new file mode 100644 index 0000000000..4880b101c7 --- /dev/null +++ b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs @@ -0,0 +1,10 @@ +using OmniSharp.Mef; +using OmniSharp.Models; + +namespace OmniSharp.Abstractions.Models.V1.ReAnalyze +{ + [OmniSharpEndpoint(OmniSharpEndpoints.ReAnalyze, typeof(ReAnalyzeRequest), typeof(ReanalyzeResponse))] + public class ReAnalyzeRequest: IRequest + { + } +} \ No newline at end of file diff --git a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeResponse.cs b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeResponse.cs new file mode 100644 index 0000000000..b40bd089ae --- /dev/null +++ b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeResponse.cs @@ -0,0 +1,6 @@ +namespace OmniSharp.Abstractions.Models.V1.ReAnalyze +{ + public class ReanalyzeResponse + { + } +} \ No newline at end of file diff --git a/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs b/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs index ca07b0ff17..18eb6e237b 100644 --- a/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs +++ b/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs @@ -43,6 +43,8 @@ public static class OmniSharpEndpoints public const string Close = "/close"; public const string Diagnostics = "/diagnostics"; + public const string ReAnalyze = "/reanalyze"; + public static class V2 { public const string GetCodeActions = "/v2/getcodeactions"; diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs new file mode 100644 index 0000000000..bfd469d7b9 --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs @@ -0,0 +1,29 @@ +using System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using OmniSharp.Abstractions.Models.V1.ReAnalyze; +using OmniSharp.Mef; +using OmniSharp.Models; +using OmniSharp.Models.Diagnostics; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; + +namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics +{ + [OmniSharpHandler(OmniSharpEndpoints.ReAnalyze, LanguageNames.CSharp)] + public class ReAnalyzeService : IRequestHandler + { + private readonly ICsDiagnosticWorker _diagWorker; + + [ImportingConstructor] + public ReAnalyzeService(ICsDiagnosticWorker diagWorker) + { + _diagWorker = diagWorker; + } + + public Task Handle(ReAnalyzeRequest request) + { + _diagWorker.QueueAllDocumentsForDiagnostics(); + return Task.FromResult(new ReanalyzeResponse()); + } + } +} diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index 99649303e9..7cb678ac52 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -98,7 +98,7 @@ public async Task When_custom_analyzers_are_executed_then_return_results() { using (var host = GetHost()) { - var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var testFile = new TestFile("testFile.cs", "public class _this_is_invalid_test_class_name {}"); host.AddFilesToWorkspace(testFile); @@ -106,7 +106,7 @@ public async Task When_custom_analyzers_are_executed_then_return_results() AddProjectWitFile(host, testFile, testAnalyzerRef); - var result = await host.RequestCodeCheckAsync(); + var result = await host.RequestCodeCheckAsync("testFile.cs"); Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs new file mode 100644 index 0000000000..4feda13dae --- /dev/null +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -0,0 +1,63 @@ +using System.Linq; +using System.Threading.Tasks; +using OmniSharp.Abstractions.Models.V1.ReAnalyze; +using OmniSharp.Models.ChangeBuffer; +using OmniSharp.Roslyn.CSharp.Services.Buffer; +using OmniSharp.Roslyn.CSharp.Services.Diagnostics; +using TestUtility; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.Roslyn.CSharp.Tests +{ + public class ReAnalysisFacts + { +private readonly ITestOutputHelper _testOutput; + + public ReAnalysisFacts(ITestOutputHelper testOutput) + { + _testOutput = testOutput; + } + + + [Fact] + public async Task WhenReAnalyzeIsExecutedForAll_ThenReanalyzeAllFiles() + { + using (var host = GetHost()) + { + var changeBufferHandler = host.GetRequestHandler(OmniSharpEndpoints.ChangeBuffer); + var reAnalyzeHandler = host.GetRequestHandler(OmniSharpEndpoints.ReAnalyze); + + host.AddFilesToWorkspace(new TestFile("a.cs", "public class A: B { }"), new TestFile("b.cs", "public class B { }")); + + await host.RequestCodeCheckAsync("a.cs"); + + var newContent = "ThisDoesntContainValidReferenceAsBClassAnyMore"; + + await changeBufferHandler.Handle(new ChangeBufferRequest() + { + StartLine = 0, + StartColumn = 0, + EndLine = 0, + EndColumn = newContent.Length, + NewText = newContent, + FileName = "b.cs" + }); + + await reAnalyzeHandler.Handle(new ReAnalyzeRequest()); + + var quickFixes = await host.RequestCodeCheckAsync("a.cs"); + + // Reference to B is lost, a.cs should contain error about invalid reference to it. + // error CS0246: The type or namespace name 'B' could not be found + Assert.Contains(quickFixes.QuickFixes.Select(x => x.ToString()), x => x.Contains("CS0246")); + } + } + + private OmniSharpTestHost GetHost() + { + return OmniSharpTestHost.Create(testOutput: _testOutput, + configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled: true)); + } + } +} \ No newline at end of file From d1e3ea521e30f05fa60b7af8c6fb902f0ed50682 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 25 May 2019 11:45:00 +0300 Subject: [PATCH 09/26] Added message on project analyzed to events. --- .../Eventing/IEventEmitterExtensions.cs | 10 ++++++++++ .../Models/Events/EventTypes.cs | 2 ++ .../Models/Events/PackageRestoreMessage.cs | 5 +++++ .../Models/v1/ReAnalyze/ReanalyzeRequest.cs | 5 +++++ .../Models/v1/ReAnalyze/ReanalyzeResponse.cs | 5 ++++- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 13 ++++++++----- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 9 ++++++--- src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs | 5 +++++ 8 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/OmniSharp.Abstractions/Eventing/IEventEmitterExtensions.cs b/src/OmniSharp.Abstractions/Eventing/IEventEmitterExtensions.cs index aeb2dfd816..8ea017c005 100644 --- a/src/OmniSharp.Abstractions/Eventing/IEventEmitterExtensions.cs +++ b/src/OmniSharp.Abstractions/Eventing/IEventEmitterExtensions.cs @@ -34,6 +34,16 @@ public static void RestoreFinished(this IEventEmitter emitter, string projectPat }); } + public static void ProjectAnalyzed(this IEventEmitter emitter, string projectPath) + { + emitter.Emit( + EventTypes.PackageRestoreFinished, + new PackageRestoreMessage + { + FileName = projectPath, + }); + } + public static void UnresolvedDepdendencies(this IEventEmitter emitter, string projectFilePath, IEnumerable unresolvedDependencies) { emitter.Emit( diff --git a/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs b/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs index 8d68a21401..85cb8e19bb 100644 --- a/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs +++ b/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs @@ -11,5 +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 ProjectAnalyzed = nameof(ProjectAnalyzed); + } } diff --git a/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs b/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs index 32a52b9bca..7a5b365d08 100644 --- a/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs +++ b/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs @@ -5,4 +5,9 @@ public class PackageRestoreMessage public string FileName { get; set; } public bool Succeeded { get; set; } } + + public class ProjectAnalyzedMessage + { + public string ProjectFileName { get; set; } + } } \ No newline at end of file diff --git a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs index 4880b101c7..fcdfb92d0b 100644 --- a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs +++ b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs @@ -6,5 +6,10 @@ namespace OmniSharp.Abstractions.Models.V1.ReAnalyze [OmniSharpEndpoint(OmniSharpEndpoints.ReAnalyze, typeof(ReAnalyzeRequest), typeof(ReanalyzeResponse))] public class ReAnalyzeRequest: IRequest { + // This is used as context to resolve which project should be analyzed, simplifies + // clients since they don't have to figure out what is correct project for current open file. + // This is information which is available in omnisharp workspace and so it should be + // used as source of truth. + public string CurrentOpenFilePathAsContext { get; set; } } } \ No newline at end of file diff --git a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeResponse.cs b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeResponse.cs index b40bd089ae..02623e0080 100644 --- a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeResponse.cs +++ b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeResponse.cs @@ -1,6 +1,9 @@ +using OmniSharp.Models; + namespace OmniSharp.Abstractions.Models.V1.ReAnalyze { - public class ReanalyzeResponse + public class ReanalyzeResponse : IAggregateResponse { + public IAggregateResponse Merge(IAggregateResponse response) { return response; } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 96e4a71fbb..31788eed74 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -64,13 +64,13 @@ public void PutWork(IReadOnlyCollection documentIds, AnalyzerWorkTyp public IReadOnlyCollection TakeWork(AnalyzerWorkType workType) { - var queue = _queues[workType]; - - if (IsThrottlingActive(queue) || queue.WorkWaitingToExecute.IsEmpty) - return ImmutableHashSet.Empty; - lock (_queueLock) { + var queue = _queues[workType]; + + if (IsThrottlingActive(queue) || queue.WorkWaitingToExecute.IsEmpty) + return ImmutableHashSet.Empty; + queue.WorkExecuting = queue.WorkWaitingToExecute; queue.WorkWaitingToExecute = ImmutableHashSet.Empty; return queue.WorkExecuting; @@ -86,6 +86,9 @@ public void WorkComplete(AnalyzerWorkType workType) { lock (_queueLock) { + if(_queues[workType].WorkExecuting.IsEmpty) + return; + _queues[workType].WorkPendingToken?.Cancel(); _queues[workType].WorkPendingToken = null; _queues[workType].WorkExecuting = ImmutableHashSet.Empty; diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 88c38cc206..4109591f28 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -129,10 +129,13 @@ private async Task Worker(AnalyzerWorkType workType) foreach (var projectGroup in currentWorkGroupedByProjects) { await AnalyzeProject(solution, projectGroup); - } - if(currentWorkGroupedByProjects.Any()) - _workQueue.WorkComplete(workType); + if(workType == AnalyzerWorkType.Background) + { + var projectPath = solution.GetProject(projectGroup.Key).FilePath; + _forwarder.ProjectAnalyzedInBackground(projectPath); + } + } await Task.Delay(50); } diff --git a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs index 9c5b45ec61..e606094385 100644 --- a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs +++ b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs @@ -22,5 +22,10 @@ public void Forward(DiagnosticMessage message) { _emitter.Emit(EventTypes.Diagnostic, message); } + + public void ProjectAnalyzedInBackground(string projectFileName) + { + _emitter.Emit(EventTypes.ProjectAnalyzed, new ProjectAnalyzedMessage { ProjectFileName = projectFileName }); + } } } From e036d04e36ced5bbdb458f5611894bce63ae3abc Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 25 May 2019 12:02:40 +0300 Subject: [PATCH 10/26] Fixed mistake. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 4 ++-- .../Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 31788eed74..e2874eda65 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -37,8 +37,8 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = n { _queues = new Dictionary { - { AnalyzerWorkType.Foreground, new Queue(TimeSpan.FromMilliseconds(300)) }, - { AnalyzerWorkType.Background, new Queue(TimeSpan.FromMilliseconds(2000)) } + { AnalyzerWorkType.Foreground, new Queue(TimeSpan.FromMilliseconds(150)) }, + { AnalyzerWorkType.Background, new Queue(TimeSpan.FromMilliseconds(1500)) } }; _logger = loggerFactory.CreateLogger(); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 4109591f28..c6a5e5496d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -137,6 +137,8 @@ private async Task Worker(AnalyzerWorkType workType) } } + _workQueue.WorkComplete(workType); + await Task.Delay(50); } catch (Exception ex) From 5b79b77f5ef28b4c5deaafb48bff31ceb66559c0 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 25 May 2019 18:33:03 +0300 Subject: [PATCH 11/26] Added test for emitted event about project analyzed. --- .../DiagnosticsV2Facts.cs | 6 ++-- .../ReAnalysisFacts.cs | 25 ++++++++++++++-- ...sticTestEmitter.cs => TestEventEmitter.cs} | 29 +++++++++---------- tests/TestUtility/OmniSharpTestHost.cs | 6 ++-- 4 files changed, 43 insertions(+), 23 deletions(-) rename tests/OmniSharp.Roslyn.CSharp.Tests/{DiagnosticsV2Facts.DiagnosticTestEmitter.cs => TestEventEmitter.cs} (63%) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index 72712fec6f..9f90b9bfa8 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -27,7 +27,7 @@ public async Task CodeCheckSpecifiedFileOnly(string filename) var testFile = new TestFile(filename, "class C { int n = true; }"); - var emitter = new DiagnosticTestEmitter(); + var emitter = new TestEventEmitter(); var forwarder = new DiagnosticEventForwarder(emitter) { IsEnabled = true @@ -58,7 +58,7 @@ public async Task CheckAllFiles(string filename1, string filename2) var testFile2 = new TestFile(filename2, "class C2 { int n = true; }"); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile1, testFile2); - var emitter = new DiagnosticTestEmitter(); + var emitter = new TestEventEmitter(); var forwarder = new DiagnosticEventForwarder(emitter); var service = CreateDiagnosticService(forwarder); @@ -82,7 +82,7 @@ public async Task EnablesWhenEndPointIsHit(string filename1, string filename2) var testFile2 = new TestFile(filename2, "class C2 { int n = true; }"); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile1, testFile2); - var emitter = new DiagnosticTestEmitter(); + var emitter = new TestEventEmitter(); var forwarder = new DiagnosticEventForwarder(emitter); var service = CreateDiagnosticService(forwarder); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs index 4feda13dae..fb52120ce4 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using OmniSharp.Abstractions.Models.V1.ReAnalyze; using OmniSharp.Models.ChangeBuffer; +using OmniSharp.Models.Events; using OmniSharp.Roslyn.CSharp.Services.Buffer; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using TestUtility; @@ -12,11 +13,13 @@ namespace OmniSharp.Roslyn.CSharp.Tests { public class ReAnalysisFacts { -private readonly ITestOutputHelper _testOutput; + private readonly ITestOutputHelper _testOutput; + private readonly TestEventEmitter _eventListener; public ReAnalysisFacts(ITestOutputHelper testOutput) { _testOutput = testOutput; + _eventListener = new TestEventEmitter(); } @@ -54,10 +57,28 @@ await changeBufferHandler.Handle(new ChangeBufferRequest() } } + [Fact] + public async Task WhenReanalyzeIsExecuted_ThenSendEventWhenAnalysisOfProjectIsReady() + { + using (var host = GetHost()) + { + var reAnalyzeHandler = host.GetRequestHandler(OmniSharpEndpoints.ReAnalyze); + + host.AddFilesToWorkspace(new TestFile("a.cs", "public class A { }")); + var project = host.Workspace.CurrentSolution.Projects.First(); + + _eventListener.Clear(); + + await reAnalyzeHandler.Handle(new ReAnalyzeRequest()); + + await _eventListener.ExpectForEmitted(x => x.ProjectFileName == project.FilePath); + } + } + private OmniSharpTestHost GetHost() { return OmniSharpTestHost.Create(testOutput: _testOutput, - configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled: true)); + configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled: true), eventEmitter: _eventListener); } } } \ No newline at end of file diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/TestEventEmitter.cs similarity index 63% rename from tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs rename to tests/OmniSharp.Roslyn.CSharp.Tests/TestEventEmitter.cs index 5136f0adbf..da0e41afc2 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/TestEventEmitter.cs @@ -1,23 +1,18 @@ -using System; +using System; using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using OmniSharp.Eventing; -using OmniSharp.Models.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Tests { - public partial class DiagnosticsV2Facts - { - private class DiagnosticTestEmitter : IEventEmitter + public class TestEventEmitter : IEventEmitter { - public readonly ConcurrentBag Messages = new ConcurrentBag(); + public ImmutableArray Messages { get; private set; } = ImmutableArray.Empty; - private readonly TaskCompletionSource _tcs; - - public async Task ExpectForEmitted(Expression> predicate) + public async Task ExpectForEmitted(Expression> predicate) { var asCompiledPredicate = predicate.Compile(); @@ -32,18 +27,20 @@ public async Task ExpectForEmitted(Expression> pred await Task.Delay(250); } - throw new InvalidOperationException($"Timeout reached before expected event count reached before prediction {predicate} came true, current diagnostics '{String.Join(";", Messages.SelectMany(x => x.Results))}'"); + throw new InvalidOperationException($"Timeout reached before expected event count reached before prediction {predicate} came true, current diagnostics '{String.Join(";", Messages)}'"); } - public DiagnosticTestEmitter() + public void Clear() { - _tcs = new TaskCompletionSource(); + Messages = ImmutableArray.Empty; } public void Emit(string kind, object args) { - Messages.Add((DiagnosticMessage)args); + if(args is T asT) + { + Messages = Messages.Add(asT); + } } } - } -} +} \ No newline at end of file diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index e81b14f6e6..6bef1ad06a 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -13,6 +13,7 @@ using OmniSharp.Cake; using OmniSharp.DotNet; using OmniSharp.DotNetTest.Models; +using OmniSharp.Eventing; using OmniSharp.Mef; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.MSBuild; @@ -102,11 +103,12 @@ public static OmniSharpTestHost Create( IEnumerable> configurationData = null, DotNetCliVersion dotNetCliVersion = DotNetCliVersion.Current, IEnumerable additionalExports = null, - [CallerMemberName] string callerName = "") + [CallerMemberName] string callerName = "", + IEventEmitter eventEmitter = null) { var environment = new OmniSharpEnvironment(path, logLevel: LogLevel.Trace); - var serviceProvider = TestServiceProvider.Create(testOutput, environment, configurationData, dotNetCliVersion); + var serviceProvider = TestServiceProvider.Create(testOutput, environment, configurationData, dotNetCliVersion, eventEmitter); return Create(serviceProvider, additionalExports, callerName); } From 26ad1fa77b7a03f12fcca2eb0a42b2ea6c78bcc6 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 25 May 2019 18:33:57 +0300 Subject: [PATCH 12/26] Renamed property at event message. --- .../Models/Events/PackageRestoreMessage.cs | 2 +- src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs | 2 +- tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs b/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs index 7a5b365d08..0f7001e1cd 100644 --- a/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs +++ b/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs @@ -8,6 +8,6 @@ public class PackageRestoreMessage public class ProjectAnalyzedMessage { - public string ProjectFileName { get; set; } + public string ProjectFilePath { get; set; } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs index e606094385..769255e8d0 100644 --- a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs +++ b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs @@ -25,7 +25,7 @@ public void Forward(DiagnosticMessage message) public void ProjectAnalyzedInBackground(string projectFileName) { - _emitter.Emit(EventTypes.ProjectAnalyzed, new ProjectAnalyzedMessage { ProjectFileName = projectFileName }); + _emitter.Emit(EventTypes.ProjectAnalyzed, new ProjectAnalyzedMessage { ProjectFilePath = projectFileName }); } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs index fb52120ce4..77bfeb9fdc 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -71,7 +71,7 @@ public async Task WhenReanalyzeIsExecuted_ThenSendEventWhenAnalysisOfProjectIsRe await reAnalyzeHandler.Handle(new ReAnalyzeRequest()); - await _eventListener.ExpectForEmitted(x => x.ProjectFileName == project.FilePath); + await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == project.FilePath); } } From 9a6b3ac268dbfe54cff6bd65a0061bb152f9f743 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 26 May 2019 08:54:59 +0300 Subject: [PATCH 13/26] Support for project scoped analysis. --- .../Models/v1/ReAnalyze/ReanalyzeRequest.cs | 4 +- .../Services/Diagnostics/ReAnalyzeService.cs | 23 ++++++- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 2 +- .../Diagnostics/CSharpDiagnosticWorker.cs | 9 ++- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 7 +++ .../CsharpDiagnosticWorkerComposer.cs | 5 ++ .../Diagnostics/ICsDiagnosticWorker.cs | 1 + .../ReAnalysisFacts.cs | 60 ++++++++++++++++++- tests/TestUtility/OmniSharpTestHost.cs | 6 +- 9 files changed, 106 insertions(+), 11 deletions(-) diff --git a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs index fcdfb92d0b..c363b3b6d7 100644 --- a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs +++ b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs @@ -6,10 +6,8 @@ namespace OmniSharp.Abstractions.Models.V1.ReAnalyze [OmniSharpEndpoint(OmniSharpEndpoints.ReAnalyze, typeof(ReAnalyzeRequest), typeof(ReanalyzeResponse))] public class ReAnalyzeRequest: IRequest { - // This is used as context to resolve which project should be analyzed, simplifies + // This document path is used as context to resolve which project should be analyzed. Simplifies // clients since they don't have to figure out what is correct project for current open file. - // This is information which is available in omnisharp workspace and so it should be - // used as source of truth. public string CurrentOpenFilePathAsContext { get; set; } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs index bfd469d7b9..74ac626010 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs @@ -1,4 +1,7 @@ +using System; +using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using OmniSharp.Abstractions.Models.V1.ReAnalyze; @@ -13,16 +16,32 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class ReAnalyzeService : IRequestHandler { private readonly ICsDiagnosticWorker _diagWorker; + private readonly OmniSharpWorkspace _workspace; [ImportingConstructor] - public ReAnalyzeService(ICsDiagnosticWorker diagWorker) + public ReAnalyzeService(ICsDiagnosticWorker diagWorker, OmniSharpWorkspace workspace) { _diagWorker = diagWorker; + _workspace = workspace; } public Task Handle(ReAnalyzeRequest request) { - _diagWorker.QueueAllDocumentsForDiagnostics(); + if(!string.IsNullOrEmpty(request.CurrentOpenFilePathAsContext)) + { + var currentSolution = _workspace.CurrentSolution; + + var projectIds = currentSolution + .GetDocumentIdsWithFilePath(request.CurrentOpenFilePathAsContext) + .Select(docId => currentSolution.GetDocument(docId).Project.Id) + .ToImmutableArray(); + + _diagWorker.QueueDocumentsOfProjectsForDiagnostics(projectIds); + } + else + { + _diagWorker.QueueAllDocumentsForDiagnostics(); + } return Task.FromResult(new ReanalyzeResponse()); } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index e2874eda65..1d261c874f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -95,7 +95,7 @@ public void WorkComplete(AnalyzerWorkType workType) } } - // Omnisharp V2 api expects that it can request current information of diagnostics any time, + // Omnisharp V2 api expects that it can request current information of diagnostics any time (single file/current document), // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. public Task WaitForegroundWorkComplete() diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 28f1e5db70..ef1b7cbf59 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -186,7 +186,14 @@ private static async Task> GetDiagnosticsForDocument( public ImmutableArray QueueAllDocumentsForDiagnostics() { - var documents = _workspace.CurrentSolution.Projects.SelectMany(x => x.Documents).ToImmutableArray(); + var documents = _workspace.CurrentSolution.Projects.SelectMany(x => x.Documents); + QueueForDiagnosis(documents.Select(x => x.FilePath).ToImmutableArray()); + return documents.Select(x => x.Id).ToImmutableArray(); + } + + public ImmutableArray QueueDocumentsOfProjectsForDiagnostics(ImmutableArray projectIds) + { + var documents = projectIds.SelectMany(projectId => _workspace.CurrentSolution.GetProject(projectId).Documents); QueueForDiagnosis(documents.Select(x => x.FilePath).ToImmutableArray()); return documents.Select(x => x.Id).ToImmutableArray(); } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index c6a5e5496d..108f03da99 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -283,5 +283,12 @@ public ImmutableArray QueueAllDocumentsForDiagnostics() var allDocumentsIds = _workspace.CurrentSolution.Projects.SelectMany(x => x.DocumentIds).ToImmutableArray(); return await GetDiagnosticsByDocumentIds(allDocumentsIds); } + + public ImmutableArray QueueDocumentsOfProjectsForDiagnostics(ImmutableArray projectIds) + { + var documentIds = projectIds.SelectMany(projectId => _workspace.CurrentSolution.GetProject(projectId).Documents.Select(x => x.Id)).ToImmutableArray(); + QueueForAnalysis(documentIds, AnalyzerWorkType.Background); + return documentIds; + } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs index f138d4da93..8c72311277 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs @@ -63,5 +63,10 @@ public ImmutableArray QueueAllDocumentsForDiagnostics() { return _implementation.QueueAllDocumentsForDiagnostics(); } + + public ImmutableArray QueueDocumentsOfProjectsForDiagnostics(ImmutableArray projectIds) + { + return _implementation.QueueDocumentsOfProjectsForDiagnostics(projectIds); + } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs index 07f6046823..d4e1da866b 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs @@ -9,5 +9,6 @@ public interface ICsDiagnosticWorker Task> GetDiagnostics(ImmutableArray documentPaths); Task> GetAllDiagnosticsAsync(); ImmutableArray QueueAllDocumentsForDiagnostics(); + ImmutableArray QueueDocumentsOfProjectsForDiagnostics(ImmutableArray projectId); } } \ No newline at end of file diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs index 77bfeb9fdc..9ff116c69e 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -64,8 +64,8 @@ public async Task WhenReanalyzeIsExecuted_ThenSendEventWhenAnalysisOfProjectIsRe { var reAnalyzeHandler = host.GetRequestHandler(OmniSharpEndpoints.ReAnalyze); - host.AddFilesToWorkspace(new TestFile("a.cs", "public class A { }")); - var project = host.Workspace.CurrentSolution.Projects.First(); + var projectId = host.AddFilesToWorkspace(new TestFile("a.cs", "public class A { }")).First(); + var project = host.Workspace.CurrentSolution.GetProject(projectId); _eventListener.Clear(); @@ -75,6 +75,62 @@ public async Task WhenReanalyzeIsExecuted_ThenSendEventWhenAnalysisOfProjectIsRe } } + [Fact] + public async Task WhenReanalyzeIsExecutedForFileInProject_ThenOnlyAnalyzeSelectedProject() + { + using (var host = GetHost()) + { + var reAnalyzeHandler = host.GetRequestHandler(OmniSharpEndpoints.ReAnalyze); + + var projectAId = host.AddFilesToWorkspace(new TestFile("a.cs", "public class A { }")).First(); + var projectA = host.Workspace.CurrentSolution.GetProject(projectAId); + + var projectBId = host.AddFilesToWorkspace(new TestFile("b.cs", "public class B { }")).First(); + var projectB = host.Workspace.CurrentSolution.GetProject(projectBId); + + _eventListener.Clear(); + + await reAnalyzeHandler.Handle(new ReAnalyzeRequest + { + CurrentOpenFilePathAsContext = projectA.Documents.Single(x => x.FilePath.EndsWith("a.cs")).FilePath + }); + + await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == projectA.FilePath); + Assert.DoesNotContain(_eventListener.Messages, x => x.ProjectFilePath == projectB.FilePath); + + _eventListener.Clear(); + + await reAnalyzeHandler.Handle(new ReAnalyzeRequest + { + CurrentOpenFilePathAsContext = projectB.Documents.Single(x => x.FilePath.EndsWith("a.cs")).FilePath + }); + + await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == projectB.FilePath); + Assert.DoesNotContain(_eventListener.Messages, x => x.ProjectFilePath == projectA.FilePath); + } + } + + [Fact] + public async Task WhenCurrentFileIsProjectItself_ThenReAnalyzeItAsExpected() + { + using (var host = GetHost()) + { + var reAnalyzeHandler = host.GetRequestHandler(OmniSharpEndpoints.ReAnalyze); + + var projectId = host.AddFilesToWorkspace(new TestFile("a.cs", "public class A { }")).First(); + var project = host.Workspace.CurrentSolution.GetProject(projectId); + + _eventListener.Clear(); + + await reAnalyzeHandler.Handle(new ReAnalyzeRequest + { + CurrentOpenFilePathAsContext = project.FilePath + }); + + await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == project.FilePath); + } + } + private OmniSharpTestHost GetHost() { return OmniSharpTestHost.Create(testOutput: _testOutput, diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 6bef1ad06a..7c2a4eadd3 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -129,9 +129,9 @@ public THandler GetRequestHandler(string name, string languageName = L return (THandler)_handlers[(name, languageName)].Value; } - public void AddFilesToWorkspace(params TestFile[] testFiles) + public IEnumerable AddFilesToWorkspace(params TestFile[] testFiles) { - TestHelpers.AddProjectToWorkspace( + var projects = TestHelpers.AddProjectToWorkspace( this.Workspace, "project.json", new[] { "dnx451", "dnxcore50" }, @@ -141,6 +141,8 @@ public void AddFilesToWorkspace(params TestFile[] testFiles) { TestHelpers.AddCsxProjectToWorkspace(Workspace, csxFile); } + + return projects; } public void ClearWorkspace() From bfe17de4b0b7fa6bca954274a3f1656f2557e272 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 26 May 2019 12:37:52 +0300 Subject: [PATCH 14/26] Testfixes and support for project file path as analysis context. --- .../Services/Diagnostics/ReAnalyzeService.cs | 34 ++++++++++++++++--- .../ReAnalysisFacts.cs | 16 +-------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs index 74ac626010..96233792a3 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -27,14 +28,13 @@ public ReAnalyzeService(ICsDiagnosticWorker diagWorker, OmniSharpWorkspace works public Task Handle(ReAnalyzeRequest request) { + if(!string.IsNullOrEmpty(request.CurrentOpenFilePathAsContext)) { var currentSolution = _workspace.CurrentSolution; - var projectIds = currentSolution - .GetDocumentIdsWithFilePath(request.CurrentOpenFilePathAsContext) - .Select(docId => currentSolution.GetDocument(docId).Project.Id) - .ToImmutableArray(); + var projectIds = WhenRequestIsProjectFileItselfGetFilesFromIt(request.CurrentOpenFilePathAsContext, currentSolution) + ?? GetDocumentsFromProjectWhereDocumentIs(request.CurrentOpenFilePathAsContext, currentSolution); _diagWorker.QueueDocumentsOfProjectsForDiagnostics(projectIds); } @@ -44,5 +44,31 @@ public Task Handle(ReAnalyzeRequest request) } return Task.FromResult(new ReanalyzeResponse()); } + + private ImmutableArray? WhenRequestIsProjectFileItselfGetFilesFromIt(string currentOpenFilePathAsContext, Solution currentSolution) + { + var projects = currentSolution.Projects.Where(x => CompareProjectPath(currentOpenFilePathAsContext, x)).Select(x => x.Id).ToImmutableArray(); + + if(!projects.Any()) + return null; + + return projects; + } + + private static bool CompareProjectPath(string currentOpenFilePathAsContext, Project x) + { + return String.Compare( + x.FilePath, + currentOpenFilePathAsContext, + StringComparison.InvariantCultureIgnoreCase) == 0; + } + + private static ImmutableArray GetDocumentsFromProjectWhereDocumentIs(string currentOpenFilePathAsContext, Solution currentSolution) + { + return currentSolution + .GetDocumentIdsWithFilePath(currentOpenFilePathAsContext) + .Select(docId => currentSolution.GetDocument(docId).Project.Id) + .ToImmutableArray(); + } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs index 9ff116c69e..2776945397 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -76,7 +76,7 @@ public async Task WhenReanalyzeIsExecuted_ThenSendEventWhenAnalysisOfProjectIsRe } [Fact] - public async Task WhenReanalyzeIsExecutedForFileInProject_ThenOnlyAnalyzeSelectedProject() + public async Task WhenReanalyzeIsExecutedForFileInProject_ThenOnlyAnalyzeProject() { using (var host = GetHost()) { @@ -85,9 +85,6 @@ public async Task WhenReanalyzeIsExecutedForFileInProject_ThenOnlyAnalyzeSelecte var projectAId = host.AddFilesToWorkspace(new TestFile("a.cs", "public class A { }")).First(); var projectA = host.Workspace.CurrentSolution.GetProject(projectAId); - var projectBId = host.AddFilesToWorkspace(new TestFile("b.cs", "public class B { }")).First(); - var projectB = host.Workspace.CurrentSolution.GetProject(projectBId); - _eventListener.Clear(); await reAnalyzeHandler.Handle(new ReAnalyzeRequest @@ -96,17 +93,6 @@ await reAnalyzeHandler.Handle(new ReAnalyzeRequest }); await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == projectA.FilePath); - Assert.DoesNotContain(_eventListener.Messages, x => x.ProjectFilePath == projectB.FilePath); - - _eventListener.Clear(); - - await reAnalyzeHandler.Handle(new ReAnalyzeRequest - { - CurrentOpenFilePathAsContext = projectB.Documents.Single(x => x.FilePath.EndsWith("a.cs")).FilePath - }); - - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == projectB.FilePath); - Assert.DoesNotContain(_eventListener.Messages, x => x.ProjectFilePath == projectA.FilePath); } } From 48efad8ccb3d8ce9593b2ef36a5c751453ab5efd Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 26 May 2019 13:16:15 +0300 Subject: [PATCH 15/26] Testfixes. --- .../CustomRoslynAnalyzerFacts.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index 7cb678ac52..58d23cddcc 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -98,16 +98,15 @@ public async Task When_custom_analyzers_are_executed_then_return_results() { using (var host = GetHost()) { - var testFile = new TestFile("testFile.cs", "public class _this_is_invalid_test_class_name {}"); + var testFile = new TestFile("testFile_66.cs", "class _this_is_invalid_test_class_name { int n = true; }"); - host.AddFilesToWorkspace(testFile); + var testAnalyzerRef = new TestAnalyzerReference("TS1100"); - var testAnalyzerRef = new TestAnalyzerReference("TS1234", isEnabledByDefault: true); + var projectIds = AddProjectWitFile(host, testFile, testAnalyzerRef); - AddProjectWitFile(host, testFile, testAnalyzerRef); + var result = await host.RequestCodeCheckAsync("testFile_66.cs"); - var result = await host.RequestCodeCheckAsync("testFile.cs"); - Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } } @@ -184,6 +183,8 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ new ImmutableArray())); var result = await host.RequestCodeCheckAsync("testFile_2.cs"); + + var bar = result.QuickFixes.ToList(); Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); } } From eddac0ee3a4d7f54a86e9c02dd1e1f231168fa0e Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 26 May 2019 15:10:31 +0300 Subject: [PATCH 16/26] Refactored background analysis event to send information of start end end. --- .../Models/Events/EventTypes.cs | 2 +- .../Models/Events/PackageRestoreMessage.cs | 5 ----- .../Events/ProjectAnalyzeStatusMessage.cs | 9 +++++++++ .../Models/Events/ProjectDiagnosticStatus.cs | 8 ++++++++ .../CSharpDiagnosticWorkerWithAnalyzers.cs | 17 ++++++++++++----- .../DiagnosticEventForwarder.cs | 4 ++-- .../ReAnalysisFacts.cs | 13 ++++++++----- 7 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 src/OmniSharp.Abstractions/Models/Events/ProjectAnalyzeStatusMessage.cs create mode 100644 src/OmniSharp.Abstractions/Models/Events/ProjectDiagnosticStatus.cs diff --git a/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs b/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs index 85cb8e19bb..38da098cbb 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 ProjectAnalyzed = nameof(ProjectAnalyzed); + public const string ProjectDiagnosticStatus = nameof(ProjectDiagnosticStatus); } } diff --git a/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs b/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs index 0f7001e1cd..32a52b9bca 100644 --- a/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs +++ b/src/OmniSharp.Abstractions/Models/Events/PackageRestoreMessage.cs @@ -5,9 +5,4 @@ public class PackageRestoreMessage public string FileName { get; set; } public bool Succeeded { get; set; } } - - public class ProjectAnalyzedMessage - { - public string ProjectFilePath { get; set; } - } } \ No newline at end of file diff --git a/src/OmniSharp.Abstractions/Models/Events/ProjectAnalyzeStatusMessage.cs b/src/OmniSharp.Abstractions/Models/Events/ProjectAnalyzeStatusMessage.cs new file mode 100644 index 0000000000..bd84bf6cd9 --- /dev/null +++ b/src/OmniSharp.Abstractions/Models/Events/ProjectAnalyzeStatusMessage.cs @@ -0,0 +1,9 @@ +namespace OmniSharp.Models.Events +{ + public class ProjectDiagnosticStatusMessage + { + public ProjectDiagnosticStatus Status { get; set; } + public string ProjectFilePath { get; set; } + public string Type = "background"; + } +} \ No newline at end of file diff --git a/src/OmniSharp.Abstractions/Models/Events/ProjectDiagnosticStatus.cs b/src/OmniSharp.Abstractions/Models/Events/ProjectDiagnosticStatus.cs new file mode 100644 index 0000000000..e00bf04975 --- /dev/null +++ b/src/OmniSharp.Abstractions/Models/Events/ProjectDiagnosticStatus.cs @@ -0,0 +1,8 @@ +namespace OmniSharp.Models.Events +{ + public enum ProjectDiagnosticStatus + { + Started = 0, + Ready = 1 + } +} \ 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 108f03da99..106219dba1 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging; using OmniSharp.Helpers; using OmniSharp.Models.Diagnostics; +using OmniSharp.Models.Events; using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; using OmniSharp.Services; @@ -128,13 +129,13 @@ private async Task Worker(AnalyzerWorkType workType) foreach (var projectGroup in currentWorkGroupedByProjects) { + var projectPath = solution.GetProject(projectGroup.Key).FilePath; + + EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Started); + await AnalyzeProject(solution, projectGroup); - if(workType == AnalyzerWorkType.Background) - { - var projectPath = solution.GetProject(projectGroup.Key).FilePath; - _forwarder.ProjectAnalyzedInBackground(projectPath); - } + EventIfBackgroundWork(workType, projectPath, ProjectDiagnosticStatus.Ready); } _workQueue.WorkComplete(workType); @@ -148,6 +149,12 @@ private async Task Worker(AnalyzerWorkType workType) } } + private void EventIfBackgroundWork(AnalyzerWorkType workType, string projectPath, ProjectDiagnosticStatus status) + { + if (workType == AnalyzerWorkType.Background) + _forwarder.ProjectAnalyzedInBackground(projectPath, status); + } + private void QueueForAnalysis(ImmutableArray documentIds, AnalyzerWorkType workType) { _workQueue.PutWork(documentIds, workType); diff --git a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs index 769255e8d0..f4f3f090cd 100644 --- a/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs +++ b/src/OmniSharp.Roslyn/DiagnosticEventForwarder.cs @@ -23,9 +23,9 @@ public void Forward(DiagnosticMessage message) _emitter.Emit(EventTypes.Diagnostic, message); } - public void ProjectAnalyzedInBackground(string projectFileName) + public void ProjectAnalyzedInBackground(string projectFileName, ProjectDiagnosticStatus status) { - _emitter.Emit(EventTypes.ProjectAnalyzed, new ProjectAnalyzedMessage { ProjectFilePath = projectFileName }); + _emitter.Emit(EventTypes.ProjectDiagnosticStatus, new ProjectDiagnosticStatusMessage { ProjectFilePath = projectFileName, Status = status }); } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs index 2776945397..a878180870 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -14,12 +14,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(); } @@ -71,7 +71,8 @@ public async Task WhenReanalyzeIsExecuted_ThenSendEventWhenAnalysisOfProjectIsRe await reAnalyzeHandler.Handle(new ReAnalyzeRequest()); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == 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); } } @@ -92,7 +93,8 @@ await reAnalyzeHandler.Handle(new ReAnalyzeRequest CurrentOpenFilePathAsContext = projectA.Documents.Single(x => x.FilePath.EndsWith("a.cs")).FilePath }); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == projectA.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); } } @@ -113,7 +115,8 @@ await reAnalyzeHandler.Handle(new ReAnalyzeRequest CurrentOpenFilePathAsContext = project.FilePath }); - await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == 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); } } From 2f83340133bda277f5b05909a4273e071f2cce80 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 26 May 2019 16:35:40 +0300 Subject: [PATCH 17/26] Improved logging. --- .../Services/Diagnostics/ReAnalyzeService.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs index 96233792a3..9cefb3b975 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; using OmniSharp.Abstractions.Models.V1.ReAnalyze; using OmniSharp.Mef; using OmniSharp.Models; @@ -18,12 +19,14 @@ public class ReAnalyzeService : IRequestHandler _logger; [ImportingConstructor] - public ReAnalyzeService(ICsDiagnosticWorker diagWorker, OmniSharpWorkspace workspace) + public ReAnalyzeService(ICsDiagnosticWorker diagWorker, OmniSharpWorkspace workspace, ILoggerFactory loggerFactory) { _diagWorker = diagWorker; _workspace = workspace; + _logger = loggerFactory.CreateLogger(); } public Task Handle(ReAnalyzeRequest request) @@ -36,12 +39,16 @@ public Task Handle(ReAnalyzeRequest request) var projectIds = WhenRequestIsProjectFileItselfGetFilesFromIt(request.CurrentOpenFilePathAsContext, currentSolution) ?? GetDocumentsFromProjectWhereDocumentIs(request.CurrentOpenFilePathAsContext, currentSolution); + _logger.LogInformation($"Queue analysis for project(s) {string.Join(", ", projectIds)}"); + _diagWorker.QueueDocumentsOfProjectsForDiagnostics(projectIds); } else { + _logger.LogInformation($"Queue analysis for all projects."); _diagWorker.QueueAllDocumentsForDiagnostics(); } + return Task.FromResult(new ReanalyzeResponse()); } From 20ad709962c6b597645b9ad9e7d3db419f753a24 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 20 Jun 2019 07:37:18 +0300 Subject: [PATCH 18/26] Additiona merge fix. --- .../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 746aa3a38e..49c3b0af9d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -165,7 +165,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv case WorkspaceChangeKind.DocumentAdded: case WorkspaceChangeKind.DocumentReloaded: case WorkspaceChangeKind.DocumentInfoChanged: - QueueForAnalysis(ImmutableArray.Create(changeEvent.DocumentId), AnalyzerWorkType.Background); + QueueForAnalysis(ImmutableArray.Create(changeEvent.DocumentId), AnalyzerWorkType.Foreground); break; case WorkspaceChangeKind.DocumentRemoved: if(!_currentDiagnosticResults.TryRemove(changeEvent.DocumentId, out _)) From 481f679a493145ba6c72e4cbe0af329fa4b5b004 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 26 Jun 2019 19:26:39 +0300 Subject: [PATCH 19/26] Attempt to fix test. --- tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs index efa44b468c..2906aa7ff0 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs @@ -64,7 +64,9 @@ public async Task CheckAllFilesWithAnalyzersWillEventuallyReturnAllResults() new TestFile("a.cs", "class C1 { int n = true; }"), new TestFile("b.cs", "class C2 { int n = true; }")); - await TestHelpers.WaitUntil(async () => (await host.RequestCodeCheckAsync()).QuickFixes.Any(), 50, timeout: 15000); + await TestHelpers.WaitUntil(async () => ( + await host.RequestCodeCheckAsync()).QuickFixes.Any(x => x.FileName == "a.cs") && + (await host.RequestCodeCheckAsync()).QuickFixes.Any(x => x.FileName == "b.cs"), frequency: 100, timeout: 10000); var quickFixes = await host.RequestCodeCheckAsync(); From 786382814ef2a063c41f11b9a119d00212e1b8e0 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 26 Jun 2019 21:06:19 +0300 Subject: [PATCH 20/26] Testing build stability From 9073b9d3c36f7fb2fa4b2ec57f30dcb6a1dc8141 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 26 Jun 2019 21:46:47 +0300 Subject: [PATCH 21/26] Testing build stability From 6bd1ce233a609024035fa4294e41657c7075f9c7 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 27 Jun 2019 09:18:37 +0300 Subject: [PATCH 22/26] Testing build stability From 6a7a739cf7c0730d18454ba64c300c719ad70a34 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Jul 2019 10:13:56 +0300 Subject: [PATCH 23/26] Renames etc based on reviews. --- .../Services/Diagnostics/DiagnosticsService.cs | 2 +- .../Services/Diagnostics/ReAnalyzeService.cs | 12 +++++------- .../Workers/Diagnostics/CSharpDiagnosticWorker.cs | 4 ++-- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 6 +++--- .../Diagnostics/CsharpDiagnosticWorkerComposer.cs | 8 ++++---- .../Workers/Diagnostics/ICsDiagnosticWorker.cs | 4 ++-- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs index 65b0f9666c..7fe6b24754 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs @@ -29,7 +29,7 @@ public Task Handle(DiagnosticsRequest request) _forwarder.IsEnabled = true; } - _diagWorker.QueueAllDocumentsForDiagnostics(); + _diagWorker.QueueDocumentsForDiagnostics(); return Task.FromResult(new DiagnosticsResponse()); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs index 9cefb3b975..aaa88072ab 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; using OmniSharp.Abstractions.Models.V1.ReAnalyze; using OmniSharp.Mef; -using OmniSharp.Models; -using OmniSharp.Models.Diagnostics; using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics @@ -37,16 +34,16 @@ public Task Handle(ReAnalyzeRequest request) var currentSolution = _workspace.CurrentSolution; var projectIds = WhenRequestIsProjectFileItselfGetFilesFromIt(request.CurrentOpenFilePathAsContext, currentSolution) - ?? GetDocumentsFromProjectWhereDocumentIs(request.CurrentOpenFilePathAsContext, currentSolution); + ?? GetProjectIdsFromDocumentFilePaths(request.CurrentOpenFilePathAsContext, currentSolution); _logger.LogInformation($"Queue analysis for project(s) {string.Join(", ", projectIds)}"); - _diagWorker.QueueDocumentsOfProjectsForDiagnostics(projectIds); + _diagWorker.QueueDocumentsForDiagnostics(projectIds); } else { _logger.LogInformation($"Queue analysis for all projects."); - _diagWorker.QueueAllDocumentsForDiagnostics(); + _diagWorker.QueueDocumentsForDiagnostics(); } return Task.FromResult(new ReanalyzeResponse()); @@ -70,11 +67,12 @@ private static bool CompareProjectPath(string currentOpenFilePathAsContext, Proj StringComparison.InvariantCultureIgnoreCase) == 0; } - private static ImmutableArray GetDocumentsFromProjectWhereDocumentIs(string currentOpenFilePathAsContext, Solution currentSolution) + private static ImmutableArray GetProjectIdsFromDocumentFilePaths(string currentOpenFilePathAsContext, Solution currentSolution) { return currentSolution .GetDocumentIdsWithFilePath(currentOpenFilePathAsContext) .Select(docId => currentSolution.GetDocument(docId).Project.Id) + .Distinct() .ToImmutableArray(); } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index ef1b7cbf59..64699d996f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -184,14 +184,14 @@ private static async Task> GetDiagnosticsForDocument( } } - public ImmutableArray QueueAllDocumentsForDiagnostics() + public ImmutableArray QueueDocumentsForDiagnostics() { var documents = _workspace.CurrentSolution.Projects.SelectMany(x => x.Documents); QueueForDiagnosis(documents.Select(x => x.FilePath).ToImmutableArray()); return documents.Select(x => x.Id).ToImmutableArray(); } - public ImmutableArray QueueDocumentsOfProjectsForDiagnostics(ImmutableArray projectIds) + public ImmutableArray QueueDocumentsForDiagnostics(ImmutableArray projectIds) { var documents = projectIds.SelectMany(projectId => _workspace.CurrentSolution.GetProject(projectId).Documents); QueueForDiagnosis(documents.Select(x => x.FilePath).ToImmutableArray()); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 49c3b0af9d..3e0c602e97 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -75,7 +75,7 @@ private Task InitializeWithWorkspaceDocumentsIfNotYetDone() .ContinueWith(_ => Task.Delay(50)) .ContinueWith(_ => { - var documentIds = QueueAllDocumentsForDiagnostics(); + var documentIds = QueueDocumentsForDiagnostics(); _logger.LogInformation($"Solution initialized -> queue all documents for code analysis. Initial document count: {documentIds.Length}."); }); } @@ -279,7 +279,7 @@ private void EmitDiagnostics(ImmutableArray results) } } - public ImmutableArray QueueAllDocumentsForDiagnostics() + public ImmutableArray QueueDocumentsForDiagnostics() { var documentIds = _workspace.CurrentSolution.Projects.SelectMany(x => x.DocumentIds).ToImmutableArray(); QueueForAnalysis(documentIds, AnalyzerWorkType.Background); @@ -293,7 +293,7 @@ public ImmutableArray QueueAllDocumentsForDiagnostics() return await GetDiagnosticsByDocumentIds(allDocumentsIds); } - public ImmutableArray QueueDocumentsOfProjectsForDiagnostics(ImmutableArray projectIds) + public ImmutableArray QueueDocumentsForDiagnostics(ImmutableArray projectIds) { var documentIds = projectIds.SelectMany(projectId => _workspace.CurrentSolution.GetProject(projectId).Documents.Select(x => x.Id)).ToImmutableArray(); QueueForAnalysis(documentIds, AnalyzerWorkType.Background); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs index 0e6b411ff4..27716749e3 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs @@ -58,14 +58,14 @@ public CsharpDiagnosticWorkerComposer( return _implementation.GetDiagnostics(documentPaths); } - public ImmutableArray QueueAllDocumentsForDiagnostics() + public ImmutableArray QueueDocumentsForDiagnostics() { - return _implementation.QueueAllDocumentsForDiagnostics(); + return _implementation.QueueDocumentsForDiagnostics(); } - public ImmutableArray QueueDocumentsOfProjectsForDiagnostics(ImmutableArray projectIds) + public ImmutableArray QueueDocumentsForDiagnostics(ImmutableArray projectIds) { - return _implementation.QueueDocumentsOfProjectsForDiagnostics(projectIds); + return _implementation.QueueDocumentsForDiagnostics(projectIds); } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs index d4e1da866b..1d22d70304 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs @@ -8,7 +8,7 @@ public interface ICsDiagnosticWorker { Task> GetDiagnostics(ImmutableArray documentPaths); Task> GetAllDiagnosticsAsync(); - ImmutableArray QueueAllDocumentsForDiagnostics(); - ImmutableArray QueueDocumentsOfProjectsForDiagnostics(ImmutableArray projectId); + ImmutableArray QueueDocumentsForDiagnostics(); + ImmutableArray QueueDocumentsForDiagnostics(ImmutableArray projectId); } } \ No newline at end of file From 0c76642375a727b2fe714a66032ba609f08fc33c Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Jul 2019 10:41:42 +0300 Subject: [PATCH 24/26] Updated re-analyze request as SimpleFileRequest. --- .../Models/v1/ReAnalyze/ReanalyzeRequest.cs | 5 +---- .../Services/Diagnostics/ReAnalyzeService.cs | 18 +++++++++--------- .../ReAnalysisFacts.cs | 4 ++-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs index c363b3b6d7..8cea94f7ad 100644 --- a/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs +++ b/src/OmniSharp.Abstractions/Models/v1/ReAnalyze/ReanalyzeRequest.cs @@ -4,10 +4,7 @@ namespace OmniSharp.Abstractions.Models.V1.ReAnalyze { [OmniSharpEndpoint(OmniSharpEndpoints.ReAnalyze, typeof(ReAnalyzeRequest), typeof(ReanalyzeResponse))] - public class ReAnalyzeRequest: IRequest + public class ReAnalyzeRequest: SimpleFileRequest { - // This document path is used as context to resolve which project should be analyzed. Simplifies - // clients since they don't have to figure out what is correct project for current open file. - public string CurrentOpenFilePathAsContext { get; set; } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs index aaa88072ab..1a957dd0e1 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/ReAnalyzeService.cs @@ -29,12 +29,12 @@ public ReAnalyzeService(ICsDiagnosticWorker diagWorker, OmniSharpWorkspace works public Task Handle(ReAnalyzeRequest request) { - if(!string.IsNullOrEmpty(request.CurrentOpenFilePathAsContext)) + if(!string.IsNullOrEmpty(request.FileName)) { var currentSolution = _workspace.CurrentSolution; - var projectIds = WhenRequestIsProjectFileItselfGetFilesFromIt(request.CurrentOpenFilePathAsContext, currentSolution) - ?? GetProjectIdsFromDocumentFilePaths(request.CurrentOpenFilePathAsContext, currentSolution); + var projectIds = WhenRequestIsProjectFileItselfGetFilesFromIt(request.FileName, currentSolution) + ?? GetProjectIdsFromDocumentFilePaths(request.FileName, currentSolution); _logger.LogInformation($"Queue analysis for project(s) {string.Join(", ", projectIds)}"); @@ -49,9 +49,9 @@ public Task Handle(ReAnalyzeRequest request) return Task.FromResult(new ReanalyzeResponse()); } - private ImmutableArray? WhenRequestIsProjectFileItselfGetFilesFromIt(string currentOpenFilePathAsContext, Solution currentSolution) + private ImmutableArray? WhenRequestIsProjectFileItselfGetFilesFromIt(string FileName, Solution currentSolution) { - var projects = currentSolution.Projects.Where(x => CompareProjectPath(currentOpenFilePathAsContext, x)).Select(x => x.Id).ToImmutableArray(); + var projects = currentSolution.Projects.Where(x => CompareProjectPath(FileName, x)).Select(x => x.Id).ToImmutableArray(); if(!projects.Any()) return null; @@ -59,18 +59,18 @@ public Task Handle(ReAnalyzeRequest request) return projects; } - private static bool CompareProjectPath(string currentOpenFilePathAsContext, Project x) + private static bool CompareProjectPath(string FileName, Project x) { return String.Compare( x.FilePath, - currentOpenFilePathAsContext, + FileName, StringComparison.InvariantCultureIgnoreCase) == 0; } - private static ImmutableArray GetProjectIdsFromDocumentFilePaths(string currentOpenFilePathAsContext, Solution currentSolution) + private static ImmutableArray GetProjectIdsFromDocumentFilePaths(string FileName, Solution currentSolution) { return currentSolution - .GetDocumentIdsWithFilePath(currentOpenFilePathAsContext) + .GetDocumentIdsWithFilePath(FileName) .Select(docId => currentSolution.GetDocument(docId).Project.Id) .Distinct() .ToImmutableArray(); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs index a878180870..0be14e9218 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/ReAnalysisFacts.cs @@ -90,7 +90,7 @@ public async Task WhenReanalyzeIsExecutedForFileInProject_ThenOnlyAnalyzeProject await reAnalyzeHandler.Handle(new ReAnalyzeRequest { - CurrentOpenFilePathAsContext = projectA.Documents.Single(x => x.FilePath.EndsWith("a.cs")).FilePath + FileName = projectA.Documents.Single(x => x.FilePath.EndsWith("a.cs")).FilePath }); await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == projectA.FilePath && x.Status == ProjectDiagnosticStatus.Started); @@ -112,7 +112,7 @@ public async Task WhenCurrentFileIsProjectItself_ThenReAnalyzeItAsExpected() await reAnalyzeHandler.Handle(new ReAnalyzeRequest { - CurrentOpenFilePathAsContext = project.FilePath + FileName = project.FilePath }); await _eventListener.ExpectForEmitted(x => x.ProjectFilePath == project.FilePath && x.Status == ProjectDiagnosticStatus.Started); From 842e9fd42b9a291b3c295cd05f70cf47451ead64 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Jul 2019 11:50:32 +0300 Subject: [PATCH 25/26] Added support for configurable timeout on analysis. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 11 ++++----- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 9 ++++--- .../CsharpDiagnosticWorkerComposer.cs | 2 +- .../Options/RoslynExtensionsOptions.cs | 3 ++- .../AnalyzerWorkerQueueFacts.cs | 24 +++++++++---------- .../DiagnosticsV2Facts.cs | 2 +- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 1d261c874f..4b785cbbd3 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -31,9 +29,9 @@ public Queue(TimeSpan throttling) private readonly ILogger _logger; private readonly Func _utcNow; private readonly int _maximumDelayWhenWaitingForResults; - private readonly object _queueLock = new Object(); + private readonly object _queueLock = new object(); - public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 15 * 1000) + public AnalyzerWorkQueue(ILoggerFactory loggerFactory, int timeoutAssertForPendingWorkMs, Func utcNow = null) { _queues = new Dictionary { @@ -43,7 +41,7 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = n _logger = loggerFactory.CreateLogger(); _utcNow = utcNow ?? (() => DateTime.UtcNow); - _maximumDelayWhenWaitingForResults = timeoutForPendingWorkMs; + _maximumDelayWhenWaitingForResults = timeoutAssertForPendingWorkMs; } public void PutWork(IReadOnlyCollection documentIds, AnalyzerWorkType workType) @@ -111,7 +109,6 @@ public Task WaitForegroundWorkComplete() public bool TryPromote(DocumentId id) { - if (_queues[AnalyzerWorkType.Background].WorkWaitingToExecute.Contains(id) || _queues[AnalyzerWorkType.Background].WorkExecuting.Contains(id)) { PutWork(new[] { id }, AnalyzerWorkType.Foreground); @@ -123,7 +120,7 @@ public bool TryPromote(DocumentId id) private void LogTimeouts(Task task) { - if (!task.IsCanceled) _logger.LogWarning($"Timeout before work got ready for foreground analysis queue."); + if (!task.IsCanceled) _logger.LogWarning($"Timeout before work got ready for foreground analysis queue. This is assertion to prevent complete api hang in case of error."); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 3e0c602e97..fb342e613c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -28,6 +28,7 @@ public class CSharpDiagnosticWorkerWithAnalyzers : ICsDiagnosticWorker new ConcurrentDictionary diagnostics)>(); private readonly ImmutableArray _providers; private readonly DiagnosticEventForwarder _forwarder; + private readonly OmniSharpOptions _options; private readonly OmniSharpWorkspace _workspace; // This is workaround. @@ -40,13 +41,15 @@ public CSharpDiagnosticWorkerWithAnalyzers( OmniSharpWorkspace workspace, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - DiagnosticEventForwarder forwarder) + DiagnosticEventForwarder forwarder, + OmniSharpOptions options) { _logger = loggerFactory.CreateLogger(); _providers = providers.ToImmutableArray(); - _workQueue = new AnalyzerWorkQueue(loggerFactory); + _workQueue = new AnalyzerWorkQueue(loggerFactory, timeoutAssertForPendingWorkMs: options.RoslynExtensionsOptions.DocumentAnalysisTimeoutMs * 3); _forwarder = forwarder; + _options = options; _workspace = workspace; _workspaceAnalyzerOptionsConstructor = Assembly @@ -217,7 +220,7 @@ private async Task AnalyzeDocument(Project project, ImmutableArray GetNormalizedLocationPaths(IOmniSharpEnvironment env) normalizePaths.Add(Path.Combine(env.TargetDirectory, locationPath)); } } - + return normalizePaths; } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index b8818821f3..09797f6b6f 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -53,7 +53,7 @@ public void Dispose() public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, workType); @@ -66,7 +66,7 @@ public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned(Analyzer public void WhenWorksIsAddedToQueueThenTheyWillBeReturned(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, workType); @@ -84,7 +84,7 @@ public void WhenWorksIsAddedToQueueThenTheyWillBeReturned(AnalyzerWorkType workT public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, workType); @@ -105,7 +105,7 @@ public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne(AnalyzerW public void WhenForegroundWorkIsAddedThenWaitNextIterationOfItReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 500); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); @@ -127,7 +127,7 @@ public void WhenForegroundWorkIsAddedThenWaitNextIterationOfItReady() public void WhenForegroundWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 500); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); @@ -150,7 +150,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() { var now = DateTime.UtcNow; var loggerFactory = new LoggerFactory(); - var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); + var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutAssertForPendingWorkMs: 20); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); @@ -172,7 +172,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 1000); var parallelQueues = Enumerable.Range(0, 10) @@ -204,7 +204,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); @@ -232,7 +232,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe [Fact] public void WhenBackgroundWorkIsAdded_DontWaitIt() { - var queue = new AnalyzerWorkQueue(new LoggerFactory()); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), timeoutAssertForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Background); @@ -244,7 +244,7 @@ public void WhenBackgroundWorkIsAdded_DontWaitIt() public void WhenSingleFileIsPromoted_ThenPromoteItFromBackgroundQueueToForeground() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Background); @@ -260,7 +260,7 @@ public void WhenSingleFileIsPromoted_ThenPromoteItFromBackgroundQueueToForegroun public void WhenFileIsntAtBackgroundQueueAndTriedToBePromoted_ThenDontDoNothing() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.TryPromote(document); @@ -274,7 +274,7 @@ public void WhenFileIsntAtBackgroundQueueAndTriedToBePromoted_ThenDontDoNothing( public void WhenFileIsProcessingInBackgroundQueue_ThenPromoteItAsForeground() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Background); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index 4d28a5ef92..64e6ad6697 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -44,7 +44,7 @@ public async Task CodeCheckSpecifiedFileOnly(string filename) private CSharpDiagnosticWorkerWithAnalyzers CreateDiagnosticService(DiagnosticEventForwarder forwarder) { - return new CSharpDiagnosticWorkerWithAnalyzers(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder); + return new CSharpDiagnosticWorkerWithAnalyzers(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new OmniSharpOptions()); } [Theory(Skip = "Test needs to be updated for service changes")] From 95a530398111270997e0c59af9a848999d362a20 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Jul 2019 12:07:36 +0300 Subject: [PATCH 26/26] Rename for parameter. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 4 ++-- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- .../AnalyzerWorkerQueueFacts.cs | 24 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 4b785cbbd3..0559544976 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -31,7 +31,7 @@ public Queue(TimeSpan throttling) private readonly int _maximumDelayWhenWaitingForResults; private readonly object _queueLock = new object(); - public AnalyzerWorkQueue(ILoggerFactory loggerFactory, int timeoutAssertForPendingWorkMs, Func utcNow = null) + public AnalyzerWorkQueue(ILoggerFactory loggerFactory, int timeoutForPendingWorkMs, Func utcNow = null) { _queues = new Dictionary { @@ -41,7 +41,7 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, int timeoutAssertForPendi _logger = loggerFactory.CreateLogger(); _utcNow = utcNow ?? (() => DateTime.UtcNow); - _maximumDelayWhenWaitingForResults = timeoutAssertForPendingWorkMs; + _maximumDelayWhenWaitingForResults = timeoutForPendingWorkMs; } public void PutWork(IReadOnlyCollection documentIds, AnalyzerWorkType workType) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index fb342e613c..60522c8b19 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -46,7 +46,7 @@ public CSharpDiagnosticWorkerWithAnalyzers( { _logger = loggerFactory.CreateLogger(); _providers = providers.ToImmutableArray(); - _workQueue = new AnalyzerWorkQueue(loggerFactory, timeoutAssertForPendingWorkMs: options.RoslynExtensionsOptions.DocumentAnalysisTimeoutMs * 3); + _workQueue = new AnalyzerWorkQueue(loggerFactory, timeoutForPendingWorkMs: options.RoslynExtensionsOptions.DocumentAnalysisTimeoutMs * 3); _forwarder = forwarder; _options = options; diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 09797f6b6f..9df8d07f22 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -53,7 +53,7 @@ public void Dispose() public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, workType); @@ -66,7 +66,7 @@ public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned(Analyzer public void WhenWorksIsAddedToQueueThenTheyWillBeReturned(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, workType); @@ -84,7 +84,7 @@ public void WhenWorksIsAddedToQueueThenTheyWillBeReturned(AnalyzerWorkType workT public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne(AnalyzerWorkType workType) { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, workType); @@ -105,7 +105,7 @@ public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne(AnalyzerW public void WhenForegroundWorkIsAddedThenWaitNextIterationOfItReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 500); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); @@ -127,7 +127,7 @@ public void WhenForegroundWorkIsAddedThenWaitNextIterationOfItReady() public void WhenForegroundWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 500); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); @@ -150,7 +150,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() { var now = DateTime.UtcNow; var loggerFactory = new LoggerFactory(); - var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutAssertForPendingWorkMs: 20); + var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); @@ -172,7 +172,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 1000); var parallelQueues = Enumerable.Range(0, 10) @@ -204,7 +204,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Foreground); @@ -232,7 +232,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe [Fact] public void WhenBackgroundWorkIsAdded_DontWaitIt() { - var queue = new AnalyzerWorkQueue(new LoggerFactory(), timeoutAssertForPendingWorkMs: 10*1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), timeoutForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Background); @@ -244,7 +244,7 @@ public void WhenBackgroundWorkIsAdded_DontWaitIt() public void WhenSingleFileIsPromoted_ThenPromoteItFromBackgroundQueueToForeground() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Background); @@ -260,7 +260,7 @@ public void WhenSingleFileIsPromoted_ThenPromoteItFromBackgroundQueueToForegroun public void WhenFileIsntAtBackgroundQueueAndTriedToBePromoted_ThenDontDoNothing() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.TryPromote(document); @@ -274,7 +274,7 @@ public void WhenFileIsntAtBackgroundQueueAndTriedToBePromoted_ThenDontDoNothing( public void WhenFileIsProcessingInBackgroundQueue_ThenPromoteItAsForeground() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutAssertForPendingWorkMs: 10*1000); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 10*1000); var document = CreateTestDocumentId(); queue.PutWork(new[] { document }, AnalyzerWorkType.Background);