From a965a6c926cb9742b365a1714a26c5b3451c9ff5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 19:38:59 -0700 Subject: [PATCH 01/39] Move off of incremental analyzer for SymbolTreeInfo --- .../AddImport/AddImportCrossLanguageTests.vb | 1 - .../MetadataSymbolsSearchScope.cs | 2 +- .../SourceSymbolsProjectSearchScope.cs | 2 +- .../SymbolTreeInfoIncrementalAnalyzer.cs | 122 +++---- ...mbolTreeInfoIncrementalAnalyzerProvider.cs | 74 ++--- .../IncrementalAnalyzerRunner.cs | 17 - .../FindSymbols/IRemoteSymbolFinderService.cs | 6 +- ...SymbolTreeInfoCacheService.MetadataInfo.cs | 2 +- .../SymbolTree/SymbolTreeInfoCacheService.cs | 307 ++++++++++-------- .../SymbolFinder/RemoteSymbolFinderService.cs | 86 ++--- 10 files changed, 310 insertions(+), 309 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb index dd3c48f2c3c28..e7e20346f5b2c 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb @@ -348,7 +348,6 @@ namespace CSAssembly2 glyphTags:=WellKnownTagArrays.CSharpProject, onAfterWorkspaceCreated:= Sub(workspace As TestWorkspace) - WaitForSolutionCrawler(workspace) Dim project = workspace.CurrentSolution.Projects.Single(Function(p) p.AssemblyName = "CSAssembly1") workspace.OnProjectNameChanged(project.Id, "NewName", "NewFilePath") WaitForSolutionCrawler(workspace) diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs index 06a3385c7b41f..bc04ede75853d 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs @@ -49,7 +49,7 @@ public override SymbolReference CreateReference(SymbolResult searchResult) protected override async Task> FindDeclarationsAsync( SymbolFilter filter, SearchQuery searchQuery) { - var service = _solution.Services.GetService(); + var service = _solution.Services.GetService(); var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(_solution, _metadataReference, CancellationToken).ConfigureAwait(false); if (info == null) return ImmutableArray.Empty; diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs index defb7b473b427..f0bf16c9a863e 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs @@ -36,7 +36,7 @@ public SourceSymbolsProjectSearchScope( protected override async Task> FindDeclarationsAsync( SymbolFilter filter, SearchQuery searchQuery) { - var service = _project.Solution.Services.GetService(); + var service = _project.Solution.Services.GetService(); var info = await service.TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(_project, CancellationToken).ConfigureAwait(false); if (info == null) { diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzer.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzer.cs index 389361e760886..64bdebe031ae7 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzer.cs @@ -1,71 +1,71 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. +//// See the LICENSE file in the project root for more information. -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.SolutionCrawler; +//using System.Threading; +//using System.Threading.Tasks; +//using Microsoft.CodeAnalysis.Remote; +//using Microsoft.CodeAnalysis.SolutionCrawler; -namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree -{ - internal partial class SymbolTreeInfoIncrementalAnalyzerProvider - { - private class SymbolTreeInfoIncrementalAnalyzer : IncrementalAnalyzerBase - { - private readonly Workspace _workspace; +//namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree +//{ +// internal partial class SymbolTreeInfoIncrementalAnalyzerProvider +// { +// private class SymbolTreeInfoIncrementalAnalyzer : IncrementalAnalyzerBase +// { +// private readonly Workspace _workspace; - public SymbolTreeInfoIncrementalAnalyzer(Workspace workspace) - => _workspace = workspace; +// public SymbolTreeInfoIncrementalAnalyzer(Workspace workspace) +// => _workspace = workspace; - public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) - { - var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); - var isMethodBodyEdit = bodyOpt != null; +// public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) +// { +// var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); +// var isMethodBodyEdit = bodyOpt != null; - if (client != null) - { - await client.TryInvokeAsync( - document.Project, (service, checksum, cancellationToken) => - service.AnalyzeDocumentAsync(checksum, document.Id, isMethodBodyEdit, cancellationToken), - cancellationToken).ConfigureAwait(false); - return; - } +// if (client != null) +// { +// await client.TryInvokeAsync( +// document.Project, (service, checksum, cancellationToken) => +// service.AnalyzeDocumentAsync(checksum, document.Id, isMethodBodyEdit, cancellationToken), +// cancellationToken).ConfigureAwait(false); +// return; +// } - var service = _workspace.Services.GetRequiredService(); - await service.AnalyzeDocumentAsync(document, isMethodBodyEdit, cancellationToken).ConfigureAwait(false); - } +// var service = _workspace.Services.GetRequiredService(); +// await service.AnalyzeDocumentAsync(document, isMethodBodyEdit, cancellationToken).ConfigureAwait(false); +// } - public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken) - { - var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false); - if (client != null) - { - await client.TryInvokeAsync( - project, (service, checksum, cancellationToken) => - service.AnalyzeProjectAsync(checksum, project.Id, cancellationToken), - cancellationToken).ConfigureAwait(false); - return; - } +// public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken) +// { +// var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false); +// if (client != null) +// { +// await client.TryInvokeAsync( +// project, (service, checksum, cancellationToken) => +// service.AnalyzeProjectAsync(checksum, project.Id, cancellationToken), +// cancellationToken).ConfigureAwait(false); +// return; +// } - var service = _workspace.Services.GetRequiredService(); - await service.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); - } +// var service = _workspace.Services.GetRequiredService(); +// await service.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); +// } - public override async Task RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken) - { - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); - if (client != null) - { - await client.TryInvokeAsync( - (service, cancellationToken) => service.RemoveProjectAsync(projectId, cancellationToken), - cancellationToken).ConfigureAwait(false); - return; - } +// public override async Task RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken) +// { +// var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); +// if (client != null) +// { +// await client.TryInvokeAsync( +// (service, cancellationToken) => service.RemoveProjectAsync(projectId, cancellationToken), +// cancellationToken).ConfigureAwait(false); +// return; +// } - var service = _workspace.Services.GetRequiredService(); - service.RemoveProject(projectId); - } - } - } -} +// var service = _workspace.Services.GetRequiredService(); +// service.RemoveProject(projectId); +// } +// } +// } +//} diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzerProvider.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzerProvider.cs index 443c68b0eb854..221df1d393418 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzerProvider.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzerProvider.cs @@ -1,40 +1,40 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. +//// See the LICENSE file in the project root for more information. -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.SolutionCrawler; +//using System; +//using System.Composition; +//using Microsoft.CodeAnalysis.Host.Mef; +//using Microsoft.CodeAnalysis.SolutionCrawler; -namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree -{ - /// - /// Features like add-using want to be able to quickly search symbol indices for projects and - /// metadata. However, creating those indices can be expensive. As such, we don't want to - /// construct them during the add-using process itself. Instead, we expose this type as an - /// Incremental-Analyzer to walk our projects/metadata in the background to keep the indices - /// up to date. - /// - /// We also then export this type as a service that can give back the index for a project or - /// metadata dll on request. If the index has been produced then it will be returned and - /// can be used by add-using. Otherwise, nothing is returned and no results will be found. - /// - /// This means that as the project is being indexed, partial results may be returned. However - /// once it is fully indexed, then total results will be returned. - /// - [ExportIncrementalAnalyzerProvider( - highPriorityForActiveFile: false, name: nameof(SymbolTreeInfoIncrementalAnalyzerProvider), - workspaceKinds: new[] { WorkspaceKind.Host }), Shared] - internal partial class SymbolTreeInfoIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SymbolTreeInfoIncrementalAnalyzerProvider() - { - } +//namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree +//{ +// /// +// /// Features like add-using want to be able to quickly search symbol indices for projects and +// /// metadata. However, creating those indices can be expensive. As such, we don't want to +// /// construct them during the add-using process itself. Instead, we expose this type as an +// /// Incremental-Analyzer to walk our projects/metadata in the background to keep the indices +// /// up to date. +// /// +// /// We also then export this type as a service that can give back the index for a project or +// /// metadata dll on request. If the index has been produced then it will be returned and +// /// can be used by add-using. Otherwise, nothing is returned and no results will be found. +// /// +// /// This means that as the project is being indexed, partial results may be returned. However +// /// once it is fully indexed, then total results will be returned. +// /// +// [ExportIncrementalAnalyzerProvider( +// highPriorityForActiveFile: false, name: nameof(SymbolTreeInfoIncrementalAnalyzerProvider), +// workspaceKinds: new[] { WorkspaceKind.Host }), Shared] +// internal partial class SymbolTreeInfoIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider +// { +// [ImportingConstructor] +// [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +// public SymbolTreeInfoIncrementalAnalyzerProvider() +// { +// } - public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) - => new SymbolTreeInfoIncrementalAnalyzer(workspace); - } -} +// public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) +// => new SymbolTreeInfoIncrementalAnalyzer(workspace); +// } +//} diff --git a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs index c8c50b5bc0c90..513279608e0e9 100644 --- a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs +++ b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs @@ -71,23 +71,6 @@ public async Task RunAsync(CancellationToken cancellationToken) incrementalAnalyzerProvider ??= incrementalAnalyzerProviders.Where(x => x.Metadata.Name == incrementalAnalyzerName).Single(provider => provider.Metadata.WorkspaceKinds is null).Value; var incrementalAnalyzer = incrementalAnalyzerProvider.CreateIncrementalAnalyzer(_workspace); solutionCrawlerRegistrationService.GetTestAccessor().WaitUntilCompletion(_workspace, ImmutableArray.Create(incrementalAnalyzer)); - - switch (incrementalAnalyzerName) - { - case nameof(SymbolTreeInfoIncrementalAnalyzerProvider): - var symbolTreeInfoCacheService = _workspace.Services.GetRequiredService(); - var symbolTreeInfo = await symbolTreeInfoCacheService.TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(_workspace.CurrentSolution.Projects.First(), cancellationToken).ConfigureAwait(false); - if (symbolTreeInfo is null) - { - throw new InvalidOperationException("Benchmark failed to calculate symbol tree info."); - } - - break; - - default: - // No additional actions required - break; - } } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index bfaab33a80e04..b834569fa6024 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -51,8 +51,8 @@ ValueTask> FindProjectSourceDecla // Notification so we can keep the remote SymbolTreeInfoCache up to date. - ValueTask AnalyzeDocumentAsync(Checksum solutionChecksum, DocumentId documentId, bool isMethodBodyEdit, CancellationToken cancellationToken); - ValueTask AnalyzeProjectAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken); - ValueTask RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken); + //ValueTask AnalyzeDocumentAsync(Checksum solutionChecksum, DocumentId documentId, bool isMethodBodyEdit, CancellationToken cancellationToken); + //ValueTask AnalyzeProjectAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken); + //ValueTask RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs index e59cca2fb5c6b..a540d1dc98e7b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree { - internal sealed partial class SymbolTreeInfoCacheService + internal sealed partial class SymbolTreeInfoCacheServiceFactory { private readonly struct MetadataInfo { diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 2d711843a5fb5..b0767dd43b809 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -22,195 +22,214 @@ namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree /// Computes and caches indices for the source symbols in s and /// for metadata symbols in s. /// - [ExportWorkspaceService(typeof(SymbolTreeInfoCacheService)), Shared] - internal sealed partial class SymbolTreeInfoCacheService : IWorkspaceService + internal interface ISymbolTreeInfoCacheService : IWorkspaceService { - private readonly ConcurrentDictionary _projectIdToInfo = new(); - private readonly ConcurrentDictionary _peReferenceToInfo = new(); + ValueTask TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken); + ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(Solution solution, PortableExecutableReference reference, CancellationToken cancellationToken); + } + [ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService)), Shared] + internal sealed partial class SymbolTreeInfoCacheServiceFactory : IWorkspaceServiceFactory + { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SymbolTreeInfoCacheService() + public SymbolTreeInfoCacheServiceFactory() { } - /// - /// Gets the latest computed for the requested . This - /// may return an index corresponding to a prior version of the refernce if it has since changed. Another - /// system is responsible for bringing these indices up to date in the background. - /// - public async ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( - Solution solution, - PortableExecutableReference reference, - CancellationToken cancellationToken) - { - // See if the last value produced exactly matches what the caller is asking for. If so, return that. - if (_peReferenceToInfo.TryGetValue(reference, out var metadataInfo)) - return metadataInfo.SymbolTreeInfo; - - // If we didn't have it in our cache, see if we can load it from disk. - var info = await SymbolTreeInfo.LoadAnyInfoForMetadataReferenceAsync(solution, reference, cancellationToken).ConfigureAwait(false); - if (info is null) - return null; - - var referencingProjects = new HashSet(solution.Projects.Where(p => p.MetadataReferences.Contains(reference)).Select(p => p.Id)); - - // attempt to add this item to the map. But defer to whatever is in the map now if something else beat us to this. - return _peReferenceToInfo.GetOrAdd(reference, new MetadataInfo(info, referencingProjects)).SymbolTreeInfo; - } + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new SymbolTreeInfoCacheService(workspaceServices.Workspace); - public async Task TryGetPotentiallyStaleSourceSymbolTreeInfoAsync( - Project project, CancellationToken cancellationToken) + private sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService { - // See if the last value produced exactly matches what the caller is asking for. If so, return that. - if (_projectIdToInfo.TryGetValue(project.Id, out var projectInfo)) - return projectInfo; - - // If we didn't have it in our cache, see if we can load some version of it from disk. - var info = await SymbolTreeInfo.LoadAnyInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false); - if (info is null) - return null; + private readonly ConcurrentDictionary _projectIdToInfo = new(); + private readonly ConcurrentDictionary _peReferenceToInfo = new(); - // attempt to add this item to the map. But defer to whatever is in the map now if something else beat us to this. - return _projectIdToInfo.GetOrAdd(project.Id, info); - } + private readonly Workspace _workspace; - public async Task AnalyzeDocumentAsync(Document document, bool isMethodBodyEdit, CancellationToken cancellationToken) - { - if (!document.Project.SupportsCompilation) - return; - - // This was a method body edit. We can reuse the existing SymbolTreeInfo if we have one. We can't just - // bail out here as the change in the document means we'll have a new checksum. We need to get that new - // checksum so that our cached information is valid. - if (isMethodBodyEdit && - _projectIdToInfo.TryGetValue(document.Project.Id, out var cachedInfo)) + public SymbolTreeInfoCacheService(Workspace workspace) { - var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync( - document.Project, cancellationToken).ConfigureAwait(false); - - var newInfo = cachedInfo.WithChecksum(checksum); - _projectIdToInfo[document.Project.Id] = newInfo; - return; + _workspace = workspace; } - await AnalyzeProjectAsync(document.Project, cancellationToken).ConfigureAwait(false); - } - - public async Task AnalyzeProjectAsync(Project project, CancellationToken cancellationToken) - { - if (!project.SupportsCompilation) - return; + /// + /// Gets the latest computed for the requested . This + /// may return an index corresponding to a prior version of the refernce if it has since changed. Another + /// system is responsible for bringing these indices up to date in the background. + /// + public async ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( + Solution solution, + PortableExecutableReference reference, + CancellationToken cancellationToken) + { + // See if the last value produced exactly matches what the caller is asking for. If so, return that. + if (_peReferenceToInfo.TryGetValue(reference, out var metadataInfo)) + return metadataInfo.SymbolTreeInfo; - // Produce the indices for the source and metadata symbols in parallel. - using var _ = ArrayBuilder.GetInstance(out var tasks); + // If we didn't have it in our cache, see if we can load it from disk. + var info = await SymbolTreeInfo.LoadAnyInfoForMetadataReferenceAsync(solution, reference, cancellationToken).ConfigureAwait(false); + if (info is null) + return null; - tasks.Add(Task.Run(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); - tasks.Add(Task.Run(() => this.UpdateReferencesAsync(project, cancellationToken), cancellationToken)); + var referencingProjects = new HashSet(solution.Projects.Where(p => p.MetadataReferences.Contains(reference)).Select(p => p.Id)); - await Task.WhenAll(tasks).ConfigureAwait(false); - } + // attempt to add this item to the map. But defer to whatever is in the map now if something else beat us to this. + return _peReferenceToInfo.GetOrAdd(reference, new MetadataInfo(info, referencingProjects)).SymbolTreeInfo; + } - private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) - { - var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - if (!_projectIdToInfo.TryGetValue(project.Id, out var projectInfo) || - projectInfo.Checksum != checksum) + public async ValueTask TryGetPotentiallyStaleSourceSymbolTreeInfoAsync( + Project project, CancellationToken cancellationToken) { - projectInfo = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( - project, checksum, cancellationToken).ConfigureAwait(false); + // See if the last value produced exactly matches what the caller is asking for. If so, return that. + if (_projectIdToInfo.TryGetValue(project.Id, out var projectInfo)) + return projectInfo; - Contract.ThrowIfNull(projectInfo); - Contract.ThrowIfTrue(projectInfo.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + // If we didn't have it in our cache, see if we can load some version of it from disk. + var info = await SymbolTreeInfo.LoadAnyInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false); + if (info is null) + return null; - // Mark that we're up to date with this project. Future calls with the same - // semantic version can bail out immediately. - _projectIdToInfo[project.Id] = projectInfo; + // attempt to add this item to the map. But defer to whatever is in the map now if something else beat us to this. + return _projectIdToInfo.GetOrAdd(project.Id, info); } - } - - private async Task UpdateReferencesAsync(Project project, CancellationToken cancellationToken) - { - // Process all metadata references. If it remote workspace, do this in parallel. - using var pendingTasks = new TemporaryArray(); - foreach (var reference in project.MetadataReferences) + public async Task AnalyzeDocumentAsync(Document document, bool isMethodBodyEdit, CancellationToken cancellationToken) { - if (reference is not PortableExecutableReference portableExecutableReference) - continue; - - if (cancellationToken.IsCancellationRequested) + if (!document.Project.SupportsCompilation) + return; + + // This was a method body edit. We can reuse the existing SymbolTreeInfo if we have one. We can't just + // bail out here as the change in the document means we'll have a new checksum. We need to get that new + // checksum so that our cached information is valid. + if (isMethodBodyEdit && + _projectIdToInfo.TryGetValue(document.Project.Id, out var cachedInfo)) { - // Break out of this loop to make sure other pending operations process cancellation before - // returning. - break; + var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync( + document.Project, cancellationToken).ConfigureAwait(false); + + var newInfo = cachedInfo.WithChecksum(checksum); + _projectIdToInfo[document.Project.Id] = newInfo; + return; } - var updateTask = UpdateReferenceAsync(_peReferenceToInfo, project, portableExecutableReference, cancellationToken); - if (updateTask.Status != TaskStatus.RanToCompletion) - pendingTasks.Add(updateTask); + await AnalyzeProjectAsync(document.Project, cancellationToken).ConfigureAwait(false); } - if (pendingTasks.Count > 0) + public async Task AnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { - // If any update operations did not complete synchronously (including any cancelled operations), - // wait for them to complete now. - await Task.WhenAll(pendingTasks.ToImmutableAndClear()).ConfigureAwait(false); + if (!project.SupportsCompilation) + return; + + // Produce the indices for the source and metadata symbols in parallel. + using var _ = ArrayBuilder.GetInstance(out var tasks); + + tasks.Add(Task.Run(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); + tasks.Add(Task.Run(() => this.UpdateReferencesAsync(project, cancellationToken), cancellationToken)); + + await Task.WhenAll(tasks).ConfigureAwait(false); } - // ⚠ This local function must be 'async' to ensure exceptions are captured in the resulting task and - // not thrown directly to the caller. - static async Task UpdateReferenceAsync( - ConcurrentDictionary peReferenceToInfo, - Project project, - PortableExecutableReference reference, - CancellationToken cancellationToken) + private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) { - // 🐉 PERF: GetMetadataChecksum indirectly uses a ConditionalWeakTable. This call is intentionally - // placed before the first 'await' of this asynchronous method to ensure it executes in the - // synchronous portion of the caller. https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1270250 - var checksum = SymbolTreeInfo.GetMetadataChecksum(project.Solution.Services, reference, cancellationToken); - if (!peReferenceToInfo.TryGetValue(reference, out var metadataInfo) || - metadataInfo.SymbolTreeInfo.Checksum != checksum) + var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); + if (!_projectIdToInfo.TryGetValue(project.Id, out var projectInfo) || + projectInfo.Checksum != checksum) { - var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( - project.Solution, reference, checksum, cancellationToken).ConfigureAwait(false); + projectInfo = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( + project, checksum, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(info); - Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + Contract.ThrowIfNull(projectInfo); + Contract.ThrowIfTrue(projectInfo.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); - // Note, getting the info may fail (for example, bogus metadata). That's ok. - // We still want to cache that result so that don't try to continuously produce - // this info over and over again. - metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); - peReferenceToInfo[reference] = metadataInfo; + // Mark that we're up to date with this project. Future calls with the same + // semantic version can bail out immediately. + _projectIdToInfo[project.Id] = projectInfo; } + } + + private async Task UpdateReferencesAsync(Project project, CancellationToken cancellationToken) + { + // Process all metadata references. If it remote workspace, do this in parallel. + using var pendingTasks = new TemporaryArray(); - // Keep track that this dll is referenced by this project. - lock (metadataInfo.ReferencingProjects) + foreach (var reference in project.MetadataReferences) { - metadataInfo.ReferencingProjects.Add(project.Id); + if (reference is not PortableExecutableReference portableExecutableReference) + continue; + + if (cancellationToken.IsCancellationRequested) + { + // Break out of this loop to make sure other pending operations process cancellation before + // returning. + break; + } + + var updateTask = UpdateReferenceAsync(_peReferenceToInfo, project, portableExecutableReference, cancellationToken); + if (updateTask.Status != TaskStatus.RanToCompletion) + pendingTasks.Add(updateTask); + } + + if (pendingTasks.Count > 0) + { + // If any update operations did not complete synchronously (including any cancelled operations), + // wait for them to complete now. + await Task.WhenAll(pendingTasks.ToImmutableAndClear()).ConfigureAwait(false); + } + + // ⚠ This local function must be 'async' to ensure exceptions are captured in the resulting task and + // not thrown directly to the caller. + static async Task UpdateReferenceAsync( + ConcurrentDictionary peReferenceToInfo, + Project project, + PortableExecutableReference reference, + CancellationToken cancellationToken) + { + // 🐉 PERF: GetMetadataChecksum indirectly uses a ConditionalWeakTable. This call is intentionally + // placed before the first 'await' of this asynchronous method to ensure it executes in the + // synchronous portion of the caller. https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1270250 + var checksum = SymbolTreeInfo.GetMetadataChecksum(project.Solution.Services, reference, cancellationToken); + if (!peReferenceToInfo.TryGetValue(reference, out var metadataInfo) || + metadataInfo.SymbolTreeInfo.Checksum != checksum) + { + var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( + project.Solution, reference, checksum, cancellationToken).ConfigureAwait(false); + + Contract.ThrowIfNull(info); + Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + + // Note, getting the info may fail (for example, bogus metadata). That's ok. + // We still want to cache that result so that don't try to continuously produce + // this info over and over again. + metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); + peReferenceToInfo[reference] = metadataInfo; + } + + // Keep track that this dll is referenced by this project. + lock (metadataInfo.ReferencingProjects) + { + metadataInfo.ReferencingProjects.Add(project.Id); + } } } - } - public void RemoveProject(ProjectId projectId) - { - _projectIdToInfo.TryRemove(projectId, out _); - RemoveMetadataReferences(projectId); - } + public void RemoveProject(ProjectId projectId) + { + _projectIdToInfo.TryRemove(projectId, out _); + RemoveMetadataReferences(projectId); + } - private void RemoveMetadataReferences(ProjectId projectId) - { - foreach (var (reference, info) in _peReferenceToInfo.ToArray()) + private void RemoveMetadataReferences(ProjectId projectId) { - lock (info.ReferencingProjects) + foreach (var (reference, info) in _peReferenceToInfo.ToArray()) { - info.ReferencingProjects.Remove(projectId); - - // If this metadata dll isn't referenced by any project. We can just dump it. - if (info.ReferencingProjects.Count == 0) - _peReferenceToInfo.TryRemove(reference, out _); + lock (info.ReferencingProjects) + { + info.ReferencingProjects.Remove(projectId); + + // If this metadata dll isn't referenced by any project. We can just dump it. + if (info.ReferencingProjects.Count == 0) + _peReferenceToInfo.TryRemove(reference, out _); + } } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index 369c74f6f668a..b923b52d11a18 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -172,49 +172,49 @@ public ValueTask> FindProjectSour }, cancellationToken); } - public ValueTask AnalyzeDocumentAsync( - Checksum solutionChecksum, - DocumentId documentId, - bool isMethodBodyEdit, - CancellationToken cancellationToken) - { - return RunServiceAsync( - solutionChecksum, - async solution => - { - var cacheService = solution.Services.GetRequiredService(); - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - await cacheService.AnalyzeDocumentAsync(document, isMethodBodyEdit, cancellationToken).ConfigureAwait(false); - }, - cancellationToken); - } - - public ValueTask AnalyzeProjectAsync( - Checksum solutionChecksum, - ProjectId projectId, - CancellationToken cancellationToken) - { - return RunServiceAsync( - solutionChecksum, - async solution => - { - var cacheService = solution.Services.GetRequiredService(); - await cacheService.AnalyzeProjectAsync(solution.GetRequiredProject(projectId), cancellationToken).ConfigureAwait(false); - }, - cancellationToken); - } - - public ValueTask RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken) - { - return RunServiceAsync( - cancellationToken => - { - var cacheService = GetWorkspaceServices().GetRequiredService(); - cacheService.RemoveProject(projectId); - return default; - }, - cancellationToken); - } + //public ValueTask AnalyzeDocumentAsync( + // Checksum solutionChecksum, + // DocumentId documentId, + // bool isMethodBodyEdit, + // CancellationToken cancellationToken) + //{ + // return RunServiceAsync( + // solutionChecksum, + // async solution => + // { + // var cacheService = solution.Services.GetRequiredService(); + // var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + // await cacheService.AnalyzeDocumentAsync(document, isMethodBodyEdit, cancellationToken).ConfigureAwait(false); + // }, + // cancellationToken); + //} + + //public ValueTask AnalyzeProjectAsync( + // Checksum solutionChecksum, + // ProjectId projectId, + // CancellationToken cancellationToken) + //{ + // return RunServiceAsync( + // solutionChecksum, + // async solution => + // { + // var cacheService = solution.Services.GetRequiredService(); + // await cacheService.AnalyzeProjectAsync(solution.GetRequiredProject(projectId), cancellationToken).ConfigureAwait(false); + // }, + // cancellationToken); + //} + + //public ValueTask RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken) + //{ + // return RunServiceAsync( + // cancellationToken => + // { + // var cacheService = GetWorkspaceServices().GetRequiredService(); + // cacheService.RemoveProject(projectId); + // return default; + // }, + // cancellationToken); + //} private sealed class FindLiteralReferencesProgressCallback : IStreamingFindLiteralReferencesProgress, IStreamingProgressTracker { From 2d3e05d58a2ea120e8004f72955bfac21ece2de0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 19:44:06 -0700 Subject: [PATCH 02/39] Update tests --- .../AddImport/AddImportCrossLanguageTests.vb | 20 ++++++++++++------- .../SymbolTree/SymbolTreeInfoCacheService.cs | 18 ++++++++++++++++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb index e7e20346f5b2c..598a54ec1d60c 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb @@ -394,13 +394,19 @@ End Namespace glyphTags:=WellKnownTagArrays.VisualBasicProject, onAfterWorkspaceCreated:=AddressOf WaitForSolutionCrawler) End Function - Private Sub WaitForSolutionCrawler(workspace As TestWorkspace) - Dim solutionCrawler = DirectCast(workspace.Services.GetService(Of ISolutionCrawlerRegistrationService), SolutionCrawlerRegistrationService) - solutionCrawler.Register(workspace) - Dim provider = DirectCast(workspace.ExportProvider.GetExports(Of IIncrementalAnalyzerProvider).First( - Function(f) TypeOf f.Value Is SymbolTreeInfoIncrementalAnalyzerProvider).Value, SymbolTreeInfoIncrementalAnalyzerProvider) - Dim analyzer = provider.CreateIncrementalAnalyzer(workspace) - solutionCrawler.GetTestAccessor().WaitUntilCompletion(workspace, ImmutableArray.Create(analyzer)) + Private Async Sub WaitForSolutionCrawler(workspace As TestWorkspace) + Dim service = DirectCast( + workspace.Services.GetRequiredService(Of ISymbolTreeInfoCacheService), + SymbolTreeInfoCacheServiceFactory.SymbolTreeInfoCacheService) + + Await service.GetTestAccessor().AnalyzeSolutionAsync() + + 'Dim solutionCrawler = DirectCast(workspace.Services.GetService(Of ISolutionCrawlerRegistrationService), SolutionCrawlerRegistrationService) + 'solutionCrawler.Register(workspace) + 'Dim provider = DirectCast(workspace.ExportProvider.GetExports(Of IIncrementalAnalyzerProvider).First( + ' Function(f) TypeOf f.Value Is SymbolTreeInfoIncrementalAnalyzerProvider).Value, SymbolTreeInfoIncrementalAnalyzerProvider) + 'Dim analyzer = provider.CreateIncrementalAnalyzer(workspace) + 'solutionCrawler.GetTestAccessor().WaitUntilCompletion(workspace, ImmutableArray.Create(analyzer)) End Sub diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index b0767dd43b809..7cb78ca6f9709 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -40,7 +40,7 @@ public SymbolTreeInfoCacheServiceFactory() public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) => new SymbolTreeInfoCacheService(workspaceServices.Workspace); - private sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService + internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService { private readonly ConcurrentDictionary _projectIdToInfo = new(); private readonly ConcurrentDictionary _peReferenceToInfo = new(); @@ -232,6 +232,22 @@ private void RemoveMetadataReferences(ProjectId projectId) } } } + + public TestAccessor GetTestAccessor() + => new TestAccessor(this); + + public struct TestAccessor + { + private readonly SymbolTreeInfoCacheService _services; + + public TestAccessor(SymbolTreeInfoCacheService service) + { + _services = service; + } + + public Task AnalyzeSolutionAsync() + => Task.CompletedTask; + } } } } From cec1d925d3e3423b03d1087be533927b6a212e5c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:24:09 -0700 Subject: [PATCH 03/39] IN progress --- .../MetadataSymbolsSearchScope.cs | 4 +- .../SymbolTree/ISymbolTreeInfoCacheService.cs | 20 +++ ...SymbolTreeInfoCacheService.MetadataInfo.cs | 0 .../SymbolTree/SymbolTreeInfoCacheService.cs | 115 ++++++++++-------- .../SymbolTree/SymbolTreeInfo_Source.cs | 9 +- 5 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 src/Features/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs rename src/{Workspaces => Features}/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs (100%) rename src/{Workspaces => Features}/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs (71%) diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs index bc04ede75853d..db99ef7bcdb55 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindSymbols.SymbolTree; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.AddImport { @@ -50,7 +51,8 @@ protected override async Task> FindDeclarationsAsync( SymbolFilter filter, SearchQuery searchQuery) { var service = _solution.Services.GetService(); - var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(_solution, _metadataReference, CancellationToken).ConfigureAwait(false); + var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( + _solution.GetRequiredProject(_assemblyProjectId), _metadataReference, CancellationToken).ConfigureAwait(false); if (info == null) return ImmutableArray.Empty; diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs new file mode 100644 index 0000000000000..1576ab28280d7 --- /dev/null +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree +{ + /// + /// Computes and caches indices for the source symbols in s and + /// for metadata symbols in s. + /// + internal interface ISymbolTreeInfoCacheService : IWorkspaceService + { + ValueTask TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken); + ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(Project project, PortableExecutableReference reference, CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs similarity index 100% rename from src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs rename to src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs similarity index 71% rename from src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs rename to src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 7cb78ca6f9709..a908b82c69529 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -6,67 +6,78 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Composition; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree { - /// - /// Computes and caches indices for the source symbols in s and - /// for metadata symbols in s. - /// - internal interface ISymbolTreeInfoCacheService : IWorkspaceService - { - ValueTask TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken); - ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(Solution solution, PortableExecutableReference reference, CancellationToken cancellationToken); - } - [ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService)), Shared] internal sealed partial class SymbolTreeInfoCacheServiceFactory : IWorkspaceServiceFactory { + private readonly IAsynchronousOperationListener _listener; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SymbolTreeInfoCacheServiceFactory() + public SymbolTreeInfoCacheServiceFactory( + IAsynchronousOperationListenerProvider listenerProvider) { + _listener = listenerProvider.GetListener(FeatureAttribute.SolutionCrawlerLegacy); } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new SymbolTreeInfoCacheService(workspaceServices.Workspace); + => new SymbolTreeInfoCacheService(workspaceServices.Workspace, _listener); - internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService + internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService, IDisposable { - private readonly ConcurrentDictionary _projectIdToInfo = new(); + private readonly ConcurrentDictionary _projectIdToInfo = new(); private readonly ConcurrentDictionary _peReferenceToInfo = new(); private readonly Workspace _workspace; + private readonly CancellationTokenSource _tokenSource = new(); - public SymbolTreeInfoCacheService(Workspace workspace) + private readonly AsyncBatchingWorkQueue _workQueue; + + public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationListener listener) { _workspace = workspace; + _workQueue = new AsyncBatchingWorkQueue( + DelayTimeSpan.NonFocus, + ProcessProjectsAsync, + EqualityComparer.Default, + listener, + _tokenSource.Token); } + void IDisposable.Dispose() + => _tokenSource.Cancel(); + /// /// Gets the latest computed for the requested . This /// may return an index corresponding to a prior version of the refernce if it has since changed. Another /// system is responsible for bringing these indices up to date in the background. /// public async ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( - Solution solution, + Project project, PortableExecutableReference reference, CancellationToken cancellationToken) { + // Kick off the work to update the data we have for this project. + _workQueue.AddWork(project.Id); + // See if the last value produced exactly matches what the caller is asking for. If so, return that. - if (_peReferenceToInfo.TryGetValue(reference, out var metadataInfo)) + if (!_peReferenceToInfo.TryGetValue(reference, out var metadataInfo)) return metadataInfo.SymbolTreeInfo; // If we didn't have it in our cache, see if we can load it from disk. + var solution = project.Solution; var info = await SymbolTreeInfo.LoadAnyInfoForMetadataReferenceAsync(solution, reference, cancellationToken).ConfigureAwait(false); if (info is null) return null; @@ -80,39 +91,41 @@ public SymbolTreeInfoCacheService(Workspace workspace) public async ValueTask TryGetPotentiallyStaleSourceSymbolTreeInfoAsync( Project project, CancellationToken cancellationToken) { + // Kick off the work to update the data we have for this project. + _workQueue.AddWork(project.Id); + // See if the last value produced exactly matches what the caller is asking for. If so, return that. if (_projectIdToInfo.TryGetValue(project.Id, out var projectInfo)) - return projectInfo; + return projectInfo.info; // If we didn't have it in our cache, see if we can load some version of it from disk. var info = await SymbolTreeInfo.LoadAnyInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false); if (info is null) return null; - // attempt to add this item to the map. But defer to whatever is in the map now if something else beat us to this. - return _projectIdToInfo.GetOrAdd(project.Id, info); + // attempt to add this item to the map. But defer to whatever is in the map now if something else beat + // us to this. Don't provide a version here so that the next time we update this data it will get + // overwritten with the latest computed data. + return _projectIdToInfo.GetOrAdd(project.Id, (semanticVersion: default, info)).info; } - public async Task AnalyzeDocumentAsync(Document document, bool isMethodBodyEdit, CancellationToken cancellationToken) + private async ValueTask ProcessProjectsAsync( + ImmutableSegmentedList projectIds, CancellationToken cancellationToken) { - if (!document.Project.SupportsCompilation) - return; + var solution = _workspace.CurrentSolution; - // This was a method body edit. We can reuse the existing SymbolTreeInfo if we have one. We can't just - // bail out here as the change in the document means we'll have a new checksum. We need to get that new - // checksum so that our cached information is valid. - if (isMethodBodyEdit && - _projectIdToInfo.TryGetValue(document.Project.Id, out var cachedInfo)) + foreach (var projectId in projectIds) { - var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync( - document.Project, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - var newInfo = cachedInfo.WithChecksum(checksum); - _projectIdToInfo[document.Project.Id] = newInfo; - return; + var project = solution.GetProject(projectId); + if (project != null) + await AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); } - await AnalyzeProjectAsync(document.Project, cancellationToken).ConfigureAwait(false); + var removedProjectIds = solution.ProjectIds.Except(_projectIdToInfo.Keys).ToArray(); + foreach (var projectId in removedProjectIds) + this.RemoveProject(projectId); } public async Task AnalyzeProjectAsync(Project project, CancellationToken cancellationToken) @@ -131,19 +144,20 @@ public async Task AnalyzeProjectAsync(Project project, CancellationToken cancell private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) { - var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - if (!_projectIdToInfo.TryGetValue(project.Id, out var projectInfo) || - projectInfo.Checksum != checksum) - { - projectInfo = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( - project, checksum, cancellationToken).ConfigureAwait(false); + // Find the top-level-version of this project. We only want to recompute if it has changed. This is + // because the symboltree contains the names of the types/namespaces in the project and would not change + // if the semantic-version of the project hasn't changed. We also do not need + var semanticVersion = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(projectInfo); - Contract.ThrowIfTrue(projectInfo.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + if (!_projectIdToInfo.TryGetValue(project.Id, out var versionAndProjectInfo) || + versionAndProjectInfo.semanticVersion != semanticVersion) + { + var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( + project, cancellationToken).ConfigureAwait(false); - // Mark that we're up to date with this project. Future calls with the same - // semantic version can bail out immediately. - _projectIdToInfo[project.Id] = projectInfo; + // Mark that we're up to date with this project. Future calls with the same semantic version can + // bail out immediately. + _projectIdToInfo[project.Id] = (semanticVersion, info); } } @@ -234,7 +248,7 @@ private void RemoveMetadataReferences(ProjectId projectId) } public TestAccessor GetTestAccessor() - => new TestAccessor(this); + => new(this); public struct TestAccessor { @@ -246,7 +260,12 @@ public TestAccessor(SymbolTreeInfoCacheService service) } public Task AnalyzeSolutionAsync() - => Task.CompletedTask; + { + foreach (var projectId in _services._workspace.CurrentSolution.ProjectIds) + _services._workQueue.AddWork(projectId); + + return _services._workQueue.WaitUntilCurrentBatchCompletesAsync(); + } } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index 274f5b27509aa..49f0173a89165 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -32,18 +32,19 @@ private static void FreeSymbolMap(MultiDictionary symbolMap) private static string GetSourceKeySuffix(Project project) => "_Source_" + project.FilePath; - public static Task GetInfoForSourceAssemblyAsync( - Project project, Checksum checksum, CancellationToken cancellationToken) + public static async Task GetInfoForSourceAssemblyAsync( + Project project, CancellationToken cancellationToken) { var solution = project.Solution; + var checksum = await GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - return LoadOrCreateAsync( + return await LoadOrCreateAsync( solution.Services, SolutionKey.ToSolutionKey(solution), checksum, createAsync: checksum => CreateSourceSymbolTreeInfoAsync(project, checksum, cancellationToken), keySuffix: GetSourceKeySuffix(project), - cancellationToken); + cancellationToken).ConfigureAwait(false); } /// From 222b3dab966895d7689cb4ab9c74cf82186ee199 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:41:19 -0700 Subject: [PATCH 04/39] Fix tests --- ...AbstractCrossLanguageUserDiagnosticTest.vb | 6 ++++-- .../AddImport/AddImportCrossLanguageTests.vb | 20 ++++++++++--------- .../SymbolTree/SymbolTreeInfoCacheService.cs | 12 ++++++----- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb b/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb index c0a37cc59e478..beda37d80fb1d 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb @@ -60,14 +60,16 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics Optional verifyTokens As Boolean = True, Optional fileNameToExpected As Dictionary(Of String, String) = Nothing, Optional verifySolutions As Func(Of Solution, Solution, Task) = Nothing, - Optional onAfterWorkspaceCreated As Action(Of TestWorkspace) = Nothing, + Optional onAfterWorkspaceCreated As Func(Of TestWorkspace, Task) = Nothing, Optional glyphTags As ImmutableArray(Of String) = Nothing) As Task Using workspace = TestWorkspace.CreateWorkspace(definition, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) If _outputHelper IsNot Nothing Then workspace.Services.SolutionServices.SetWorkspaceTestOutput(_outputHelper) End If - onAfterWorkspaceCreated?.Invoke(workspace) + If onAfterWorkspaceCreated IsNot Nothing Then + Await onAfterWorkspaceCreated(workspace) + End If Dim diagnosticAndFix = Await GetDiagnosticAndFixAsync(workspace) Dim codeActions As IList(Of CodeAction) = diagnosticAndFix.Item2.Fixes.Select(Function(f) f.Action).ToList() diff --git a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb index 598a54ec1d60c..cf1917bc644e6 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb @@ -347,11 +347,11 @@ namespace CSAssembly2 Await TestAsync(input, expected, codeActionIndex:=0, addedReference:="NewName", glyphTags:=WellKnownTagArrays.CSharpProject, onAfterWorkspaceCreated:= - Sub(workspace As TestWorkspace) + Async Function(workspace As TestWorkspace) Dim project = workspace.CurrentSolution.Projects.Single(Function(p) p.AssemblyName = "CSAssembly1") workspace.OnProjectNameChanged(project.Id, "NewName", "NewFilePath") - WaitForSolutionCrawler(workspace) - End Sub) + Await WaitForSolutionCrawler(workspace) + End Function) End Function @@ -394,7 +394,7 @@ End Namespace glyphTags:=WellKnownTagArrays.VisualBasicProject, onAfterWorkspaceCreated:=AddressOf WaitForSolutionCrawler) End Function - Private Async Sub WaitForSolutionCrawler(workspace As TestWorkspace) + Private Async Function WaitForSolutionCrawler(workspace As TestWorkspace) As Task Dim service = DirectCast( workspace.Services.GetRequiredService(Of ISymbolTreeInfoCacheService), SymbolTreeInfoCacheServiceFactory.SymbolTreeInfoCacheService) @@ -407,7 +407,7 @@ End Namespace ' Function(f) TypeOf f.Value Is SymbolTreeInfoIncrementalAnalyzerProvider).Value, SymbolTreeInfoIncrementalAnalyzerProvider) 'Dim analyzer = provider.CreateIncrementalAnalyzer(workspace) 'solutionCrawler.GetTestAccessor().WaitUntilCompletion(workspace, ImmutableArray.Create(analyzer)) - End Sub + End Function Public Async Function TestAddProjectReference_CSharpToVB_ExtensionMethod() As Task @@ -516,7 +516,7 @@ namespace CSAssembly2 Optional expected As String = Nothing, Optional codeActionIndex As Integer = 0, Optional addedReference As String = Nothing, - Optional onAfterWorkspaceCreated As Action(Of TestWorkspace) = Nothing, + Optional onAfterWorkspaceCreated As Func(Of TestWorkspace, Task) = Nothing, Optional glyphTags As ImmutableArray(Of String) = Nothing) As Task Dim verifySolutions As Func(Of Solution, Solution, Task) = Nothing Dim workspace As TestWorkspace = Nothing @@ -545,10 +545,12 @@ namespace CSAssembly2 Await TestAsync(definition, expected, codeActionIndex, verifySolutions:=verifySolutions, glyphTags:=glyphTags, - onAfterWorkspaceCreated:=Sub(ws As TestWorkspace) + onAfterWorkspaceCreated:=Async Function(ws As TestWorkspace) workspace = ws - onAfterWorkspaceCreated?.Invoke(ws) - End Sub) + If onAfterWorkspaceCreated IsNot Nothing Then + Await onAfterWorkspaceCreated(ws) + End If + End Function) End Function End Class End Namespace diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index a908b82c69529..da3e7e4f22ead 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -40,9 +40,9 @@ internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheS private readonly ConcurrentDictionary _projectIdToInfo = new(); private readonly ConcurrentDictionary _peReferenceToInfo = new(); - private readonly Workspace _workspace; private readonly CancellationTokenSource _tokenSource = new(); + private readonly Workspace _workspace; private readonly AsyncBatchingWorkQueue _workQueue; public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationListener listener) @@ -60,9 +60,9 @@ void IDisposable.Dispose() => _tokenSource.Cancel(); /// - /// Gets the latest computed for the requested . This - /// may return an index corresponding to a prior version of the refernce if it has since changed. Another - /// system is responsible for bringing these indices up to date in the background. + /// Gets the latest computed for the requested . + /// This may return an index corresponding to a prior version of the reference if it has since changed. + /// Another system is responsible for bringing these indices up to date in the background. /// public async ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( Project project, @@ -146,7 +146,9 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation { // Find the top-level-version of this project. We only want to recompute if it has changed. This is // because the symboltree contains the names of the types/namespaces in the project and would not change - // if the semantic-version of the project hasn't changed. We also do not need + // if the semantic-version of the project hasn't changed. We also do not need to check the 'dependent + // version'. As this is just tracking parent/child relationships of namespace/type names for the source + // types in this project, this cannot change based on what happens in other projects. var semanticVersion = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); if (!_projectIdToInfo.TryGetValue(project.Id, out var versionAndProjectInfo) || From cf0b105993a22cc305d9b1411ea2873b7af5ed67 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:46:39 -0700 Subject: [PATCH 05/39] Add back checksum --- .../SymbolTree/SymbolTreeInfoCacheService.cs | 17 ++++++++++++----- .../SymbolTree/SymbolTreeInfo_Source.cs | 9 ++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index da3e7e4f22ead..1a74bb2a672aa 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -154,12 +154,19 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation if (!_projectIdToInfo.TryGetValue(project.Id, out var versionAndProjectInfo) || versionAndProjectInfo.semanticVersion != semanticVersion) { - var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( - project, cancellationToken).ConfigureAwait(false); + // If the checksum is the same (which can happen if we loaded the previous index from disk), then no + // need to recompute. + var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); + if (versionAndProjectInfo.info.Checksum != checksum) + { + // Otherwise, looks like things changed. Compute and persist the latest index. + var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( + project, checksum, cancellationToken).ConfigureAwait(false); - // Mark that we're up to date with this project. Future calls with the same semantic version can - // bail out immediately. - _projectIdToInfo[project.Id] = (semanticVersion, info); + // Mark that we're up to date with this project. Future calls with the same semantic-version or + // checksum can bail out immediately. + _projectIdToInfo[project.Id] = (semanticVersion, info); + } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index 49f0173a89165..274f5b27509aa 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -32,19 +32,18 @@ private static void FreeSymbolMap(MultiDictionary symbolMap) private static string GetSourceKeySuffix(Project project) => "_Source_" + project.FilePath; - public static async Task GetInfoForSourceAssemblyAsync( - Project project, CancellationToken cancellationToken) + public static Task GetInfoForSourceAssemblyAsync( + Project project, Checksum checksum, CancellationToken cancellationToken) { var solution = project.Solution; - var checksum = await GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - return await LoadOrCreateAsync( + return LoadOrCreateAsync( solution.Services, SolutionKey.ToSolutionKey(solution), checksum, createAsync: checksum => CreateSourceSymbolTreeInfoAsync(project, checksum, cancellationToken), keySuffix: GetSourceKeySuffix(project), - cancellationToken).ConfigureAwait(false); + cancellationToken); } /// From e7e3abd44cf8bb391283b3a063bd6c23551166e1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:47:36 -0700 Subject: [PATCH 06/39] Rename method --- .../AddImport/AddImportCrossLanguageTests.vb | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb index cf1917bc644e6..ff0cae4d9b3c4 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb @@ -266,7 +266,7 @@ namespace CSAssembly2 Await TestAsync( input, expected, codeActionIndex:=0, addedReference:="CSAssembly1", - glyphTags:=WellKnownTagArrays.CSharpProject, onAfterWorkspaceCreated:=AddressOf WaitForSolutionCrawler) + glyphTags:=WellKnownTagArrays.CSharpProject, onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) End Function @@ -350,7 +350,7 @@ namespace CSAssembly2 Async Function(workspace As TestWorkspace) Dim project = workspace.CurrentSolution.Projects.Single(Function(p) p.AssemblyName = "CSAssembly1") workspace.OnProjectNameChanged(project.Id, "NewName", "NewFilePath") - Await WaitForSolutionCrawler(workspace) + Await WaitForSymbolTreeInfoCache(workspace) End Function) End Function @@ -391,22 +391,15 @@ End Namespace Await TestAsync( input, expected, codeActionIndex:=0, addedReference:="VBAssembly1", - glyphTags:=WellKnownTagArrays.VisualBasicProject, onAfterWorkspaceCreated:=AddressOf WaitForSolutionCrawler) + glyphTags:=WellKnownTagArrays.VisualBasicProject, onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) End Function - Private Async Function WaitForSolutionCrawler(workspace As TestWorkspace) As Task + Private Async Function WaitForSymbolTreeInfoCache(workspace As TestWorkspace) As Task Dim service = DirectCast( workspace.Services.GetRequiredService(Of ISymbolTreeInfoCacheService), SymbolTreeInfoCacheServiceFactory.SymbolTreeInfoCacheService) Await service.GetTestAccessor().AnalyzeSolutionAsync() - - 'Dim solutionCrawler = DirectCast(workspace.Services.GetService(Of ISolutionCrawlerRegistrationService), SolutionCrawlerRegistrationService) - 'solutionCrawler.Register(workspace) - 'Dim provider = DirectCast(workspace.ExportProvider.GetExports(Of IIncrementalAnalyzerProvider).First( - ' Function(f) TypeOf f.Value Is SymbolTreeInfoIncrementalAnalyzerProvider).Value, SymbolTreeInfoIncrementalAnalyzerProvider) - 'Dim analyzer = provider.CreateIncrementalAnalyzer(workspace) - 'solutionCrawler.GetTestAccessor().WaitUntilCompletion(workspace, ImmutableArray.Create(analyzer)) End Function @@ -476,7 +469,7 @@ namespace A Await TestAsync(input, addedReference:="CSAssembly2", glyphTags:=WellKnownTagArrays.CSharpProject, - onAfterWorkspaceCreated:=AddressOf WaitForSolutionCrawler) + onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) End Function From 7a2832f34cec0401c8065fb1b2bb575a9635815c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:49:02 -0700 Subject: [PATCH 07/39] Remove files --- .../SymbolTreeInfoIncrementalAnalyzer.cs | 71 ------------------- ...mbolTreeInfoIncrementalAnalyzerProvider.cs | 40 ----------- 2 files changed, 111 deletions(-) delete mode 100644 src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzer.cs delete mode 100644 src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzerProvider.cs diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzer.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzer.cs deleted file mode 100644 index 64bdebe031ae7..0000000000000 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. -//// See the LICENSE file in the project root for more information. - -//using System.Threading; -//using System.Threading.Tasks; -//using Microsoft.CodeAnalysis.Remote; -//using Microsoft.CodeAnalysis.SolutionCrawler; - -//namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree -//{ -// internal partial class SymbolTreeInfoIncrementalAnalyzerProvider -// { -// private class SymbolTreeInfoIncrementalAnalyzer : IncrementalAnalyzerBase -// { -// private readonly Workspace _workspace; - -// public SymbolTreeInfoIncrementalAnalyzer(Workspace workspace) -// => _workspace = workspace; - -// public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) -// { -// var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); -// var isMethodBodyEdit = bodyOpt != null; - -// if (client != null) -// { -// await client.TryInvokeAsync( -// document.Project, (service, checksum, cancellationToken) => -// service.AnalyzeDocumentAsync(checksum, document.Id, isMethodBodyEdit, cancellationToken), -// cancellationToken).ConfigureAwait(false); -// return; -// } - -// var service = _workspace.Services.GetRequiredService(); -// await service.AnalyzeDocumentAsync(document, isMethodBodyEdit, cancellationToken).ConfigureAwait(false); -// } - -// public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken) -// { -// var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false); -// if (client != null) -// { -// await client.TryInvokeAsync( -// project, (service, checksum, cancellationToken) => -// service.AnalyzeProjectAsync(checksum, project.Id, cancellationToken), -// cancellationToken).ConfigureAwait(false); -// return; -// } - -// var service = _workspace.Services.GetRequiredService(); -// await service.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); -// } - -// public override async Task RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken) -// { -// var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); -// if (client != null) -// { -// await client.TryInvokeAsync( -// (service, cancellationToken) => service.RemoveProjectAsync(projectId, cancellationToken), -// cancellationToken).ConfigureAwait(false); -// return; -// } - -// var service = _workspace.Services.GetRequiredService(); -// service.RemoveProject(projectId); -// } -// } -// } -//} diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzerProvider.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzerProvider.cs deleted file mode 100644 index 221df1d393418..0000000000000 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoIncrementalAnalyzerProvider.cs +++ /dev/null @@ -1,40 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. -//// See the LICENSE file in the project root for more information. - -//using System; -//using System.Composition; -//using Microsoft.CodeAnalysis.Host.Mef; -//using Microsoft.CodeAnalysis.SolutionCrawler; - -//namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree -//{ -// /// -// /// Features like add-using want to be able to quickly search symbol indices for projects and -// /// metadata. However, creating those indices can be expensive. As such, we don't want to -// /// construct them during the add-using process itself. Instead, we expose this type as an -// /// Incremental-Analyzer to walk our projects/metadata in the background to keep the indices -// /// up to date. -// /// -// /// We also then export this type as a service that can give back the index for a project or -// /// metadata dll on request. If the index has been produced then it will be returned and -// /// can be used by add-using. Otherwise, nothing is returned and no results will be found. -// /// -// /// This means that as the project is being indexed, partial results may be returned. However -// /// once it is fully indexed, then total results will be returned. -// /// -// [ExportIncrementalAnalyzerProvider( -// highPriorityForActiveFile: false, name: nameof(SymbolTreeInfoIncrementalAnalyzerProvider), -// workspaceKinds: new[] { WorkspaceKind.Host }), Shared] -// internal partial class SymbolTreeInfoIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider -// { -// [ImportingConstructor] -// [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -// public SymbolTreeInfoIncrementalAnalyzerProvider() -// { -// } - -// public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) -// => new SymbolTreeInfoIncrementalAnalyzer(workspace); -// } -//} From 45d292426d8124266caf0b39d04027dfaf4e96fe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:49:46 -0700 Subject: [PATCH 08/39] REmove files --- .../Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index b834569fa6024..4a9eac28592b0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -48,11 +48,5 @@ ValueTask> FindSolutionSourceDecl ValueTask> FindProjectSourceDeclarationsWithPatternAsync( Checksum solutionChecksum, ProjectId projectId, string pattern, SymbolFilter criteria, CancellationToken cancellationToken); - - // Notification so we can keep the remote SymbolTreeInfoCache up to date. - - //ValueTask AnalyzeDocumentAsync(Checksum solutionChecksum, DocumentId documentId, bool isMethodBodyEdit, CancellationToken cancellationToken); - //ValueTask AnalyzeProjectAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken); - //ValueTask RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken); } } From b6fa67253a6044fd88b2c25576046de2ef4d00a9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:50:02 -0700 Subject: [PATCH 09/39] dedent --- .../SymbolTree/SymbolTreeInfoCacheService.cs | 411 +++++++++--------- 1 file changed, 205 insertions(+), 206 deletions(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 1a74bb2a672aa..4d0661eb9c68b 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -17,264 +17,263 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree +namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree; + +[ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService)), Shared] +internal sealed partial class SymbolTreeInfoCacheServiceFactory : IWorkspaceServiceFactory { - [ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService)), Shared] - internal sealed partial class SymbolTreeInfoCacheServiceFactory : IWorkspaceServiceFactory + private readonly IAsynchronousOperationListener _listener; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public SymbolTreeInfoCacheServiceFactory( + IAsynchronousOperationListenerProvider listenerProvider) + { + _listener = listenerProvider.GetListener(FeatureAttribute.SolutionCrawlerLegacy); + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new SymbolTreeInfoCacheService(workspaceServices.Workspace, _listener); + + internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService, IDisposable { - private readonly IAsynchronousOperationListener _listener; + private readonly ConcurrentDictionary _projectIdToInfo = new(); + private readonly ConcurrentDictionary _peReferenceToInfo = new(); + + private readonly CancellationTokenSource _tokenSource = new(); + + private readonly Workspace _workspace; + private readonly AsyncBatchingWorkQueue _workQueue; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SymbolTreeInfoCacheServiceFactory( - IAsynchronousOperationListenerProvider listenerProvider) + public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationListener listener) { - _listener = listenerProvider.GetListener(FeatureAttribute.SolutionCrawlerLegacy); + _workspace = workspace; + _workQueue = new AsyncBatchingWorkQueue( + DelayTimeSpan.NonFocus, + ProcessProjectsAsync, + EqualityComparer.Default, + listener, + _tokenSource.Token); } - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new SymbolTreeInfoCacheService(workspaceServices.Workspace, _listener); - - internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService, IDisposable + void IDisposable.Dispose() + => _tokenSource.Cancel(); + + /// + /// Gets the latest computed for the requested . + /// This may return an index corresponding to a prior version of the reference if it has since changed. + /// Another system is responsible for bringing these indices up to date in the background. + /// + public async ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( + Project project, + PortableExecutableReference reference, + CancellationToken cancellationToken) { - private readonly ConcurrentDictionary _projectIdToInfo = new(); - private readonly ConcurrentDictionary _peReferenceToInfo = new(); + // Kick off the work to update the data we have for this project. + _workQueue.AddWork(project.Id); - private readonly CancellationTokenSource _tokenSource = new(); + // See if the last value produced exactly matches what the caller is asking for. If so, return that. + if (!_peReferenceToInfo.TryGetValue(reference, out var metadataInfo)) + return metadataInfo.SymbolTreeInfo; - private readonly Workspace _workspace; - private readonly AsyncBatchingWorkQueue _workQueue; + // If we didn't have it in our cache, see if we can load it from disk. + var solution = project.Solution; + var info = await SymbolTreeInfo.LoadAnyInfoForMetadataReferenceAsync(solution, reference, cancellationToken).ConfigureAwait(false); + if (info is null) + return null; - public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationListener listener) - { - _workspace = workspace; - _workQueue = new AsyncBatchingWorkQueue( - DelayTimeSpan.NonFocus, - ProcessProjectsAsync, - EqualityComparer.Default, - listener, - _tokenSource.Token); - } + var referencingProjects = new HashSet(solution.Projects.Where(p => p.MetadataReferences.Contains(reference)).Select(p => p.Id)); - void IDisposable.Dispose() - => _tokenSource.Cancel(); + // attempt to add this item to the map. But defer to whatever is in the map now if something else beat us to this. + return _peReferenceToInfo.GetOrAdd(reference, new MetadataInfo(info, referencingProjects)).SymbolTreeInfo; + } - /// - /// Gets the latest computed for the requested . - /// This may return an index corresponding to a prior version of the reference if it has since changed. - /// Another system is responsible for bringing these indices up to date in the background. - /// - public async ValueTask TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( - Project project, - PortableExecutableReference reference, - CancellationToken cancellationToken) + public async ValueTask TryGetPotentiallyStaleSourceSymbolTreeInfoAsync( + Project project, CancellationToken cancellationToken) + { + // Kick off the work to update the data we have for this project. + _workQueue.AddWork(project.Id); + + // See if the last value produced exactly matches what the caller is asking for. If so, return that. + if (_projectIdToInfo.TryGetValue(project.Id, out var projectInfo)) + return projectInfo.info; + + // If we didn't have it in our cache, see if we can load some version of it from disk. + var info = await SymbolTreeInfo.LoadAnyInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false); + if (info is null) + return null; + + // attempt to add this item to the map. But defer to whatever is in the map now if something else beat + // us to this. Don't provide a version here so that the next time we update this data it will get + // overwritten with the latest computed data. + return _projectIdToInfo.GetOrAdd(project.Id, (semanticVersion: default, info)).info; + } + + private async ValueTask ProcessProjectsAsync( + ImmutableSegmentedList projectIds, CancellationToken cancellationToken) + { + var solution = _workspace.CurrentSolution; + + foreach (var projectId in projectIds) { - // Kick off the work to update the data we have for this project. - _workQueue.AddWork(project.Id); + cancellationToken.ThrowIfCancellationRequested(); - // See if the last value produced exactly matches what the caller is asking for. If so, return that. - if (!_peReferenceToInfo.TryGetValue(reference, out var metadataInfo)) - return metadataInfo.SymbolTreeInfo; + var project = solution.GetProject(projectId); + if (project != null) + await AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); + } - // If we didn't have it in our cache, see if we can load it from disk. - var solution = project.Solution; - var info = await SymbolTreeInfo.LoadAnyInfoForMetadataReferenceAsync(solution, reference, cancellationToken).ConfigureAwait(false); - if (info is null) - return null; + var removedProjectIds = solution.ProjectIds.Except(_projectIdToInfo.Keys).ToArray(); + foreach (var projectId in removedProjectIds) + this.RemoveProject(projectId); + } - var referencingProjects = new HashSet(solution.Projects.Where(p => p.MetadataReferences.Contains(reference)).Select(p => p.Id)); + public async Task AnalyzeProjectAsync(Project project, CancellationToken cancellationToken) + { + if (!project.SupportsCompilation) + return; - // attempt to add this item to the map. But defer to whatever is in the map now if something else beat us to this. - return _peReferenceToInfo.GetOrAdd(reference, new MetadataInfo(info, referencingProjects)).SymbolTreeInfo; - } + // Produce the indices for the source and metadata symbols in parallel. + using var _ = ArrayBuilder.GetInstance(out var tasks); - public async ValueTask TryGetPotentiallyStaleSourceSymbolTreeInfoAsync( - Project project, CancellationToken cancellationToken) - { - // Kick off the work to update the data we have for this project. - _workQueue.AddWork(project.Id); - - // See if the last value produced exactly matches what the caller is asking for. If so, return that. - if (_projectIdToInfo.TryGetValue(project.Id, out var projectInfo)) - return projectInfo.info; - - // If we didn't have it in our cache, see if we can load some version of it from disk. - var info = await SymbolTreeInfo.LoadAnyInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false); - if (info is null) - return null; - - // attempt to add this item to the map. But defer to whatever is in the map now if something else beat - // us to this. Don't provide a version here so that the next time we update this data it will get - // overwritten with the latest computed data. - return _projectIdToInfo.GetOrAdd(project.Id, (semanticVersion: default, info)).info; - } + tasks.Add(Task.Run(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); + tasks.Add(Task.Run(() => this.UpdateReferencesAsync(project, cancellationToken), cancellationToken)); - private async ValueTask ProcessProjectsAsync( - ImmutableSegmentedList projectIds, CancellationToken cancellationToken) - { - var solution = _workspace.CurrentSolution; + await Task.WhenAll(tasks).ConfigureAwait(false); + } - foreach (var projectId in projectIds) + private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) + { + // Find the top-level-version of this project. We only want to recompute if it has changed. This is + // because the symboltree contains the names of the types/namespaces in the project and would not change + // if the semantic-version of the project hasn't changed. We also do not need to check the 'dependent + // version'. As this is just tracking parent/child relationships of namespace/type names for the source + // types in this project, this cannot change based on what happens in other projects. + var semanticVersion = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); + + if (!_projectIdToInfo.TryGetValue(project.Id, out var versionAndProjectInfo) || + versionAndProjectInfo.semanticVersion != semanticVersion) + { + // If the checksum is the same (which can happen if we loaded the previous index from disk), then no + // need to recompute. + var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); + if (versionAndProjectInfo.info.Checksum != checksum) { - cancellationToken.ThrowIfCancellationRequested(); + // Otherwise, looks like things changed. Compute and persist the latest index. + var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( + project, checksum, cancellationToken).ConfigureAwait(false); - var project = solution.GetProject(projectId); - if (project != null) - await AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); + // Mark that we're up to date with this project. Future calls with the same semantic-version or + // checksum can bail out immediately. + _projectIdToInfo[project.Id] = (semanticVersion, info); } - - var removedProjectIds = solution.ProjectIds.Except(_projectIdToInfo.Keys).ToArray(); - foreach (var projectId in removedProjectIds) - this.RemoveProject(projectId); } + } - public async Task AnalyzeProjectAsync(Project project, CancellationToken cancellationToken) - { - if (!project.SupportsCompilation) - return; + private async Task UpdateReferencesAsync(Project project, CancellationToken cancellationToken) + { + // Process all metadata references. If it remote workspace, do this in parallel. + using var pendingTasks = new TemporaryArray(); - // Produce the indices for the source and metadata symbols in parallel. - using var _ = ArrayBuilder.GetInstance(out var tasks); + foreach (var reference in project.MetadataReferences) + { + if (reference is not PortableExecutableReference portableExecutableReference) + continue; - tasks.Add(Task.Run(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); - tasks.Add(Task.Run(() => this.UpdateReferencesAsync(project, cancellationToken), cancellationToken)); + if (cancellationToken.IsCancellationRequested) + { + // Break out of this loop to make sure other pending operations process cancellation before + // returning. + break; + } - await Task.WhenAll(tasks).ConfigureAwait(false); + var updateTask = UpdateReferenceAsync(_peReferenceToInfo, project, portableExecutableReference, cancellationToken); + if (updateTask.Status != TaskStatus.RanToCompletion) + pendingTasks.Add(updateTask); } - private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) + if (pendingTasks.Count > 0) { - // Find the top-level-version of this project. We only want to recompute if it has changed. This is - // because the symboltree contains the names of the types/namespaces in the project and would not change - // if the semantic-version of the project hasn't changed. We also do not need to check the 'dependent - // version'. As this is just tracking parent/child relationships of namespace/type names for the source - // types in this project, this cannot change based on what happens in other projects. - var semanticVersion = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - - if (!_projectIdToInfo.TryGetValue(project.Id, out var versionAndProjectInfo) || - versionAndProjectInfo.semanticVersion != semanticVersion) - { - // If the checksum is the same (which can happen if we loaded the previous index from disk), then no - // need to recompute. - var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - if (versionAndProjectInfo.info.Checksum != checksum) - { - // Otherwise, looks like things changed. Compute and persist the latest index. - var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( - project, checksum, cancellationToken).ConfigureAwait(false); - - // Mark that we're up to date with this project. Future calls with the same semantic-version or - // checksum can bail out immediately. - _projectIdToInfo[project.Id] = (semanticVersion, info); - } - } + // If any update operations did not complete synchronously (including any cancelled operations), + // wait for them to complete now. + await Task.WhenAll(pendingTasks.ToImmutableAndClear()).ConfigureAwait(false); } - private async Task UpdateReferencesAsync(Project project, CancellationToken cancellationToken) + // ⚠ This local function must be 'async' to ensure exceptions are captured in the resulting task and + // not thrown directly to the caller. + static async Task UpdateReferenceAsync( + ConcurrentDictionary peReferenceToInfo, + Project project, + PortableExecutableReference reference, + CancellationToken cancellationToken) { - // Process all metadata references. If it remote workspace, do this in parallel. - using var pendingTasks = new TemporaryArray(); - - foreach (var reference in project.MetadataReferences) + // 🐉 PERF: GetMetadataChecksum indirectly uses a ConditionalWeakTable. This call is intentionally + // placed before the first 'await' of this asynchronous method to ensure it executes in the + // synchronous portion of the caller. https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1270250 + var checksum = SymbolTreeInfo.GetMetadataChecksum(project.Solution.Services, reference, cancellationToken); + if (!peReferenceToInfo.TryGetValue(reference, out var metadataInfo) || + metadataInfo.SymbolTreeInfo.Checksum != checksum) { - if (reference is not PortableExecutableReference portableExecutableReference) - continue; - - if (cancellationToken.IsCancellationRequested) - { - // Break out of this loop to make sure other pending operations process cancellation before - // returning. - break; - } - - var updateTask = UpdateReferenceAsync(_peReferenceToInfo, project, portableExecutableReference, cancellationToken); - if (updateTask.Status != TaskStatus.RanToCompletion) - pendingTasks.Add(updateTask); - } + var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( + project.Solution, reference, checksum, cancellationToken).ConfigureAwait(false); - if (pendingTasks.Count > 0) - { - // If any update operations did not complete synchronously (including any cancelled operations), - // wait for them to complete now. - await Task.WhenAll(pendingTasks.ToImmutableAndClear()).ConfigureAwait(false); + Contract.ThrowIfNull(info); + Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + + // Note, getting the info may fail (for example, bogus metadata). That's ok. + // We still want to cache that result so that don't try to continuously produce + // this info over and over again. + metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); + peReferenceToInfo[reference] = metadataInfo; } - // ⚠ This local function must be 'async' to ensure exceptions are captured in the resulting task and - // not thrown directly to the caller. - static async Task UpdateReferenceAsync( - ConcurrentDictionary peReferenceToInfo, - Project project, - PortableExecutableReference reference, - CancellationToken cancellationToken) + // Keep track that this dll is referenced by this project. + lock (metadataInfo.ReferencingProjects) { - // 🐉 PERF: GetMetadataChecksum indirectly uses a ConditionalWeakTable. This call is intentionally - // placed before the first 'await' of this asynchronous method to ensure it executes in the - // synchronous portion of the caller. https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1270250 - var checksum = SymbolTreeInfo.GetMetadataChecksum(project.Solution.Services, reference, cancellationToken); - if (!peReferenceToInfo.TryGetValue(reference, out var metadataInfo) || - metadataInfo.SymbolTreeInfo.Checksum != checksum) - { - var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( - project.Solution, reference, checksum, cancellationToken).ConfigureAwait(false); - - Contract.ThrowIfNull(info); - Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); - - // Note, getting the info may fail (for example, bogus metadata). That's ok. - // We still want to cache that result so that don't try to continuously produce - // this info over and over again. - metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); - peReferenceToInfo[reference] = metadataInfo; - } - - // Keep track that this dll is referenced by this project. - lock (metadataInfo.ReferencingProjects) - { - metadataInfo.ReferencingProjects.Add(project.Id); - } + metadataInfo.ReferencingProjects.Add(project.Id); } } + } - public void RemoveProject(ProjectId projectId) - { - _projectIdToInfo.TryRemove(projectId, out _); - RemoveMetadataReferences(projectId); - } + public void RemoveProject(ProjectId projectId) + { + _projectIdToInfo.TryRemove(projectId, out _); + RemoveMetadataReferences(projectId); + } - private void RemoveMetadataReferences(ProjectId projectId) + private void RemoveMetadataReferences(ProjectId projectId) + { + foreach (var (reference, info) in _peReferenceToInfo.ToArray()) { - foreach (var (reference, info) in _peReferenceToInfo.ToArray()) + lock (info.ReferencingProjects) { - lock (info.ReferencingProjects) - { - info.ReferencingProjects.Remove(projectId); - - // If this metadata dll isn't referenced by any project. We can just dump it. - if (info.ReferencingProjects.Count == 0) - _peReferenceToInfo.TryRemove(reference, out _); - } + info.ReferencingProjects.Remove(projectId); + + // If this metadata dll isn't referenced by any project. We can just dump it. + if (info.ReferencingProjects.Count == 0) + _peReferenceToInfo.TryRemove(reference, out _); } } + } - public TestAccessor GetTestAccessor() - => new(this); + public TestAccessor GetTestAccessor() + => new(this); - public struct TestAccessor - { - private readonly SymbolTreeInfoCacheService _services; + public struct TestAccessor + { + private readonly SymbolTreeInfoCacheService _services; - public TestAccessor(SymbolTreeInfoCacheService service) - { - _services = service; - } + public TestAccessor(SymbolTreeInfoCacheService service) + { + _services = service; + } - public Task AnalyzeSolutionAsync() - { - foreach (var projectId in _services._workspace.CurrentSolution.ProjectIds) - _services._workQueue.AddWork(projectId); + public Task AnalyzeSolutionAsync() + { + foreach (var projectId in _services._workspace.CurrentSolution.ProjectIds) + _services._workQueue.AddWork(projectId); - return _services._workQueue.WaitUntilCurrentBatchCompletesAsync(); - } + return _services._workQueue.WaitUntilCurrentBatchCompletesAsync(); } } } From 943faed2cf915efb69fa136aac25efc7800ad352 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:50:39 -0700 Subject: [PATCH 10/39] revert --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 4d0661eb9c68b..0385afb20b9c3 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -73,7 +73,7 @@ void IDisposable.Dispose() _workQueue.AddWork(project.Id); // See if the last value produced exactly matches what the caller is asking for. If so, return that. - if (!_peReferenceToInfo.TryGetValue(reference, out var metadataInfo)) + if (_peReferenceToInfo.TryGetValue(reference, out var metadataInfo)) return metadataInfo.SymbolTreeInfo; // If we didn't have it in our cache, see if we can load it from disk. From 7b572956d89641725497d3ef427ce96c3d05cdc3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:52:01 -0700 Subject: [PATCH 11/39] revert --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 0385afb20b9c3..d8c637df71394 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -151,13 +151,13 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation // types in this project, this cannot change based on what happens in other projects. var semanticVersion = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - if (!_projectIdToInfo.TryGetValue(project.Id, out var versionAndProjectInfo) || - versionAndProjectInfo.semanticVersion != semanticVersion) + if (!_projectIdToInfo.TryGetValue(project.Id, out var projectInfo) || + projectInfo.semanticVersion != semanticVersion) { // If the checksum is the same (which can happen if we loaded the previous index from disk), then no // need to recompute. var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - if (versionAndProjectInfo.info.Checksum != checksum) + if (projectInfo.info.Checksum != checksum) { // Otherwise, looks like things changed. Compute and persist the latest index. var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( From e63cd453ea8ec1c73dce404ade4fcda02586b11a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:53:55 -0700 Subject: [PATCH 12/39] revert --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index d8c637df71394..2557dc629f074 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -163,6 +163,9 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( project, checksum, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(info); + Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + // Mark that we're up to date with this project. Future calls with the same semantic-version or // checksum can bail out immediately. _projectIdToInfo[project.Id] = (semanticVersion, info); From e456259333fb06370dc6b6a4769242074cbf723f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 20:54:27 -0700 Subject: [PATCH 13/39] revert --- .../SymbolFinder/RemoteSymbolFinderService.cs | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index b923b52d11a18..5d38cc493c2ab 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -172,50 +172,6 @@ public ValueTask> FindProjectSour }, cancellationToken); } - //public ValueTask AnalyzeDocumentAsync( - // Checksum solutionChecksum, - // DocumentId documentId, - // bool isMethodBodyEdit, - // CancellationToken cancellationToken) - //{ - // return RunServiceAsync( - // solutionChecksum, - // async solution => - // { - // var cacheService = solution.Services.GetRequiredService(); - // var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - // await cacheService.AnalyzeDocumentAsync(document, isMethodBodyEdit, cancellationToken).ConfigureAwait(false); - // }, - // cancellationToken); - //} - - //public ValueTask AnalyzeProjectAsync( - // Checksum solutionChecksum, - // ProjectId projectId, - // CancellationToken cancellationToken) - //{ - // return RunServiceAsync( - // solutionChecksum, - // async solution => - // { - // var cacheService = solution.Services.GetRequiredService(); - // await cacheService.AnalyzeProjectAsync(solution.GetRequiredProject(projectId), cancellationToken).ConfigureAwait(false); - // }, - // cancellationToken); - //} - - //public ValueTask RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken) - //{ - // return RunServiceAsync( - // cancellationToken => - // { - // var cacheService = GetWorkspaceServices().GetRequiredService(); - // cacheService.RemoveProject(projectId); - // return default; - // }, - // cancellationToken); - //} - private sealed class FindLiteralReferencesProgressCallback : IStreamingFindLiteralReferencesProgress, IStreamingProgressTracker { private readonly RemoteCallback _callback; From 946bf00fbac908d67d7ab27ee043f5abb8dd660b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 21:01:06 -0700 Subject: [PATCH 14/39] Switch to using projects --- .../SearchScopes/MetadataSymbolsSearchScope.cs | 15 ++++++--------- .../Portable/AddImport/SymbolReferenceFinder.cs | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs index db99ef7bcdb55..22b409f8cc225 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs @@ -17,24 +17,21 @@ internal abstract partial class AbstractAddImportFeatureService provider, - Solution solution, + Project assemblyProject, IAssemblySymbol assembly, - ProjectId assemblyProjectId, PortableExecutableReference metadataReference, bool exact, CancellationToken cancellationToken) : base(provider, exact, cancellationToken) { - _solution = solution; + _assemblyProject = assemblyProject; _assembly = assembly; - _assemblyProjectId = assemblyProjectId; _metadataReference = metadataReference; } @@ -43,16 +40,16 @@ public override SymbolReference CreateReference(SymbolResult searchResult) return new MetadataSymbolReference( provider, searchResult.WithSymbol(searchResult.Symbol), - _assemblyProjectId, + _assemblyProject.Id, _metadataReference); } protected override async Task> FindDeclarationsAsync( SymbolFilter filter, SearchQuery searchQuery) { - var service = _solution.Services.GetService(); + var service = _assemblyProject.Solution.Services.GetService(); var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( - _solution.GetRequiredProject(_assemblyProjectId), _metadataReference, CancellationToken).ConfigureAwait(false); + _assemblyProject, _metadataReference, CancellationToken).ConfigureAwait(false); if (info == null) return ImmutableArray.Empty; diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs index 33aba1ead62cf..cc04e0a4a546c 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs @@ -109,7 +109,7 @@ internal Task> FindInMetadataSymbolsAsync( bool exact, CancellationToken cancellationToken) { var searchScope = new MetadataSymbolsSearchScope( - _owner, _document.Project.Solution, assembly, assemblyProjectId, + _owner, _document.Project.Solution.GetRequiredProject(assemblyProjectId), assembly, metadataReference, exact, cancellationToken); return DoAsync(searchScope); } From 3932f2b7befa2baa957b6e0af15bbf55a2d6a5cf Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Sat, 15 Oct 2022 21:01:45 -0700 Subject: [PATCH 15/39] Update src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs --- .../AddImport/SearchScopes/MetadataSymbolsSearchScope.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs index 22b409f8cc225..769ae7b42d55d 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs @@ -48,8 +48,7 @@ protected override async Task> FindDeclarationsAsync( SymbolFilter filter, SearchQuery searchQuery) { var service = _assemblyProject.Solution.Services.GetService(); - var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync( - _assemblyProject, _metadataReference, CancellationToken).ConfigureAwait(false); + var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(_assemblyProject, _metadataReference, CancellationToken).ConfigureAwait(false); if (info == null) return ImmutableArray.Empty; From 89457437b0fdd9c2266c52aea2d9e9f09fcbd0f2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 21:06:12 -0700 Subject: [PATCH 16/39] Switch to using projects --- .../AddImport/AbstractAddImportFeatureService.cs | 10 +++++----- .../Core/Portable/AddImport/SymbolReferenceFinder.cs | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 384ed1efb7bd8..b8e8e187ddbdc 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -262,7 +262,7 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( using var nestedTokenSource = new CancellationTokenSource(); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(nestedTokenSource.Token, cancellationToken); - foreach (var (referenceProjectId, reference) in newReferences) + foreach (var (referenceProject, reference) in newReferences) { var compilation = referenceToCompilation.GetOrAdd( reference, r => CreateCompilation(project, r)); @@ -272,7 +272,7 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly) { findTasks.Add(finder.FindInMetadataSymbolsAsync( - assembly, referenceProjectId, reference, exact, linkedTokenSource.Token)); + assembly, referenceProject, reference, exact, linkedTokenSource.Token)); } } @@ -284,10 +284,10 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( /// by this project. The set returned will be tuples containing the PEReference, and the project-id /// for the project we found the pe-reference in. /// - private static ImmutableArray<(ProjectId, PortableExecutableReference)> GetUnreferencedMetadataReferences( + private static ImmutableArray<(Project, PortableExecutableReference)> GetUnreferencedMetadataReferences( Project project, HashSet seenReferences) { - var result = ArrayBuilder<(ProjectId, PortableExecutableReference)>.GetInstance(); + var result = ArrayBuilder<(Project, PortableExecutableReference)>.GetInstance(); var solution = project.Solution; foreach (var p in solution.Projects) @@ -303,7 +303,7 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( !IsInPackagesDirectory(peReference) && seenReferences.Add(peReference)) { - result.Add((p.Id, peReference)); + result.Add((p, peReference)); } } } diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs index cc04e0a4a546c..63f0d03d3f4f6 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs @@ -105,12 +105,11 @@ internal Task> FindInSourceSymbolsInProjectAsync } internal Task> FindInMetadataSymbolsAsync( - IAssemblySymbol assembly, ProjectId assemblyProjectId, PortableExecutableReference metadataReference, + IAssemblySymbol assembly, Project assemblyProject, PortableExecutableReference metadataReference, bool exact, CancellationToken cancellationToken) { var searchScope = new MetadataSymbolsSearchScope( - _owner, _document.Project.Solution.GetRequiredProject(assemblyProjectId), assembly, - metadataReference, exact, cancellationToken); + _owner, assemblyProject, assembly, metadataReference, exact, cancellationToken); return DoAsync(searchScope); } From c454cbf3a38656651980da8a81583f9f4bcc90c6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 21:09:12 -0700 Subject: [PATCH 17/39] Fix --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 2557dc629f074..c8b2d546cf1d5 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -123,7 +123,9 @@ private async ValueTask ProcessProjectsAsync( await AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); } - var removedProjectIds = solution.ProjectIds.Except(_projectIdToInfo.Keys).ToArray(); + // Now that we've produced all the indices for the projects asked for, also remove any indices for projects + // no longer in the solution. + var removedProjectIds = _projectIdToInfo.Keys.Except(solution.ProjectIds).ToArray(); foreach (var projectId in removedProjectIds) this.RemoveProject(projectId); } From b5df6007616cebbfbc5111124e72f33e2a39b0c3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 21:11:21 -0700 Subject: [PATCH 18/39] SLower update --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index c8b2d546cf1d5..91f7b173e8a80 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree; @@ -49,7 +50,7 @@ public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationLis { _workspace = workspace; _workQueue = new AsyncBatchingWorkQueue( - DelayTimeSpan.NonFocus, + SolutionCrawlerTimeSpan.EntireProjectWorkerBackOff, ProcessProjectsAsync, EqualityComparer.Default, listener, From 80d364df2ddc3b7ce3111791e81c92019b6e0e93 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 21:54:13 -0700 Subject: [PATCH 19/39] run in parallel in remoteworkspace --- .../SymbolTree/SymbolTreeInfoCacheService.cs | 125 ++++++++---------- 1 file changed, 54 insertions(+), 71 deletions(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 91f7b173e8a80..4ce02cee71362 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -38,6 +38,8 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService, IDisposable { + private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; + private readonly ConcurrentDictionary _projectIdToInfo = new(); private readonly ConcurrentDictionary _peReferenceToInfo = new(); @@ -46,6 +48,12 @@ internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheS private readonly Workspace _workspace; private readonly AsyncBatchingWorkQueue _workQueue; + /// + /// Scheduler to run our tasks on. If we're in the remote host , we'll run all our tasks concurrently. + /// Otherwise, we will run them serially using + /// + private readonly TaskScheduler _scheduler; + public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationListener listener) { _workspace = workspace; @@ -55,11 +63,16 @@ public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationLis EqualityComparer.Default, listener, _tokenSource.Token); + + _scheduler = workspace.Kind == WorkspaceKind.RemoteWorkspace ? TaskScheduler.Default : s_exclusiveScheduler; } void IDisposable.Dispose() => _tokenSource.Cancel(); + public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancellationToken) + => Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap(); + /// /// Gets the latest computed for the requested . /// This may return an index corresponding to a prior version of the reference if it has since changed. @@ -115,15 +128,29 @@ private async ValueTask ProcessProjectsAsync( { var solution = _workspace.CurrentSolution; + using var _ = ArrayBuilder.GetInstance(out var tasks); + foreach (var projectId in projectIds) { cancellationToken.ThrowIfCancellationRequested(); var project = solution.GetProject(projectId); - if (project != null) - await AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); + if (project == null || !project.SupportsCompilation) + continue; + + tasks.Add(CreateWorkAsync(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); + + foreach (var reference in project.MetadataReferences) + { + if (reference is not PortableExecutableReference portableExecutableReference) + continue; + + tasks.Add(CreateWorkAsync(() => UpdateReferenceAsync(_peReferenceToInfo, project, portableExecutableReference, cancellationToken), cancellationToken)); + } } + await Task.WhenAll(tasks).ConfigureAwait(false); + // Now that we've produced all the indices for the projects asked for, also remove any indices for projects // no longer in the solution. var removedProjectIds = _projectIdToInfo.Keys.Except(solution.ProjectIds).ToArray(); @@ -131,20 +158,6 @@ private async ValueTask ProcessProjectsAsync( this.RemoveProject(projectId); } - public async Task AnalyzeProjectAsync(Project project, CancellationToken cancellationToken) - { - if (!project.SupportsCompilation) - return; - - // Produce the indices for the source and metadata symbols in parallel. - using var _ = ArrayBuilder.GetInstance(out var tasks); - - tasks.Add(Task.Run(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); - tasks.Add(Task.Run(() => this.UpdateReferencesAsync(project, cancellationToken), cancellationToken)); - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) { // Find the top-level-version of this project. We only want to recompute if it has changed. This is @@ -176,68 +189,38 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation } } - private async Task UpdateReferencesAsync(Project project, CancellationToken cancellationToken) + // ⚠ This local function must be 'async' to ensure exceptions are captured in the resulting task and + // not thrown directly to the caller. + private static async Task UpdateReferenceAsync( + ConcurrentDictionary peReferenceToInfo, + Project project, + PortableExecutableReference reference, + CancellationToken cancellationToken) { - // Process all metadata references. If it remote workspace, do this in parallel. - using var pendingTasks = new TemporaryArray(); - - foreach (var reference in project.MetadataReferences) + // 🐉 PERF: GetMetadataChecksum indirectly uses a ConditionalWeakTable. This call is intentionally + // placed before the first 'await' of this asynchronous method to ensure it executes in the + // synchronous portion of the caller. https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1270250 + var checksum = SymbolTreeInfo.GetMetadataChecksum(project.Solution.Services, reference, cancellationToken); + if (!peReferenceToInfo.TryGetValue(reference, out var metadataInfo) || + metadataInfo.SymbolTreeInfo.Checksum != checksum) { - if (reference is not PortableExecutableReference portableExecutableReference) - continue; + var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( + project.Solution, reference, checksum, cancellationToken).ConfigureAwait(false); - if (cancellationToken.IsCancellationRequested) - { - // Break out of this loop to make sure other pending operations process cancellation before - // returning. - break; - } + Contract.ThrowIfNull(info); + Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); - var updateTask = UpdateReferenceAsync(_peReferenceToInfo, project, portableExecutableReference, cancellationToken); - if (updateTask.Status != TaskStatus.RanToCompletion) - pendingTasks.Add(updateTask); + // Note, getting the info may fail (for example, bogus metadata). That's ok. + // We still want to cache that result so that don't try to continuously produce + // this info over and over again. + metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); + peReferenceToInfo[reference] = metadataInfo; } - if (pendingTasks.Count > 0) + // Keep track that this dll is referenced by this project. + lock (metadataInfo.ReferencingProjects) { - // If any update operations did not complete synchronously (including any cancelled operations), - // wait for them to complete now. - await Task.WhenAll(pendingTasks.ToImmutableAndClear()).ConfigureAwait(false); - } - - // ⚠ This local function must be 'async' to ensure exceptions are captured in the resulting task and - // not thrown directly to the caller. - static async Task UpdateReferenceAsync( - ConcurrentDictionary peReferenceToInfo, - Project project, - PortableExecutableReference reference, - CancellationToken cancellationToken) - { - // 🐉 PERF: GetMetadataChecksum indirectly uses a ConditionalWeakTable. This call is intentionally - // placed before the first 'await' of this asynchronous method to ensure it executes in the - // synchronous portion of the caller. https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1270250 - var checksum = SymbolTreeInfo.GetMetadataChecksum(project.Solution.Services, reference, cancellationToken); - if (!peReferenceToInfo.TryGetValue(reference, out var metadataInfo) || - metadataInfo.SymbolTreeInfo.Checksum != checksum) - { - var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( - project.Solution, reference, checksum, cancellationToken).ConfigureAwait(false); - - Contract.ThrowIfNull(info); - Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); - - // Note, getting the info may fail (for example, bogus metadata). That's ok. - // We still want to cache that result so that don't try to continuously produce - // this info over and over again. - metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); - peReferenceToInfo[reference] = metadataInfo; - } - - // Keep track that this dll is referenced by this project. - lock (metadataInfo.ReferencingProjects) - { - metadataInfo.ReferencingProjects.Add(project.Id); - } + metadataInfo.ReferencingProjects.Add(project.Id); } } From cdc5d3b7a7d7b29ba6bc5853999620a85346e3bc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 21:54:53 -0700 Subject: [PATCH 20/39] Simplify --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 4ce02cee71362..89c5e994fe3e5 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -135,7 +135,7 @@ private async ValueTask ProcessProjectsAsync( cancellationToken.ThrowIfCancellationRequested(); var project = solution.GetProject(projectId); - if (project == null || !project.SupportsCompilation) + if (project is not { SupportsCompilation: true }) continue; tasks.Add(CreateWorkAsync(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); From 0518f12c366cd0f80a3a1a12de98bfa285909c61 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 21:56:53 -0700 Subject: [PATCH 21/39] Simplify --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 89c5e994fe3e5..1afcd2d5ea81e 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -83,6 +83,9 @@ public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancel PortableExecutableReference reference, CancellationToken cancellationToken) { + if (!project.SupportsCompilation) + return null; + // Kick off the work to update the data we have for this project. _workQueue.AddWork(project.Id); @@ -105,6 +108,9 @@ public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancel public async ValueTask TryGetPotentiallyStaleSourceSymbolTreeInfoAsync( Project project, CancellationToken cancellationToken) { + if (!project.SupportsCompilation) + return null; + // Kick off the work to update the data we have for this project. _workQueue.AddWork(project.Id); @@ -138,6 +144,7 @@ private async ValueTask ProcessProjectsAsync( if (project is not { SupportsCompilation: true }) continue; + // Add a task to update the symboltree for the source symbols. tasks.Add(CreateWorkAsync(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); foreach (var reference in project.MetadataReferences) @@ -145,10 +152,12 @@ private async ValueTask ProcessProjectsAsync( if (reference is not PortableExecutableReference portableExecutableReference) continue; + // And tasks to update the symboltree for all metadata references. tasks.Add(CreateWorkAsync(() => UpdateReferenceAsync(_peReferenceToInfo, project, portableExecutableReference, cancellationToken), cancellationToken)); } } + // Wait for all the work to finish. await Task.WhenAll(tasks).ConfigureAwait(false); // Now that we've produced all the indices for the projects asked for, also remove any indices for projects From 7ff57b3caaf494b1b14666db39c8f0998529791b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:10:28 -0700 Subject: [PATCH 22/39] Projects serially --- .../SymbolTree/SymbolTreeInfoCacheService.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 1afcd2d5ea81e..84b3b9e74f993 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -134,8 +134,6 @@ private async ValueTask ProcessProjectsAsync( { var solution = _workspace.CurrentSolution; - using var _ = ArrayBuilder.GetInstance(out var tasks); - foreach (var projectId in projectIds) { cancellationToken.ThrowIfCancellationRequested(); @@ -144,22 +142,11 @@ private async ValueTask ProcessProjectsAsync( if (project is not { SupportsCompilation: true }) continue; - // Add a task to update the symboltree for the source symbols. - tasks.Add(CreateWorkAsync(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); - - foreach (var reference in project.MetadataReferences) - { - if (reference is not PortableExecutableReference portableExecutableReference) - continue; - - // And tasks to update the symboltree for all metadata references. - tasks.Add(CreateWorkAsync(() => UpdateReferenceAsync(_peReferenceToInfo, project, portableExecutableReference, cancellationToken), cancellationToken)); - } + // NOTE: currently we process projects serially. This is because the same metadata reference might be + // found in multiple projects and we can't currently process that in parallel. + await AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); } - // Wait for all the work to finish. - await Task.WhenAll(tasks).ConfigureAwait(false); - // Now that we've produced all the indices for the projects asked for, also remove any indices for projects // no longer in the solution. var removedProjectIds = _projectIdToInfo.Keys.Except(solution.ProjectIds).ToArray(); @@ -167,6 +154,26 @@ private async ValueTask ProcessProjectsAsync( this.RemoveProject(projectId); } + private async Task AnalyzeProjectAsync(Project project, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var tasks); + + // We can compute source-symbols in parallel with the metadata symbols. + tasks.Add(CreateWorkAsync(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); + + // the metadata references from a single project can be computed in parallel. + + foreach (var reference in project.MetadataReferences.OfType().Distinct()) + { + // And tasks to update the symboltree for all metadata references. As these are all distinct, they can + // run in parallel as we won't be trying to update the associated data for the same reference at the + // same time. + tasks.Add(CreateWorkAsync(() => UpdateReferenceAsync(project, reference, cancellationToken), cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) { // Find the top-level-version of this project. We only want to recompute if it has changed. This is @@ -198,19 +205,13 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation } } - // ⚠ This local function must be 'async' to ensure exceptions are captured in the resulting task and - // not thrown directly to the caller. - private static async Task UpdateReferenceAsync( - ConcurrentDictionary peReferenceToInfo, + private async Task UpdateReferenceAsync( Project project, PortableExecutableReference reference, CancellationToken cancellationToken) { - // 🐉 PERF: GetMetadataChecksum indirectly uses a ConditionalWeakTable. This call is intentionally - // placed before the first 'await' of this asynchronous method to ensure it executes in the - // synchronous portion of the caller. https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1270250 var checksum = SymbolTreeInfo.GetMetadataChecksum(project.Solution.Services, reference, cancellationToken); - if (!peReferenceToInfo.TryGetValue(reference, out var metadataInfo) || + if (!_peReferenceToInfo.TryGetValue(reference, out var metadataInfo) || metadataInfo.SymbolTreeInfo.Checksum != checksum) { var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( @@ -219,11 +220,10 @@ private static async Task UpdateReferenceAsync( Contract.ThrowIfNull(info); Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); - // Note, getting the info may fail (for example, bogus metadata). That's ok. - // We still want to cache that result so that don't try to continuously produce - // this info over and over again. + // Note, getting the info may fail (for example, bogus metadata). That's ok. We still want to cache + // that result so that don't try to continuously produce this info over and over again. metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); - peReferenceToInfo[reference] = metadataInfo; + _peReferenceToInfo[reference] = metadataInfo; } // Keep track that this dll is referenced by this project. From 97312869d0073d928852323a06b260d88f522be4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:13:23 -0700 Subject: [PATCH 23/39] Break out files --- ...ervice.MetadataInfo.cs => MetadataInfo.cs} | 0 .../SymbolTree/SymbolTreeInfoCacheService.cs | 20 +--------- .../SymbolTreeInfoCacheServiceFactory.cs | 37 +++++++++++++++++++ 3 files changed, 38 insertions(+), 19 deletions(-) rename src/Features/Core/Portable/FindSymbols/SymbolTree/{SymbolTreeInfoCacheService.MetadataInfo.cs => MetadataInfo.cs} (100%) create mode 100644 src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/MetadataInfo.cs similarity index 100% rename from src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.MetadataInfo.cs rename to src/Features/Core/Portable/FindSymbols/SymbolTree/MetadataInfo.cs diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 84b3b9e74f993..b4bcd4122f83b 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -5,37 +5,19 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree; -[ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService)), Shared] -internal sealed partial class SymbolTreeInfoCacheServiceFactory : IWorkspaceServiceFactory +internal sealed partial class SymbolTreeInfoCacheServiceFactory { - private readonly IAsynchronousOperationListener _listener; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SymbolTreeInfoCacheServiceFactory( - IAsynchronousOperationListenerProvider listenerProvider) - { - _listener = listenerProvider.GetListener(FeatureAttribute.SolutionCrawlerLegacy); - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new SymbolTreeInfoCacheService(workspaceServices.Workspace, _listener); - internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService, IDisposable { private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs new file mode 100644 index 0000000000000..28140e9f31e32 --- /dev/null +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree; + +[ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService)), Shared] +internal sealed partial class SymbolTreeInfoCacheServiceFactory : IWorkspaceServiceFactory +{ + private readonly IAsynchronousOperationListener _listener; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public SymbolTreeInfoCacheServiceFactory( + IAsynchronousOperationListenerProvider listenerProvider) + { + _listener = listenerProvider.GetListener(FeatureAttribute.SolutionCrawlerLegacy); + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new SymbolTreeInfoCacheService(workspaceServices.Workspace, _listener); +} From c1a247ee36a6448dd2e9e38ba66ae1f0f20ad44e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:13:40 -0700 Subject: [PATCH 24/39] Simplify --- .../SymbolTree/SymbolTreeInfoCacheServiceFactory.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs index 28140e9f31e32..85772f815bdf9 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs +++ b/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs @@ -3,19 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree; From 02e40dd43fd801ebf04de7fe7077262fc6c88f68 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:19:06 -0700 Subject: [PATCH 25/39] Move code back --- .../Core/Portable/Microsoft.CodeAnalysis.Features.csproj | 3 +++ .../FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs | 0 .../Core/Portable/FindSymbols/SymbolTree/MetadataInfo.cs | 0 .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 7 ++++++- .../SymbolTree/SymbolTreeInfoCacheServiceFactory.cs | 0 5 files changed, 9 insertions(+), 1 deletion(-) rename src/{Features => Workspaces}/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs (100%) rename src/{Features => Workspaces}/Core/Portable/FindSymbols/SymbolTree/MetadataInfo.cs (100%) rename src/{Features => Workspaces}/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs (97%) rename src/{Features => Workspaces}/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs (100%) diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index abd367ee55ef3..2dea58f86eed4 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -142,6 +142,9 @@ + + + diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs similarity index 100% rename from src/Features/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs rename to src/Workspaces/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/MetadataInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/MetadataInfo.cs similarity index 100% rename from src/Features/Core/Portable/FindSymbols/SymbolTree/MetadataInfo.cs rename to src/Workspaces/Core/Portable/FindSymbols/SymbolTree/MetadataInfo.cs diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs similarity index 97% rename from src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs rename to src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index b4bcd4122f83b..c06c8f2ef5a19 100644 --- a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -20,6 +20,11 @@ internal sealed partial class SymbolTreeInfoCacheServiceFactory { internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService, IDisposable { + /// + /// Same value as SolutionCrawlerTimeSpan.EntireProjectWorkerBackOff + /// + private static readonly TimeSpan EntireProjectWorkerBackOff = TimeSpan.FromMilliseconds(5000); + private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; private readonly ConcurrentDictionary _projectIdToInfo = new(); @@ -40,7 +45,7 @@ public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationLis { _workspace = workspace; _workQueue = new AsyncBatchingWorkQueue( - SolutionCrawlerTimeSpan.EntireProjectWorkerBackOff, + EntireProjectWorkerBackOff, ProcessProjectsAsync, EqualityComparer.Default, listener, diff --git a/src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs similarity index 100% rename from src/Features/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs rename to src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheServiceFactory.cs From 4cc9ce7ac7da450540b9ce889de503c810d6423d Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Sat, 15 Oct 2022 22:20:03 -0700 Subject: [PATCH 26/39] Update src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj --- .../Core/Portable/Microsoft.CodeAnalysis.Features.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index 2dea58f86eed4..abd367ee55ef3 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -142,9 +142,6 @@ - - - From 3fefbe90c7dd9fda9fc3a825affaa0dd61cc3e26 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Sat, 15 Oct 2022 22:21:50 -0700 Subject: [PATCH 27/39] Update src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index c06c8f2ef5a19..d4723cbc4001e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -148,8 +148,6 @@ private async Task AnalyzeProjectAsync(Project project, CancellationToken cancel // We can compute source-symbols in parallel with the metadata symbols. tasks.Add(CreateWorkAsync(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); - // the metadata references from a single project can be computed in parallel. - foreach (var reference in project.MetadataReferences.OfType().Distinct()) { // And tasks to update the symboltree for all metadata references. As these are all distinct, they can From eb9d878513e8ed1432d99afd7285931650c1277d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:22:49 -0700 Subject: [PATCH 28/39] Cleanup --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index c06c8f2ef5a19..d7a4a7faf7878 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -163,7 +163,7 @@ private async Task AnalyzeProjectAsync(Project project, CancellationToken cancel private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) { - // Find the top-level-version of this project. We only want to recompute if it has changed. This is + // Find the top-level-semantic-version of this project. We only want to recompute if it has changed. This is // because the symboltree contains the names of the types/namespaces in the project and would not change // if the semantic-version of the project hasn't changed. We also do not need to check the 'dependent // version'. As this is just tracking parent/child relationships of namespace/type names for the source @@ -207,8 +207,6 @@ private async Task UpdateReferenceAsync( Contract.ThrowIfNull(info); Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); - // Note, getting the info may fail (for example, bogus metadata). That's ok. We still want to cache - // that result so that don't try to continuously produce this info over and over again. metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); _peReferenceToInfo[reference] = metadataInfo; } From e5b42e0e2c95178723a8951fe509ba63f8568c89 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:23:25 -0700 Subject: [PATCH 29/39] Remove --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index d7a4a7faf7878..bcd0392ea5d88 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree; From 6c95a5edf06e3a37128e49a5ced5fc4a7e38f955 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:33:12 -0700 Subject: [PATCH 30/39] Include version in the info --- .../FindSymbols/SymbolTree/SymbolTreeInfo.cs | 18 +++++++++++++---- .../SymbolTree/SymbolTreeInfoCacheService.cs | 20 +++++++++---------- .../SymbolTree/SymbolTreeInfo_Metadata.cs | 2 +- .../SymbolTreeInfo_Serialization.cs | 4 +++- .../SymbolTree/SymbolTreeInfo_Source.cs | 7 ++++--- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs index ac0b403cd3674..651325273d207 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs @@ -32,6 +32,12 @@ namespace Microsoft.CodeAnalysis.FindSymbols /// internal partial class SymbolTreeInfo : IChecksummedObject { + /// + /// The version of the project if this is a SymbolTreeInfo for + /// source (not metadata). + /// + public VersionStamp SourceSemanticVersion { get; } + public Checksum Checksum { get; } /// @@ -94,24 +100,27 @@ public MultiDictionary.ValueSet GetExtensionMethodI }; private SymbolTreeInfo( + VersionStamp sourceSemanticVersion, Checksum checksum, ImmutableArray sortedNodes, SpellChecker spellChecker, OrderPreservingMultiDictionary inheritanceMap, MultiDictionary? receiverTypeNameToExtensionMethodMap) - : this(checksum, sortedNodes, spellChecker, + : this(sourceSemanticVersion, checksum, sortedNodes, spellChecker, CreateIndexBasedInheritanceMap(sortedNodes, inheritanceMap), receiverTypeNameToExtensionMethodMap) { } private SymbolTreeInfo( + VersionStamp sourceSemanticVersion, Checksum checksum, ImmutableArray sortedNodes, SpellChecker spellChecker, OrderPreservingMultiDictionary inheritanceMap, MultiDictionary? receiverTypeNameToExtensionMethodMap) { + SourceSemanticVersion = sourceSemanticVersion; Checksum = checksum; _nodes = sortedNodes; _spellChecker = spellChecker; @@ -124,7 +133,7 @@ public static SymbolTreeInfo CreateEmpty(Checksum checksum) var unsortedNodes = ImmutableArray.Create(BuilderNode.RootNode); SortNodes(unsortedNodes, out var sortedNodes); - return new SymbolTreeInfo(checksum, sortedNodes, + return new SymbolTreeInfo(VersionStamp.Default, checksum, sortedNodes, CreateSpellChecker(checksum, sortedNodes), new OrderPreservingMultiDictionary(), new MultiDictionary()); @@ -136,7 +145,7 @@ public SymbolTreeInfo WithChecksum(Checksum checksum) return this; return new SymbolTreeInfo( - checksum, _nodes, _spellChecker, _inheritanceMap, _receiverTypeNameToExtensionMethodMap); + SourceSemanticVersion, checksum, _nodes, _spellChecker, _inheritanceMap, _receiverTypeNameToExtensionMethodMap); } public Task> FindAsync( @@ -471,6 +480,7 @@ internal void AssertEquivalentTo(SymbolTreeInfo other) } private static SymbolTreeInfo CreateSymbolTreeInfo( + VersionStamp sourceSemanticVersion, Checksum checksum, ImmutableArray unsortedNodes, OrderPreservingMultiDictionary inheritanceMap, @@ -480,7 +490,7 @@ private static SymbolTreeInfo CreateSymbolTreeInfo( var spellChecker = CreateSpellChecker(checksum, sortedNodes); return new SymbolTreeInfo( - checksum, sortedNodes, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap); + sourceSemanticVersion, checksum, sortedNodes, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap); } private static OrderPreservingMultiDictionary CreateIndexBasedInheritanceMap( diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 7044dd1014f46..7e822e0111347 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -26,7 +26,7 @@ internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheS private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; - private readonly ConcurrentDictionary _projectIdToInfo = new(); + private readonly ConcurrentDictionary _projectIdToInfo = new(); private readonly ConcurrentDictionary _peReferenceToInfo = new(); private readonly CancellationTokenSource _tokenSource = new(); @@ -102,7 +102,7 @@ public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancel // See if the last value produced exactly matches what the caller is asking for. If so, return that. if (_projectIdToInfo.TryGetValue(project.Id, out var projectInfo)) - return projectInfo.info; + return projectInfo; // If we didn't have it in our cache, see if we can load some version of it from disk. var info = await SymbolTreeInfo.LoadAnyInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false); @@ -112,7 +112,7 @@ public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancel // attempt to add this item to the map. But defer to whatever is in the map now if something else beat // us to this. Don't provide a version here so that the next time we update this data it will get // overwritten with the latest computed data. - return _projectIdToInfo.GetOrAdd(project.Id, (semanticVersion: default, info)).info; + return _projectIdToInfo.GetOrAdd(project.Id, info); } private async ValueTask ProcessProjectsAsync( @@ -168,23 +168,23 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation var semanticVersion = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); if (!_projectIdToInfo.TryGetValue(project.Id, out var projectInfo) || - projectInfo.semanticVersion != semanticVersion) + projectInfo.SourceSemanticVersion != semanticVersion) { // If the checksum is the same (which can happen if we loaded the previous index from disk), then no // need to recompute. var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - if (projectInfo.info.Checksum != checksum) + if (projectInfo.Checksum != checksum) { // Otherwise, looks like things changed. Compute and persist the latest index. - var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( - project, checksum, cancellationToken).ConfigureAwait(false); + projectInfo = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( + project, semanticVersion, checksum, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(info); - Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + Contract.ThrowIfNull(projectInfo); + Contract.ThrowIfTrue(projectInfo.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); // Mark that we're up to date with this project. Future calls with the same semantic-version or // checksum can bail out immediately. - _projectIdToInfo[project.Id] = (semanticVersion, info); + _projectIdToInfo[project.Id] = projectInfo; } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index 55966325c3d1a..c2031903fa2bc 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -327,7 +327,7 @@ internal SymbolTreeInfo Create() var unsortedNodes = GenerateUnsortedNodes(receiverTypeNameToExtensionMethodMap); return CreateSymbolTreeInfo( - _checksum, unsortedNodes, _inheritanceMap, receiverTypeNameToExtensionMethodMap); + sourceSemanticVersion: VersionStamp.Default, _checksum, unsortedNodes, _inheritanceMap, receiverTypeNameToExtensionMethodMap); } public void Dispose() diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index 38b8219b169fb..d7effd3b62ea0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -192,6 +192,8 @@ static IEnumerable> GroupByName(ReadOnlyMemory sorted try { + var version = VersionStamp.ReadFrom(reader); + var nodeCount = reader.ReadInt32(); var nodes = ArrayBuilder.GetInstance(nodeCount); while (nodes.Count < nodeCount) @@ -251,7 +253,7 @@ static IEnumerable> GroupByName(ReadOnlyMemory sorted return null; return new SymbolTreeInfo( - checksum, nodeArray, spellChecker, inheritanceMap, + version, checksum, nodeArray, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap); } catch diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index 274f5b27509aa..17739c72142c7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -33,7 +33,7 @@ private static string GetSourceKeySuffix(Project project) => "_Source_" + project.FilePath; public static Task GetInfoForSourceAssemblyAsync( - Project project, Checksum checksum, CancellationToken cancellationToken) + Project project, VersionStamp sourceSemanticVersion, Checksum checksum, CancellationToken cancellationToken) { var solution = project.Solution; @@ -41,7 +41,7 @@ public static Task GetInfoForSourceAssemblyAsync( solution.Services, SolutionKey.ToSolutionKey(solution), checksum, - createAsync: checksum => CreateSourceSymbolTreeInfoAsync(project, checksum, cancellationToken), + createAsync: checksum => CreateSourceSymbolTreeInfoAsync(project, sourceSemanticVersion, checksum, cancellationToken), keySuffix: GetSourceKeySuffix(project), cancellationToken); } @@ -115,7 +115,7 @@ private static async Task ComputeSourceSymbolsChecksumAsync(ProjectSta } internal static async ValueTask CreateSourceSymbolTreeInfoAsync( - Project project, Checksum checksum, CancellationToken cancellationToken) + Project project, VersionStamp sourceSemanticVersion, Checksum checksum, CancellationToken cancellationToken) { var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var assembly = compilation?.Assembly; @@ -128,6 +128,7 @@ internal static async ValueTask CreateSourceSymbolTreeInfoAsync( GenerateSourceNodes(assembly.GlobalNamespace, unsortedNodes, s_getMembersNoPrivate); return CreateSymbolTreeInfo( + sourceSemanticVersion, checksum, unsortedNodes.ToImmutableAndFree(), inheritanceMap: new OrderPreservingMultiDictionary(), From fe073884be13c1003168e3c8b4f5bad04618f8da Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:33:48 -0700 Subject: [PATCH 31/39] Fix serialization --- .../FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index d7effd3b62ea0..b5414e65921b6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -111,6 +111,8 @@ private static async Task LoadOrCreateAsync( public void WriteTo(ObjectWriter writer) { + this.SourceSemanticVersion.WriteTo(writer); + writer.WriteInt32(_nodes.Length); foreach (var group in GroupByName(_nodes.AsMemory())) { From 95c358bc995e417299b1e8e89d5363445b2ee1d9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:36:09 -0700 Subject: [PATCH 32/39] Simplify --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 7e822e0111347..26d05b9e3751f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -147,13 +147,10 @@ private async Task AnalyzeProjectAsync(Project project, CancellationToken cancel // We can compute source-symbols in parallel with the metadata symbols. tasks.Add(CreateWorkAsync(() => this.UpdateSourceSymbolTreeInfoAsync(project, cancellationToken), cancellationToken)); + // Add tasks to update the symboltree for all metadata references. As these are all distinct, they can run + // in parallel as we won't be trying to update the associated data for the same reference at the same time. foreach (var reference in project.MetadataReferences.OfType().Distinct()) - { - // And tasks to update the symboltree for all metadata references. As these are all distinct, they can - // run in parallel as we won't be trying to update the associated data for the same reference at the - // same time. tasks.Add(CreateWorkAsync(() => UpdateReferenceAsync(project, reference, cancellationToken), cancellationToken)); - } await Task.WhenAll(tasks).ConfigureAwait(false); } From 43a359d1ee141041a11045810751cba3792c2fe5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:37:35 -0700 Subject: [PATCH 33/39] UPdate version --- .../FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index b5414e65921b6..faecd6d921fc3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; @@ -21,7 +19,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols internal partial class SymbolTreeInfo : IObjectWritable { private const string PrefixSymbolTreeInfo = ""; - private static readonly Checksum SerializationFormatChecksum = Checksum.Create("23"); + private static readonly Checksum SerializationFormatChecksum = Checksum.Create("24"); /// /// Generalized function for loading/creating/persisting data. Used as the common core code for serialization From 5e8420a76dd53cf2aa693f2635786141f01e31cf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 15 Oct 2022 22:44:15 -0700 Subject: [PATCH 34/39] Improve reading --- .../SymbolTree/SymbolTreeInfo_Serialization.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index faecd6d921fc3..b80fffca67f2f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -184,8 +184,7 @@ static IEnumerable> GroupByName(ReadOnlyMemory sorted } private static SymbolTreeInfo? TryReadSymbolTreeInfo( - ObjectReader reader, - Checksum checksum) + ObjectReader reader, Checksum checksum) { if (reader == null) return null; @@ -195,12 +194,13 @@ static IEnumerable> GroupByName(ReadOnlyMemory sorted var version = VersionStamp.ReadFrom(reader); var nodeCount = reader.ReadInt32(); - var nodes = ArrayBuilder.GetInstance(nodeCount); - while (nodes.Count < nodeCount) + using var _ = ArrayBuilder.GetInstance(nodeCount, out var nodes); + + for (var i = 0; i < nodeCount; i++) { var name = reader.ReadString(); var groupCount = reader.ReadInt32(); - for (var i = 0; i < groupCount; i++) + for (var j = 0; j < groupCount; j++) { var parentIndex = reader.ReadInt32(); nodes.Add(new Node(name, parentIndex)); @@ -247,14 +247,14 @@ static IEnumerable> GroupByName(ReadOnlyMemory sorted } } - var nodeArray = nodes.ToImmutableAndFree(); var spellChecker = SpellChecker.TryReadFrom(reader); if (spellChecker is null) return null; + var nodeArray = nodes.ToImmutableAndClear(); + return new SymbolTreeInfo( - version, checksum, nodeArray, spellChecker, inheritanceMap, - receiverTypeNameToExtensionMethodMap); + version, checksum, nodeArray, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap); } catch { From f87cf94322cd483900e530ca3f982a7121631d9b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 16 Oct 2022 00:19:41 -0700 Subject: [PATCH 35/39] nrt --- .../Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs | 1 + .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 2 +- src/Workspaces/CoreTest/FindAllDeclarationsTests.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs index 651325273d207..52665ed3a38ce 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs @@ -455,6 +455,7 @@ private void Bind( internal void AssertEquivalentTo(SymbolTreeInfo other) { + Debug.Assert(SourceSemanticVersion.Equals(other.SourceSemanticVersion)); Debug.Assert(Checksum.Equals(other.Checksum)); Debug.Assert(_nodes.Length == other._nodes.Length); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 26d05b9e3751f..e891a433591e5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -170,7 +170,7 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation // If the checksum is the same (which can happen if we loaded the previous index from disk), then no // need to recompute. var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - if (projectInfo.Checksum != checksum) + if (projectInfo?.Checksum != checksum) { // Otherwise, looks like things changed. Compute and persist the latest index. projectInfo = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( diff --git a/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs b/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs index eceb5be1d1379..fac365a24674a 100644 --- a/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs +++ b/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs @@ -668,7 +668,7 @@ public async Task TestSymbolTreeInfoSerialization() // create symbol tree info from assembly var info = await SymbolTreeInfo.CreateSourceSymbolTreeInfoAsync( - project, Checksum.Null, cancellationToken: CancellationToken.None); + project, VersionStamp.Default, Checksum.Null, cancellationToken: CancellationToken.None); using var writerStream = new MemoryStream(); using (var writer = new ObjectWriter(writerStream, leaveOpen: true)) From 0f7ca97a05b2d82a1c54307b75ffc9698535ed13 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 16 Oct 2022 10:30:47 -0700 Subject: [PATCH 36/39] SImplify walk --- .../SymbolTree/SymbolTreeInfo_Source.cs | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index 17739c72142c7..65211cb9470c6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -125,7 +125,7 @@ internal static async ValueTask CreateSourceSymbolTreeInfoAsync( var unsortedNodes = ArrayBuilder.GetInstance(); unsortedNodes.Add(new BuilderNode(assembly.GlobalNamespace.Name, RootNodeParentIndex)); - GenerateSourceNodes(assembly.GlobalNamespace, unsortedNodes, s_getMembersNoPrivate); + GenerateSourceNodes(assembly.GlobalNamespace, unsortedNodes); return CreateSymbolTreeInfo( sourceSemanticVersion, @@ -138,17 +138,16 @@ internal static async ValueTask CreateSourceSymbolTreeInfoAsync( // generate nodes for the global namespace an all descendants private static void GenerateSourceNodes( INamespaceSymbol globalNamespace, - ArrayBuilder list, - Action> lookup) + ArrayBuilder list) { // Add all child members var symbolMap = AllocateSymbolMap(); try { - lookup(globalNamespace, symbolMap); + AddChildSymbols(globalNamespace, symbolMap); foreach (var (name, symbols) in symbolMap) - GenerateSourceNodes(name, 0 /*index of root node*/, symbols, list, lookup); + GenerateSourceNodes(name, 0 /*index of root node*/, symbols, list); } finally { @@ -156,16 +155,12 @@ private static void GenerateSourceNodes( } } - private static readonly Func s_useSymbolNoPrivate = - s => s.CanBeReferencedByName && s.DeclaredAccessibility != Accessibility.Private; - // generate nodes for symbols that share the same name, and all their descendants private static void GenerateSourceNodes( string name, int parentIndex, MultiDictionary.ValueSet symbolsWithSameName, - ArrayBuilder list, - Action> lookup) + ArrayBuilder list) { var node = new BuilderNode(name, parentIndex); var nodeIndex = list.Count; @@ -176,12 +171,10 @@ private static void GenerateSourceNodes( { // Add all child members foreach (var symbol in symbolsWithSameName) - { - lookup(symbol, symbolMap); - } + AddChildSymbols(symbol, symbolMap); foreach (var (symbolName, symbols) in symbolMap) - GenerateSourceNodes(symbolName, nodeIndex, symbols, list, lookup); + GenerateSourceNodes(symbolName, nodeIndex, symbols, list); } finally { @@ -189,21 +182,29 @@ private static void GenerateSourceNodes( } } - private static readonly Action> s_getMembersNoPrivate = - (symbol, symbolMap) => AddSymbol(symbol, symbolMap, s_useSymbolNoPrivate); - - private static void AddSymbol(ISymbol symbol, MultiDictionary symbolMap, Func useSymbol) + private static void AddChildSymbols(ISymbol symbol, MultiDictionary symbolMap) { - if (symbol is INamespaceOrTypeSymbol nt) + if (symbol is INamespaceSymbol @namespace) { - foreach (var member in nt.GetMembers()) + foreach (var member in @namespace.GetMembers()) { - if (useSymbol(member)) - { + if (UseSymbol(member)) symbolMap.Add(member.Name, member); - } } } + else if (symbol is INamedTypeSymbol namedType) + { + foreach (var member in namedType.GetTypeMembers()) + { + if (UseSymbol(member)) + symbolMap.Add(member.Name, member); + } + } + + return; + + static bool UseSymbol(ISymbol s) + => s.CanBeReferencedByName && s.DeclaredAccessibility != Accessibility.Private; } } } From cb6490918f5726beb21ae29a12b92cd445a43667 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 16 Oct 2022 12:33:46 -0700 Subject: [PATCH 37/39] Revert and add test showing purpose In progress Do not realize member symbols --- .../AddImport/AddImportCrossLanguageTests.vb | 54 ++++++++ .../SymbolTree/SymbolTreeInfo_Source.cs | 131 ++++++++++-------- 2 files changed, 127 insertions(+), 58 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb index ff0cae4d9b3c4..348eb6832d533 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb @@ -302,6 +302,60 @@ namespace CSAssembly2 Await TestMissing(input) End Function + + Public Async Function AddProjectReference_CSharpToCSharp_ExtensionMethod() As Task + Dim input = + + + +using System.Collections.Generic; +namespace CSAssembly1 +{ + public static class Class1 + { + public static void Goo(this int x) { } + } +} + + + + + +namespace CSAssembly2 +{ + public class Class2 + { + void Bar(int i) + { + i.$$Goo(); + } + } +} + + + + + Dim expected = + +using CSAssembly1; + +namespace CSAssembly2 +{ + public class Class2 + { + void Bar(int i) + { + i.Goo(); + } + } +} + .Value.Trim() + + Await TestAsync( + input, expected, codeActionIndex:=0, addedReference:="CSAssembly1", + glyphTags:=WellKnownTagArrays.CSharpProject, onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) + End Function + Public Async Function TestAddProjectReference_CSharpToCSharp_WithProjectRenamed() As Task Dim input = diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index 65211cb9470c6..eb6eeb5bcf546 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -17,13 +18,13 @@ namespace Microsoft.CodeAnalysis.FindSymbols { internal partial class SymbolTreeInfo { - private static readonly SimplePool> s_symbolMapPool = - new(() => new MultiDictionary()); + private static readonly SimplePool> s_symbolMapPool = + new(() => new MultiDictionary()); - private static MultiDictionary AllocateSymbolMap() + private static MultiDictionary AllocateSymbolMap() => s_symbolMapPool.Allocate(); - private static void FreeSymbolMap(MultiDictionary symbolMap) + private static void FreeSymbolMap(MultiDictionary symbolMap) { symbolMap.Clear(); s_symbolMapPool.Free(symbolMap); @@ -122,89 +123,103 @@ internal static async ValueTask CreateSourceSymbolTreeInfoAsync( if (assembly == null) return CreateEmpty(checksum); - var unsortedNodes = ArrayBuilder.GetInstance(); - unsortedNodes.Add(new BuilderNode(assembly.GlobalNamespace.Name, RootNodeParentIndex)); - - GenerateSourceNodes(assembly.GlobalNamespace, unsortedNodes); - - return CreateSymbolTreeInfo( - sourceSemanticVersion, - checksum, - unsortedNodes.ToImmutableAndFree(), - inheritanceMap: new OrderPreservingMultiDictionary(), - receiverTypeNameToExtensionMethodMap: null); - } - - // generate nodes for the global namespace an all descendants - private static void GenerateSourceNodes( - INamespaceSymbol globalNamespace, - ArrayBuilder list) - { - // Add all child members - var symbolMap = AllocateSymbolMap(); + var symbolsByName = AllocateSymbolMap(); try { - AddChildSymbols(globalNamespace, symbolMap); - - foreach (var (name, symbols) in symbolMap) - GenerateSourceNodes(name, 0 /*index of root node*/, symbols, list); + // generate nodes for the global namespace and all descendants + using var _ = ArrayBuilder.GetInstance(out var unsortedBuilderNodes); + + var globalNamespaceName = assembly.GlobalNamespace.Name; + symbolsByName.Add(globalNamespaceName, assembly.GlobalNamespace); + GenerateSourceNodes(globalNamespaceName, RootNodeParentIndex, symbolsByName[globalNamespaceName], unsortedBuilderNodes); + + return CreateSymbolTreeInfo( + sourceSemanticVersion, + checksum, + unsortedBuilderNodes.ToImmutable(), + inheritanceMap: new OrderPreservingMultiDictionary(), + receiverTypeNameToExtensionMethodMap: null); } finally { - FreeSymbolMap(symbolMap); + FreeSymbolMap(symbolsByName); } } - // generate nodes for symbols that share the same name, and all their descendants private static void GenerateSourceNodes( string name, int parentIndex, - MultiDictionary.ValueSet symbolsWithSameName, - ArrayBuilder list) + MultiDictionary.ValueSet symbolsWithName, + ArrayBuilder unsortedBuilderNodes) { - var node = new BuilderNode(name, parentIndex); - var nodeIndex = list.Count; - list.Add(node); + // Add the node for this name, and record which parent it points at. + unsortedBuilderNodes.Add(new BuilderNode(name, parentIndex)); + + // Keep track of the index of the node we just added. + var thisSymbolIndex = unsortedBuilderNodes.Count - 1; - var symbolMap = AllocateSymbolMap(); + var childSymbolsByName = AllocateSymbolMap(); + using var _ = PooledHashSet.GetInstance(out var seenNames); try { - // Add all child members - foreach (var symbol in symbolsWithSameName) - AddChildSymbols(symbol, symbolMap); + // Walk the symbols with this name, and add all their child namespaces and types, grouping them together + // based on their name. There may be multiple (for example, Action, Action, etc.) + foreach (var symbol in symbolsWithName) + AddChildNamespacesAndTypes(symbol, childSymbolsByName); + + // Now, go through all those groups and make the single mapping from their name to the builder-node we + // just created above, and recurse into their children as well. + foreach (var (childName, childSymbols) in childSymbolsByName) + { + seenNames.Add(childName); + GenerateSourceNodes(childName, thisSymbolIndex, childSymbols, unsortedBuilderNodes); + } + + // The above loops only create nodes for namespaces and types. we also want nodes for members as well. + // However, we do not want to force the symbols for those members to be created just to get the names. + // + // So walk through the symbols again, and for the named-types grab all the member-names contained + // therein. If we didn't already see that child name when recursing above, then make a builder-node for + // it that points to the builder-node we just created above. - foreach (var (symbolName, symbols) in symbolMap) - GenerateSourceNodes(symbolName, nodeIndex, symbols, list); + foreach (var symbol in symbolsWithName) + { + if (symbol is INamedTypeSymbol namedType) + { + foreach (var childMemberName in namedType.MemberNames) + { + if (seenNames.Add(childMemberName)) + unsortedBuilderNodes.Add(new BuilderNode(childMemberName, thisSymbolIndex)); + } + } + } } finally { - FreeSymbolMap(symbolMap); + FreeSymbolMap(childSymbolsByName); } } - private static void AddChildSymbols(ISymbol symbol, MultiDictionary symbolMap) + private static void AddChildNamespacesAndTypes(INamespaceOrTypeSymbol symbol, MultiDictionary symbolMap) { - if (symbol is INamespaceSymbol @namespace) + if (symbol is INamespaceSymbol namespaceSymbol) { - foreach (var member in @namespace.GetMembers()) + // grab all children of the namespace (should only be namespaces or types only). + foreach (var child in namespaceSymbol.GetMembers()) { - if (UseSymbol(member)) - symbolMap.Add(member.Name, member); + // Only assert here in case the language ever allows other types of symbols within a namespace. + Debug.Assert(child is INamespaceOrTypeSymbol); + if (child is INamespaceOrTypeSymbol childNamespaceOrType) + symbolMap.Add(childNamespaceOrType.Name, childNamespaceOrType); } } - else if (symbol is INamedTypeSymbol namedType) + else if (symbol is INamedTypeSymbol namedTypeSymbol) { - foreach (var member in namedType.GetTypeMembers()) - { - if (UseSymbol(member)) - symbolMap.Add(member.Name, member); - } + // for named-types, we only need to recurse into child types. Call GetTypeMembers instead of GetMembers + // so we do not cause all child symbols to be created. + foreach (var childType in namedTypeSymbol.GetTypeMembers()) + symbolMap.Add(childType.Name, childType); } - - return; - - static bool UseSymbol(ISymbol s) - => s.CanBeReferencedByName && s.DeclaredAccessibility != Accessibility.Private; } } } From bfca3bcac9e0767c7d72c6c80c3b2b7326502c42 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Oct 2022 09:36:27 -0700 Subject: [PATCH 38/39] Move versionstamp back --- .../FindSymbols/SymbolTree/SymbolTreeInfo.cs | 19 ++++-------------- .../SymbolTree/SymbolTreeInfoCacheService.cs | 20 +++++++++---------- .../SymbolTree/SymbolTreeInfo_Metadata.cs | 2 +- .../SymbolTreeInfo_Serialization.cs | 6 +----- .../SymbolTree/SymbolTreeInfo_Source.cs | 7 +++---- .../CoreTest/FindAllDeclarationsTests.cs | 2 +- 6 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs index 52665ed3a38ce..ac0b403cd3674 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs @@ -32,12 +32,6 @@ namespace Microsoft.CodeAnalysis.FindSymbols /// internal partial class SymbolTreeInfo : IChecksummedObject { - /// - /// The version of the project if this is a SymbolTreeInfo for - /// source (not metadata). - /// - public VersionStamp SourceSemanticVersion { get; } - public Checksum Checksum { get; } /// @@ -100,27 +94,24 @@ public MultiDictionary.ValueSet GetExtensionMethodI }; private SymbolTreeInfo( - VersionStamp sourceSemanticVersion, Checksum checksum, ImmutableArray sortedNodes, SpellChecker spellChecker, OrderPreservingMultiDictionary inheritanceMap, MultiDictionary? receiverTypeNameToExtensionMethodMap) - : this(sourceSemanticVersion, checksum, sortedNodes, spellChecker, + : this(checksum, sortedNodes, spellChecker, CreateIndexBasedInheritanceMap(sortedNodes, inheritanceMap), receiverTypeNameToExtensionMethodMap) { } private SymbolTreeInfo( - VersionStamp sourceSemanticVersion, Checksum checksum, ImmutableArray sortedNodes, SpellChecker spellChecker, OrderPreservingMultiDictionary inheritanceMap, MultiDictionary? receiverTypeNameToExtensionMethodMap) { - SourceSemanticVersion = sourceSemanticVersion; Checksum = checksum; _nodes = sortedNodes; _spellChecker = spellChecker; @@ -133,7 +124,7 @@ public static SymbolTreeInfo CreateEmpty(Checksum checksum) var unsortedNodes = ImmutableArray.Create(BuilderNode.RootNode); SortNodes(unsortedNodes, out var sortedNodes); - return new SymbolTreeInfo(VersionStamp.Default, checksum, sortedNodes, + return new SymbolTreeInfo(checksum, sortedNodes, CreateSpellChecker(checksum, sortedNodes), new OrderPreservingMultiDictionary(), new MultiDictionary()); @@ -145,7 +136,7 @@ public SymbolTreeInfo WithChecksum(Checksum checksum) return this; return new SymbolTreeInfo( - SourceSemanticVersion, checksum, _nodes, _spellChecker, _inheritanceMap, _receiverTypeNameToExtensionMethodMap); + checksum, _nodes, _spellChecker, _inheritanceMap, _receiverTypeNameToExtensionMethodMap); } public Task> FindAsync( @@ -455,7 +446,6 @@ private void Bind( internal void AssertEquivalentTo(SymbolTreeInfo other) { - Debug.Assert(SourceSemanticVersion.Equals(other.SourceSemanticVersion)); Debug.Assert(Checksum.Equals(other.Checksum)); Debug.Assert(_nodes.Length == other._nodes.Length); @@ -481,7 +471,6 @@ internal void AssertEquivalentTo(SymbolTreeInfo other) } private static SymbolTreeInfo CreateSymbolTreeInfo( - VersionStamp sourceSemanticVersion, Checksum checksum, ImmutableArray unsortedNodes, OrderPreservingMultiDictionary inheritanceMap, @@ -491,7 +480,7 @@ private static SymbolTreeInfo CreateSymbolTreeInfo( var spellChecker = CreateSpellChecker(checksum, sortedNodes); return new SymbolTreeInfo( - sourceSemanticVersion, checksum, sortedNodes, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap); + checksum, sortedNodes, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap); } private static OrderPreservingMultiDictionary CreateIndexBasedInheritanceMap( diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index e891a433591e5..ee45adbdb4814 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -26,7 +26,7 @@ internal sealed partial class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheS private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; - private readonly ConcurrentDictionary _projectIdToInfo = new(); + private readonly ConcurrentDictionary _projectIdToInfo = new(); private readonly ConcurrentDictionary _peReferenceToInfo = new(); private readonly CancellationTokenSource _tokenSource = new(); @@ -102,7 +102,7 @@ public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancel // See if the last value produced exactly matches what the caller is asking for. If so, return that. if (_projectIdToInfo.TryGetValue(project.Id, out var projectInfo)) - return projectInfo; + return projectInfo.info; // If we didn't have it in our cache, see if we can load some version of it from disk. var info = await SymbolTreeInfo.LoadAnyInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false); @@ -112,7 +112,7 @@ public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancel // attempt to add this item to the map. But defer to whatever is in the map now if something else beat // us to this. Don't provide a version here so that the next time we update this data it will get // overwritten with the latest computed data. - return _projectIdToInfo.GetOrAdd(project.Id, info); + return _projectIdToInfo.GetOrAdd(project.Id, (semanticVersion: default, info)).info; } private async ValueTask ProcessProjectsAsync( @@ -165,23 +165,23 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation var semanticVersion = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); if (!_projectIdToInfo.TryGetValue(project.Id, out var projectInfo) || - projectInfo.SourceSemanticVersion != semanticVersion) + projectInfo.semanticVersion != semanticVersion) { // If the checksum is the same (which can happen if we loaded the previous index from disk), then no // need to recompute. var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); - if (projectInfo?.Checksum != checksum) + if (projectInfo.info?.Checksum != checksum) { // Otherwise, looks like things changed. Compute and persist the latest index. - projectInfo = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( - project, semanticVersion, checksum, cancellationToken).ConfigureAwait(false); + var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( + project, checksum, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(projectInfo); - Contract.ThrowIfTrue(projectInfo.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + Contract.ThrowIfNull(info); + Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); // Mark that we're up to date with this project. Future calls with the same semantic-version or // checksum can bail out immediately. - _projectIdToInfo[project.Id] = projectInfo; + _projectIdToInfo[project.Id] = (semanticVersion, info); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index c2031903fa2bc..55966325c3d1a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -327,7 +327,7 @@ internal SymbolTreeInfo Create() var unsortedNodes = GenerateUnsortedNodes(receiverTypeNameToExtensionMethodMap); return CreateSymbolTreeInfo( - sourceSemanticVersion: VersionStamp.Default, _checksum, unsortedNodes, _inheritanceMap, receiverTypeNameToExtensionMethodMap); + _checksum, unsortedNodes, _inheritanceMap, receiverTypeNameToExtensionMethodMap); } public void Dispose() diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index b80fffca67f2f..4fdc3e7fe2e79 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -109,8 +109,6 @@ private static async Task LoadOrCreateAsync( public void WriteTo(ObjectWriter writer) { - this.SourceSemanticVersion.WriteTo(writer); - writer.WriteInt32(_nodes.Length); foreach (var group in GroupByName(_nodes.AsMemory())) { @@ -191,8 +189,6 @@ static IEnumerable> GroupByName(ReadOnlyMemory sorted try { - var version = VersionStamp.ReadFrom(reader); - var nodeCount = reader.ReadInt32(); using var _ = ArrayBuilder.GetInstance(nodeCount, out var nodes); @@ -254,7 +250,7 @@ static IEnumerable> GroupByName(ReadOnlyMemory sorted var nodeArray = nodes.ToImmutableAndClear(); return new SymbolTreeInfo( - version, checksum, nodeArray, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap); + checksum, nodeArray, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap); } catch { diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index eb6eeb5bcf546..f13b11f868bc7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -34,7 +34,7 @@ private static string GetSourceKeySuffix(Project project) => "_Source_" + project.FilePath; public static Task GetInfoForSourceAssemblyAsync( - Project project, VersionStamp sourceSemanticVersion, Checksum checksum, CancellationToken cancellationToken) + Project project, Checksum checksum, CancellationToken cancellationToken) { var solution = project.Solution; @@ -42,7 +42,7 @@ public static Task GetInfoForSourceAssemblyAsync( solution.Services, SolutionKey.ToSolutionKey(solution), checksum, - createAsync: checksum => CreateSourceSymbolTreeInfoAsync(project, sourceSemanticVersion, checksum, cancellationToken), + createAsync: checksum => CreateSourceSymbolTreeInfoAsync(project, checksum, cancellationToken), keySuffix: GetSourceKeySuffix(project), cancellationToken); } @@ -116,7 +116,7 @@ private static async Task ComputeSourceSymbolsChecksumAsync(ProjectSta } internal static async ValueTask CreateSourceSymbolTreeInfoAsync( - Project project, VersionStamp sourceSemanticVersion, Checksum checksum, CancellationToken cancellationToken) + Project project, Checksum checksum, CancellationToken cancellationToken) { var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var assembly = compilation?.Assembly; @@ -134,7 +134,6 @@ internal static async ValueTask CreateSourceSymbolTreeInfoAsync( GenerateSourceNodes(globalNamespaceName, RootNodeParentIndex, symbolsByName[globalNamespaceName], unsortedBuilderNodes); return CreateSymbolTreeInfo( - sourceSemanticVersion, checksum, unsortedBuilderNodes.ToImmutable(), inheritanceMap: new OrderPreservingMultiDictionary(), diff --git a/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs b/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs index fac365a24674a..eceb5be1d1379 100644 --- a/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs +++ b/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs @@ -668,7 +668,7 @@ public async Task TestSymbolTreeInfoSerialization() // create symbol tree info from assembly var info = await SymbolTreeInfo.CreateSourceSymbolTreeInfoAsync( - project, VersionStamp.Default, Checksum.Null, cancellationToken: CancellationToken.None); + project, Checksum.Null, cancellationToken: CancellationToken.None); using var writerStream = new MemoryStream(); using (var writer = new ObjectWriter(writerStream, leaveOpen: true)) From 58833a4084276c064a4e2b45abaa39cca4893c70 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Oct 2022 11:51:46 -0700 Subject: [PATCH 39/39] Make private --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index ee45adbdb4814..b1510a0680cb9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -56,7 +56,7 @@ public SymbolTreeInfoCacheService(Workspace workspace, IAsynchronousOperationLis void IDisposable.Dispose() => _tokenSource.Cancel(); - public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancellationToken) + private Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancellationToken) => Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap(); ///