diff --git a/src/Features/Core/SolutionCrawler/ISolutionCrawlerProgressReporter.cs b/src/Features/Core/SolutionCrawler/ISolutionCrawlerProgressReporter.cs index 63632ccb26df0a7bf35229e0a7eb009dba899129..3a91041bd574dab9b5c2f659a0ede0619bfb02a9 100644 --- a/src/Features/Core/SolutionCrawler/ISolutionCrawlerProgressReporter.cs +++ b/src/Features/Core/SolutionCrawler/ISolutionCrawlerProgressReporter.cs @@ -10,12 +10,17 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler internal interface ISolutionCrawlerProgressReporter { /// - /// Raised when there is pending work in solution crawler + /// Return true if solution crawler is in progress. + /// + bool InProgress { get; } + + /// + /// Raised when there is pending work in solution crawler. /// event EventHandler Started; /// - /// Raised when there is no more pending work in solutino crawler + /// Raised when there is no more pending work in solutino crawler. /// event EventHandler Stopped; } diff --git a/src/Features/Core/SolutionCrawler/SolutionCrawlerProgressReporter.cs b/src/Features/Core/SolutionCrawler/SolutionCrawlerProgressReporter.cs index 2c9f3f9d8c8a2c5b5a9541c6fb57469736d9ac19..f3e3f48777edc19cb4fba50692bf2765abaffe8b 100644 --- a/src/Features/Core/SolutionCrawler/SolutionCrawlerProgressReporter.cs +++ b/src/Features/Core/SolutionCrawler/SolutionCrawlerProgressReporter.cs @@ -25,6 +25,14 @@ private class SolutionCrawlerProgressReporter : ISolutionCrawlerProgressReporter private int _count = 0; + public bool InProgress + { + get + { + return _count > 0; + } + } + public event EventHandler Started { add @@ -103,6 +111,14 @@ private class NullReporter : ISolutionCrawlerProgressReporter { public static readonly NullReporter Instance = new NullReporter(); + public bool InProgress + { + get + { + return false; + } + } + public event EventHandler Started { add { } diff --git a/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncDocumentWorkItemQueue.cs b/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncDocumentWorkItemQueue.cs index f25e642830ebf9eea1ad37a0edbb1f5ae1d003bd..4c9394635de530484d94d8788e85284d087fb07b 100644 --- a/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncDocumentWorkItemQueue.cs +++ b/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncDocumentWorkItemQueue.cs @@ -14,6 +14,11 @@ private class AsyncDocumentWorkItemQueue : AsyncWorkItemQueue { private readonly Dictionary> _documentWorkQueue = new Dictionary>(); + public AsyncDocumentWorkItemQueue(SolutionCrawlerProgressReporter progressReporter) : + base(progressReporter) + { + } + protected override int WorkItemCount_NoLock { get diff --git a/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncProjectWorkItemQueue.cs b/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncProjectWorkItemQueue.cs index 08397013d023f7e2cde859993516812ad9b35024..c1cb9cd66c24267ca1faffcfb39dae39b7cb349a 100644 --- a/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncProjectWorkItemQueue.cs +++ b/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncProjectWorkItemQueue.cs @@ -15,6 +15,11 @@ private sealed class AsyncProjectWorkItemQueue : AsyncWorkItemQueue { private readonly Dictionary _projectWorkQueue = new Dictionary(); + public AsyncProjectWorkItemQueue(SolutionCrawlerProgressReporter progressReporter) : + base(progressReporter) + { + } + protected override int WorkItemCount_NoLock { get diff --git a/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncWorkItemQueue.cs b/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncWorkItemQueue.cs index 3a3efb6016e24c07558c059ef65acaae2ebbf5fa..20f1a358dd7f6560dad0e198c75f95482eacfc59 100644 --- a/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncWorkItemQueue.cs +++ b/src/Features/Core/SolutionCrawler/WorkCoordinator.AsyncWorkItemQueue.cs @@ -15,12 +15,20 @@ private partial class WorkCoordinator private abstract class AsyncWorkItemQueue : IDisposable where TKey : class { - private readonly AsyncSemaphore _semaphore = new AsyncSemaphore(initialCount: 0); + private readonly object _gate; + private readonly AsyncSemaphore _semaphore; + private readonly SolutionCrawlerProgressReporter _progressReporter; // map containing cancellation source for the item given out. - private readonly Dictionary _cancellationMap = new Dictionary(); + private readonly Dictionary _cancellationMap; - private readonly object _gate = new object(); + public AsyncWorkItemQueue(SolutionCrawlerProgressReporter progressReporter) + { + _gate = new object(); + _semaphore = new AsyncSemaphore(initialCount: 0); + _cancellationMap = new Dictionary(); + _progressReporter = progressReporter; + } protected abstract int WorkItemCount_NoLock { get; } @@ -38,7 +46,7 @@ public bool HasAnyWork { lock (_gate) { - return WorkItemCount_NoLock > 0; + return HasAnyWork_NoLock; } } } @@ -72,6 +80,12 @@ public virtual Task WaitAsync(CancellationToken cancellationToken) public bool AddOrReplace(WorkItem item) { + if (!HasAnyWork) + { + // first work is added. + _progressReporter.Start(); + } + lock (_gate) { if (AddOrReplace_NoLock(item)) @@ -89,6 +103,10 @@ public void Dispose() { lock (_gate) { + // here we don't need to care about progress reporter since + // it will be only called when host is shutting down. + // we do the below since we want to kill any pending tasks + Dispose_NoLock(); _cancellationMap.Do(p => p.Value.Cancel()); @@ -96,6 +114,14 @@ public void Dispose() } } + private bool HasAnyWork_NoLock + { + get + { + return WorkItemCount_NoLock > 0; + } + } + protected void Cancel_NoLock(object key) { CancellationTokenSource source; @@ -112,6 +138,12 @@ public bool TryTake(TKey key, out WorkItem workInfo, out CancellationTokenSource { if (TryTake_NoLock(key, out workInfo)) { + if (!HasAnyWork_NoLock) + { + // last work is done. + _progressReporter.Stop(); + } + source = GetNewCancellationSource_NoLock(key); workInfo.AsyncToken.Dispose(); return true; @@ -131,6 +163,12 @@ public bool TryTakeAnyWork(ProjectId preferableProjectId, out WorkItem workItem, // there must be at least one item in the map when this is called unless host is shutting down. if (TryTakeAnyWork_NoLock(preferableProjectId, out workItem)) { + if (!HasAnyWork_NoLock) + { + // last work is done. + _progressReporter.Stop(); + } + source = GetNewCancellationSource_NoLock(workItem.Key); workItem.AsyncToken.Dispose(); return true; diff --git a/src/Features/Core/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs b/src/Features/Core/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs index 19f1645c11ce7ac40643cd77c47dfbd4a3d9f0b9..9753a521c851f714bea211c6efc2133bf2899536 100644 --- a/src/Features/Core/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs +++ b/src/Features/Core/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs @@ -38,7 +38,7 @@ private sealed class HighPriorityProcessor : IdleProcessor _lazyAnalyzers = lazyAnalyzers; _running = SpecializedTasks.EmptyTask; - _workItemQueue = new AsyncDocumentWorkItemQueue(); + _workItemQueue = new AsyncDocumentWorkItemQueue(processor._registration.ProgressReporter); Start(); } diff --git a/src/Features/Core/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs b/src/Features/Core/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs index bb747483993e688545c0f54b4def9b91ca46f35f..eb5c6dc50010e9f619b34be9bb3e1c2cbf70bae9 100644 --- a/src/Features/Core/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs +++ b/src/Features/Core/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs @@ -34,7 +34,7 @@ private sealed class LowPriorityProcessor : GlobalOperationAwareIdleProcessor base(listener, processor, globalOperationNotificationService, backOffTimeSpanInMs, shutdownToken) { _lazyAnalyzers = lazyAnalyzers; - _workItemQueue = new AsyncProjectWorkItemQueue(); + _workItemQueue = new AsyncProjectWorkItemQueue(processor._registration.ProgressReporter); Start(); } @@ -69,11 +69,11 @@ protected override async Task ExecuteAsync() await ProcessProjectAsync(this.Analyzers, workItem, projectCancellation).ConfigureAwait(false); } } - catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) + catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } - } + } public void Enqueue(WorkItem item) { @@ -135,7 +135,7 @@ private async Task ProcessProjectAsync(ImmutableArray anal processedEverything = true; } - catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) + catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } diff --git a/src/Features/Core/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index fea0a1892b5d856c826588ebe394e5b433b7f885..00c47a2413c099e111fa85596e51a6c09434b4dd 100644 --- a/src/Features/Core/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -51,7 +51,7 @@ private sealed class NormalPriorityProcessor : GlobalOperationAwareIdleProcessor _lazyAnalyzers = lazyAnalyzers; _running = SpecializedTasks.EmptyTask; - _workItemQueue = new AsyncDocumentWorkItemQueue(); + _workItemQueue = new AsyncDocumentWorkItemQueue(processor._registration.ProgressReporter); _higherPriorityDocumentsNotProcessed = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 20); _currentProjectProcessing = default(ProjectId); diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableDataSource.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableDataSource.cs index 27d9644c4cd6742d0d729b245400844ef6289e11..35a875d08907a4f5ad6e29e9ebd2fe03a03660dd 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableDataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableDataSource.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.VisualStudio.TableManager; namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource @@ -36,6 +37,28 @@ public virtual void OnProjectDependencyChanged(Solution solution) protected abstract AbstractTableEntriesFactory CreateTableEntryFactory(object key, TArgs data); + protected void ConnectToSolutionCrawlerService(Workspace workspace) + { + var crawlerService = workspace.Services.GetService(); + var reporter = crawlerService.GetProgressReporter(workspace); + + // set initial value + ChangeStableState(stable: !reporter.InProgress); + + reporter.Started += OnSolutionCrawlerStarted; + reporter.Stopped += OnSolutionCrawlerStopped; + } + + private void OnSolutionCrawlerStarted(object sender, EventArgs e) + { + ChangeStableState(stable: false); + } + + private void OnSolutionCrawlerStopped(object sender, EventArgs e) + { + ChangeStableState(stable: true); + } + protected void OnDataAddedOrChanged(object key, TArgs data) { // reuse factory. it is okay to re-use factory since we make sure we remove the factory before @@ -88,6 +111,21 @@ protected void OnDataRemoved(object key) } } + private void ChangeStableState(bool stable) + { + ImmutableArray snapshot; + + lock (_gate) + { + snapshot = _subscriptions; + } + + for (var i = 0; i < snapshot.Length; i++) + { + snapshot[i].IsStable = stable; + } + } + protected void RefreshAllFactories() { ImmutableArray snapshot; @@ -136,6 +174,19 @@ public SubscriptionWithoutLock(AbstractTableDataSource source, ITa ReportInitialData(); } + public bool IsStable + { + get + { + return _sink.IsStable; + } + + set + { + _sink.IsStable = value; + } + } + public void AddOrUpdate(ITableEntriesSnapshotFactory provider, bool newFactory) { if (newFactory) diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs index 120fab1a52e8a8d11ca7d9f515ca433d6a631a13..3d38b3415fcfad5b6cd8028ef066bcb5abedb5a6 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs @@ -10,6 +10,7 @@ using System.Windows.Documents; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; @@ -113,6 +114,8 @@ public TableDataSource(IServiceProvider serviceProvider, Workspace workspace, ID _diagnosticService = diagnosticService; _diagnosticService.DiagnosticsUpdated += OnDiagnosticsUpdated; + + ConnectToSolutionCrawlerService(_workspace); } public override void OnProjectDependencyChanged(Solution solution) @@ -329,7 +332,7 @@ private int GetErrorRank(DiagnosticData item) return ErrorRank.Other; } - switch(value) + switch (value) { case nameof(ErrorRank.Lexical): return ErrorRank.Lexical; diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs index d874f7729a33a644ad7480f12fcfc1cdb9fa2987..ee38ad9da88e7b761a1bb2c33158df92c7d1c7c7 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs @@ -50,6 +50,8 @@ public TableDataSource(Workspace workspace, ITodoListProvider todoListProvider, _identifier = identifier; _todoListProvider = todoListProvider; _todoListProvider.TodoListUpdated += OnTodoListUpdated; + + ConnectToSolutionCrawlerService(_workspace); } public override string DisplayName