From ac3e73cf867080e563f09a8f8f4278f3ff651deb Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Wed, 18 Feb 2015 04:12:56 -0800 Subject: [PATCH] connect ProgressReport to TableControl first working version. need some more tweaks. --- .../ISolutionCrawlerProgressReporter.cs | 9 +++- .../SolutionCrawlerProgressReporter.cs | 16 ++++++ ...kCoordinator.AsyncDocumentWorkItemQueue.cs | 5 ++ ...rkCoordinator.AsyncProjectWorkItemQueue.cs | 5 ++ .../WorkCoordinator.AsyncWorkItemQueue.cs | 46 +++++++++++++++-- .../WorkCoordinator.HighPriorityProcessor.cs | 2 +- .../WorkCoordinator.LowPriorityProcessor.cs | 8 +-- ...WorkCoordinator.NormalPriorityProcessor.cs | 2 +- .../AbstractTableDataSource.cs | 51 +++++++++++++++++++ .../VisualStudioBaseDiagnosticListTable.cs | 5 +- .../VisualStudioBaseTodoListTable.cs | 2 + 11 files changed, 138 insertions(+), 13 deletions(-) diff --git a/src/Features/Core/SolutionCrawler/ISolutionCrawlerProgressReporter.cs b/src/Features/Core/SolutionCrawler/ISolutionCrawlerProgressReporter.cs index 63632ccb26d..3a91041bd57 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 2c9f3f9d8c8..f3e3f48777e 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 f25e642830e..4c9394635de 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 08397013d02..c1cb9cd66c2 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 3a3efb6016e..20f1a358dd7 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 19f1645c11c..9753a521c85 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 bb747483993..eb5c6dc5001 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 fea0a1892b5..00c47a2413c 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 27d9644c4cd..35a875d0890 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 120fab1a52e..3d38b3415fc 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 d874f7729a3..ee38ad9da88 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 -- GitLab