diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.ProgressWrapper.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.ProgressWrapper.cs deleted file mode 100644 index b8764c13c7d8a..0000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.ProgressWrapper.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - internal partial class FindReferencesSearchEngine - { - private class ProgressWrapper - { - private readonly IStreamingFindReferencesProgress _progress; - private readonly int _maximum; - private int _current; - - public ProgressWrapper(IStreamingFindReferencesProgress progress, int maximum) - { - _progress = progress; - _maximum = maximum; - } - - public Task IncrementAsync() - { - var result = Interlocked.Increment(ref _current); - return _progress.ReportProgressAsync(_current, _maximum); - } - } - } -} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index a4bfe5e778ffd..721cbb59a82b9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -15,6 +15,8 @@ namespace Microsoft.CodeAnalysis.FindSymbols { + using ProjectToDocumentMap = Dictionary>; + internal partial class FindReferencesSearchEngine { private readonly Solution _solution; @@ -59,8 +61,10 @@ public async Task FindReferencesAsync(SymbolAndProjectId symbolAndProjectId) var symbols = await DetermineAllSymbolsAsync(symbolAndProjectId).ConfigureAwait(false); var projectMap = await CreateProjectMapAsync(symbols).ConfigureAwait(false); - var documentMap = await CreateDocumentMapAsync(projectMap).ConfigureAwait(false); - await ProcessAsync(documentMap).ConfigureAwait(false); + var projectToDocumentMap = await CreateProjectToDocumentMapAsync(projectMap).ConfigureAwait(false); + ValidateProjectToDocumentMap(projectToDocumentMap); + + await ProcessAsync(projectToDocumentMap).ConfigureAwait(false); } finally { @@ -69,72 +73,59 @@ public async Task FindReferencesAsync(SymbolAndProjectId symbolAndProjectId) } } - private async Task ProcessAsync( - ConcurrentDictionary> documentMap) + private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap) { using (Logger.LogBlock(FunctionId.FindReference_ProcessAsync, _cancellationToken)) { // quick exit - if (documentMap.Count == 0) + if (projectToDocumentMap.Count == 0) { return; } - var wrapper = new ProgressWrapper(_progress, documentMap.Count); - // Get the connected components of the dependency graph and process each individually. // That way once a component is done we can throw away all the memory associated with // it. + // For each connected component, we'll process the individual projects from bottom to + // top. i.e. we'll first process the projects with no dependencies. Then the projects + // that depend on those projects, and so and. This way we always have creates the + // dependent compilations when they're needed by later projects. If we went the other + // way (i.e. processed the projects with lots of project dependencies first), then we'd + // have to create all their depedent compilations in order to get their compilation. + // This would be very expensive and would take a lot of time before we got our first + // result. var connectedProjects = _dependencyGraph.GetDependencySets(_cancellationToken); - var projectMap = CreateProjectMap(documentMap); - await _progressTracker.AddItemsAsync(connectedProjects.Flatten().Count()).ConfigureAwait(false); - foreach (var projectSet in connectedProjects) + // Add a progress item for each (document, symbol, finder) set that we will execute. + // We'll mark the item as completed in "ProcessDocumentAsync". + var totalFindCount = projectToDocumentMap.Sum( + kvp1 => kvp1.Value.Sum(kvp2 => kvp2.Value.Count)); + await _progressTracker.AddItemsAsync(totalFindCount).ConfigureAwait(false); + + // Now, go through each connected project set and process it independently. + foreach (var connectedProjectSet in connectedProjects) { _cancellationToken.ThrowIfCancellationRequested(); - await ProcessProjectsAsync(projectSet, projectMap, wrapper).ConfigureAwait(false); + await ProcessProjectsAsync( + connectedProjectSet, projectToDocumentMap).ConfigureAwait(false); } } } - private static readonly Func>> s_documentMapGetter = - _ => new Dictionary>(); - - private static readonly Func> s_queueGetter = - _ => new List<(SymbolAndProjectId symbolAndProjectId, IReferenceFinder finder)>(); - - private static Dictionary>> CreateProjectMap( - ConcurrentDictionary> map) - { - Contract.Requires(map.Count > 0); - - var projectMap = new Dictionary>>(); - foreach (var kv in map) - { - var documentMap = projectMap.GetOrAdd(kv.Key.Project, s_documentMapGetter); - var queue = documentMap.GetOrAdd(kv.Key, s_queueGetter); - - queue.AddRange(kv.Value); - } - - ValidateProjectMap(projectMap); - return projectMap; - } - [Conditional("DEBUG")] - private static void ValidateProjectMap( - Dictionary>> projectMap) + private static void ValidateProjectToDocumentMap( + ProjectToDocumentMap projectToDocumentMap) { var set = new HashSet<(SymbolAndProjectId symbolAndProjectId, IReferenceFinder finder)>(); - foreach (var map in projectMap.Values) + foreach (var documentMap in projectToDocumentMap.Values) { - foreach (var finderList in map.Values) + foreach (var documentToFinderList in documentMap) { set.Clear(); - foreach (var finder in finderList) + foreach (var finder in documentToFinderList.Value) { Contract.Requires(set.Add(finder)); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs index 52d7046217cd5..34230bc1312d4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; @@ -9,12 +8,13 @@ namespace Microsoft.CodeAnalysis.FindSymbols { + using DocumentMap = MultiDictionary; + internal partial class FindReferencesSearchEngine { private async Task ProcessDocumentQueueAsync( Document document, - List<(SymbolAndProjectId symbolAndProjectId, IReferenceFinder finder)> documentQueue, - ProgressWrapper wrapper) + DocumentMap.ValueSet documentQueue) { await _progress.OnFindInDocumentStartedAsync(document).ConfigureAwait(false); @@ -26,26 +26,13 @@ private async Task ProcessDocumentQueueAsync( // start cache for this semantic model FindReferenceCache.Start(model); -#if PARALLEL - Roslyn.Utilities.TaskExtensions.RethrowIncorrectAggregateExceptions(cancellationToken, () => - { - documentQueue.AsParallel().WithCancellation(cancellationToken).ForAll(symbolAndFinder => - { - var symbol = symbolAndFinder.Item1; - var finder = symbolAndFinder.Item2; - - ProcessDocument(document, symbol, finder, wrapper); - }); - }); -#else foreach (var symbolAndFinder in documentQueue) { var symbol = symbolAndFinder.symbolAndProjectId; var finder = symbolAndFinder.finder; - await ProcessDocumentAsync(document, symbol, finder, wrapper).ConfigureAwait(false); + await ProcessDocumentAsync(document, symbol, finder).ConfigureAwait(false); } -#endif } finally { @@ -63,8 +50,7 @@ private async Task ProcessDocumentQueueAsync( private async Task ProcessDocumentAsync( Document document, SymbolAndProjectId symbolAndProjectId, - IReferenceFinder finder, - ProgressWrapper wrapper) + IReferenceFinder finder) { using (Logger.LogBlock(FunctionId.FindReference_ProcessDocumentAsync, s_logDocument, document, symbolAndProjectId.Symbol, _cancellationToken)) { @@ -78,9 +64,9 @@ private async Task ProcessDocumentAsync( } finally { - await wrapper.IncrementAsync().ConfigureAwait(false); + await _progressTracker.ItemCompletedAsync().ConfigureAwait(false); } } } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs index d79b3626b94e3..b662c48e04754 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -13,48 +11,25 @@ namespace Microsoft.CodeAnalysis.FindSymbols { + using DocumentMap = MultiDictionary; + using ProjectMap = MultiDictionary; + using ProjectToDocumentMap = Dictionary>; + internal partial class FindReferencesSearchEngine { - private async Task>> CreateDocumentMapAsync( - ConcurrentDictionary> projectMap) + private async Task CreateProjectToDocumentMapAsync(ProjectMap projectMap) { using (Logger.LogBlock(FunctionId.FindReference_CreateDocumentMapAsync, _cancellationToken)) { - Func> createQueue = - d => new ConcurrentQueue<(SymbolAndProjectId symbolAndProjectId, IReferenceFinder finder)>(); - - var documentMap = new ConcurrentDictionary>(); - -#if PARALLEL - Roslyn.Utilities.TaskExtensions.RethrowIncorrectAggregateExceptions(cancellationToken, () => - { - projectMap.AsParallel().WithCancellation(cancellationToken).ForAll(kvp => - { - var project = kvp.Key; - var projectQueue = kvp.Value; + var finalMap = new ProjectToDocumentMap(); - projectQueue.AsParallel().WithCancellation(cancellationToken).ForAll(symbolAndFinder => - { - var symbol = symbolAndFinder.Item1; - var finder = symbolAndFinder.Item2; - - var documents = finder.DetermineDocumentsToSearch(symbol, project, cancellationToken) ?? SpecializedCollections.EmptyEnumerable(); - foreach (var document in documents.Distinct().WhereNotNull()) - { - if (includeDocument(document)) - { - documentMap.GetOrAdd(document, createQueue).Enqueue(symbolAndFinder); - } - } - }); - }); - }); -#else foreach (var kvp in projectMap) { var project = kvp.Key; var projectQueue = kvp.Value; + var documentMap = new DocumentMap(); + foreach (var symbolAndFinder in projectQueue) { _cancellationToken.ThrowIfCancellationRequested(); @@ -68,46 +43,30 @@ internal partial class FindReferencesSearchEngine { if (_documents == null || _documents.Contains(document)) { - documentMap.GetOrAdd(document, createQueue).Enqueue(symbolAndFinder); + documentMap.Add(document, symbolAndFinder); } } } + + Contract.ThrowIfTrue(documentMap.Any(kvp1 => kvp1.Value.Count != kvp1.Value.ToSet().Count)); + + if (documentMap.Count > 0) + { + finalMap.Add(project, documentMap); + } } -#endif - Contract.ThrowIfTrue(documentMap.Any(kvp => kvp.Value.Count != kvp.Value.ToSet().Count)); - return documentMap; + return finalMap; } } - private async Task>> CreateProjectMapAsync( - ConcurrentSet symbols) + private async Task CreateProjectMapAsync(ConcurrentSet symbols) { using (Logger.LogBlock(FunctionId.FindReference_CreateProjectMapAsync, _cancellationToken)) { - Func> createQueue = - p => new ConcurrentQueue<(SymbolAndProjectId symbolAndProjectId, IReferenceFinder finder)>(); - - var projectMap = new ConcurrentDictionary>(); - -#if PARALLEL - Roslyn.Utilities.TaskExtensions.RethrowIncorrectAggregateExceptions(cancellationToken, () => - { - symbols.AsParallel().WithCancellation(cancellationToken).ForAll(s => - { - finders.AsParallel().WithCancellation(cancellationToken).ForAll(f => - { - var projects = f.DetermineProjectsToSearch(s, solution, cancellationToken) ?? SpecializedCollections.EmptyEnumerable(); - foreach (var project in projects.Distinct()) - { - projectMap.GetOrAdd(project, createQueue).Enqueue(ValueTuple.Create(s, f)); - } - }); - }); - }); -#else + var projectMap = new ProjectMap(); - var scope = _documents != null ? _documents.Select(d => d.Project).ToImmutableHashSet() : null; + var scope = _documents?.Select(d => d.Project).ToImmutableHashSet(); foreach (var symbolAndProjectId in symbols) { foreach (var finder in _finders) @@ -119,12 +78,11 @@ internal partial class FindReferencesSearchEngine { if (scope == null || scope.Contains(project)) { - projectMap.GetOrAdd(project, createQueue).Enqueue((symbolAndProjectId, finder)); + projectMap.Add(project, (symbolAndProjectId, finder)); } } } } -#endif Contract.ThrowIfTrue(projectMap.Any(kvp => kvp.Value.Count != kvp.Value.ToSet().Count)); return projectMap; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs index 4ca8d55fdce55..d6b7179eb18f5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs @@ -9,38 +9,33 @@ namespace Microsoft.CodeAnalysis.FindSymbols { + using DocumentMap = MultiDictionary; + using ProjectToDocumentMap = Dictionary>; + internal partial class FindReferencesSearchEngine { private async Task ProcessProjectsAsync( - IEnumerable projectSet, - Dictionary>> projectMap, - ProgressWrapper wrapper) + IEnumerable connectedProjectSet, + ProjectToDocumentMap projectToDocumentMap) { var visitedProjects = new HashSet(); // Make sure we process each project in the set. Process each project in depth first // order. That way when we process a project, the compilations for all projects that it // depends on will have been created already. - foreach (var projectId in projectSet) + foreach (var projectId in connectedProjectSet) { _cancellationToken.ThrowIfCancellationRequested(); - try - { - await ProcessProjectAsync(projectId, projectMap, visitedProjects, wrapper).ConfigureAwait(false); - } - finally - { - await _progressTracker.ItemCompletedAsync().ConfigureAwait(false); - } + await ProcessProjectAsync( + projectId, projectToDocumentMap, visitedProjects).ConfigureAwait(false); } } private async Task ProcessProjectAsync( ProjectId projectId, - Dictionary>> projectMap, - HashSet visitedProjects, - ProgressWrapper wrapper) + ProjectToDocumentMap projectToDocumentMap, + HashSet visitedProjects) { // Don't visit projects more than once. if (visitedProjects.Add(projectId)) @@ -53,36 +48,35 @@ private async Task ProcessProjectAsync( { _cancellationToken.ThrowIfCancellationRequested(); - await ProcessProjectAsync(dependent.ProjectId, projectMap, visitedProjects, wrapper).ConfigureAwait(false); + await ProcessProjectAsync( + dependent.ProjectId, projectToDocumentMap, visitedProjects).ConfigureAwait(false); } - await ProcessProjectAsync(project, projectMap, wrapper).ConfigureAwait(false); + await ProcessProjectAsync(project, projectToDocumentMap).ConfigureAwait(false); } } private async Task ProcessProjectAsync( Project project, - Dictionary>> projectMap, - ProgressWrapper wrapper) + ProjectToDocumentMap projectToDocumentMap) { - if (!projectMap.TryGetValue(project, out var map)) + if (!projectToDocumentMap.TryGetValue(project, out var documentMap)) { // No files in this project to process. We can bail here. We'll have cached our // compilation if there are any projects left to process that depend on us. return; } - // Now actually process the project. - await ProcessProjectAsync(project, map, wrapper).ConfigureAwait(false); - // We've now finished working on the project. Remove it from the set of remaining items. - projectMap.Remove(project); + projectToDocumentMap.Remove(project); + + // Now actually process the project. + await ProcessProjectAsync(project, documentMap).ConfigureAwait(false); } private async Task ProcessProjectAsync( Project project, - Dictionary> map, - ProgressWrapper wrapper) + DocumentMap documentMap) { using (Logger.LogBlock(FunctionId.FindReference_ProcessProjectAsync, project.Name, _cancellationToken)) { @@ -90,12 +84,17 @@ private async Task ProcessProjectAsync( var compilation = await project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); var documentTasks = new List(); - foreach (var kvp in map) + foreach (var kvp in documentMap) { var document = kvp.Key; - var documentQueue = kvp.Value; - documentTasks.Add(Task.Run(() => ProcessDocumentQueueAsync(document, documentQueue, wrapper), _cancellationToken)); + if (document.Project == project) + { + var documentQueue = kvp.Value; + + documentTasks.Add(Task.Run(() => ProcessDocumentQueueAsync( + document, documentQueue), _cancellationToken)); + } } await Task.WhenAll(documentTasks).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 8a13d659db2a0..9f951a6eda1d8 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -637,7 +637,6 @@ -