diff --git a/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests.cs b/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests.cs index eb6e97d8a621b..8b61883e334bd 100644 --- a/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests.cs +++ b/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests.cs @@ -344,7 +344,7 @@ private static void CheckCharacters(VirtualCharSequence virtualChars, ref int po private static string And(params string[] regexes) { - var conj = $"({regexes[regexes.Length - 1]})"; + var conj = $"({regexes[^1]})"; for (var i = regexes.Length - 2; i >= 0; i--) conj = $"(?({regexes[i]}){conj}|[0-[0]])"; diff --git a/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs b/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs index 3515897e3310b..6cff75d9f36c9 100644 --- a/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs +++ b/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs @@ -81,8 +81,8 @@ private static async Task BuildInteractiveContentAsync( // Stack the first paragraph of the documentation comments with the last line of the description // to avoid vertical padding between the two. - var lastElement = elements[elements.Count - 1]; - elements[elements.Count - 1] = new ContainerElement( + var lastElement = elements[^1]; + elements[^1] = new ContainerElement( ContainerElementStyle.Stacked, lastElement, element); diff --git a/src/Features/CSharp/Portable/LineSeparators/CSharpLineSeparatorService.cs b/src/Features/CSharp/Portable/LineSeparators/CSharpLineSeparatorService.cs index 75f87a9720437..36af95476fd8a 100644 --- a/src/Features/CSharp/Portable/LineSeparators/CSharpLineSeparatorService.cs +++ b/src/Features/CSharp/Portable/LineSeparators/CSharpLineSeparatorService.cs @@ -299,7 +299,7 @@ private static void ProcessNodeList(SyntaxList children, ArrayBuilder= 0 && Name != null && Name.Length >= 2 && Name[0] == '[' && Name[Name.Length - 1] == ']'; + return Index >= 0 && Name != null && Name.Length >= 2 && Name[0] == '[' && Name[^1] == ']'; } public bool AppendAsCollectionEntry(Builder result) diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs index 5c90553b23d48..2303b85fedd59 100644 --- a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs @@ -697,7 +697,7 @@ private void FormatMultidimensionalArrayElements(Builder result, Array array, bo string _; FormatObjectRecursive(result, array.GetValue(indices), isRoot: false, debuggerDisplayName: out _); - indices[indices.Length - 1]++; + indices[^1]++; flatIndex++; } } diff --git a/src/VisualStudio/Core/Def/Library/VsNavInfo/NavInfo.cs b/src/VisualStudio/Core/Def/Library/VsNavInfo/NavInfo.cs index 12578c5fe84c1..5e6e6808b042c 100644 --- a/src/VisualStudio/Core/Def/Library/VsNavInfo/NavInfo.cs +++ b/src/VisualStudio/Core/Def/Library/VsNavInfo/NavInfo.cs @@ -59,7 +59,7 @@ public NavInfo( _basePresentationNodes = CreateNodes(expandDottedNames: false); _symbolType = _basePresentationNodes.Length > 0 - ? _basePresentationNodes[_basePresentationNodes.Length - 1].ListType + ? _basePresentationNodes[^1].ListType : 0; } diff --git a/src/VisualStudio/Core/Def/Preview/FileChange.cs b/src/VisualStudio/Core/Def/Preview/FileChange.cs index 2effe84546440..37c83672c3755 100644 --- a/src/VisualStudio/Core/Def/Preview/FileChange.cs +++ b/src/VisualStudio/Core/Def/Preview/FileChange.cs @@ -135,7 +135,7 @@ private static string GetDisplayText(string excerpt) var split = excerpt.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); if (split.Length > 1) { - return string.Format("{0} ... {1}", split[0].Trim(), split[split.Length - 1].Trim()); + return string.Format("{0} ... {1}", split[0].Trim(), split[^1].Trim()); } } diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs index 8692768d94ba5..011c3c055f5bf 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs @@ -114,7 +114,7 @@ private TreeViewItemBase GetPreviousItem() { if (ValueTrackingTreeView.SelectedItem is null) { - return (TreeViewItemBase)ValueTrackingTreeView.Items[ValueTrackingTreeView.Items.Count - 1]; + return (TreeViewItemBase)ValueTrackingTreeView.Items[^1]; } var item = (TreeViewItemBase)ValueTrackingTreeView.SelectedItem; diff --git a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs index 5dbb5103ff05b..659163fe9c21b 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs @@ -1073,7 +1073,7 @@ private bool IsCodeBlock(ITextSnapshot surfaceSnapshot, int position, char ch) private static bool CheckCode(ITextSnapshot snapshot, int position, char ch, string tag, bool checkAt = true) { - if (ch != tag[tag.Length - 1] || position < tag.Length) + if (ch != tag[^1] || position < tag.Length) { return false; } diff --git a/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.AbstractNodeNameGenerator.cs b/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.AbstractNodeNameGenerator.cs index d6e7c57d32ec6..8abf72e54eb40 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.AbstractNodeNameGenerator.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.AbstractNodeNameGenerator.cs @@ -23,7 +23,7 @@ protected abstract class AbstractNodeNameGenerator protected static void AppendDotIfNeeded(StringBuilder builder) { if (builder.Length > 0 && - char.IsLetterOrDigit(builder[builder.Length - 1])) + char.IsLetterOrDigit(builder[^1])) { builder.Append('.'); } diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index 8b661cec79e46..7f6957f670a6a 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; using Microsoft.VisualStudio.PlatformUI; using Roslyn.Test.Utilities; @@ -21,15 +22,23 @@ namespace Microsoft.CodeAnalysis.Remote.UnitTests { internal sealed class SerializationValidator { - private sealed class AssetProvider : AbstractAssetProvider + private sealed class AssetProvider(SerializationValidator validator) : AbstractAssetProvider { - private readonly SerializationValidator _validator; + public override async ValueTask GetAssetAsync(AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken) + => await validator.GetValueAsync(checksum).ConfigureAwait(false); - public AssetProvider(SerializationValidator validator) - => _validator = validator; + public override async ValueTask> GetAssetsAsync(AssetPath assetPath, HashSet checksums, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder<(Checksum checksum, T asset)>.GetInstance(out var result); + + foreach (var checksum in checksums) + { + var value = await GetAssetAsync(assetPath, checksum, cancellationToken).ConfigureAwait(false); + result.Add((checksum, value)); + } - public override async ValueTask GetAssetAsync(AssetHint assetHint, Checksum checksum, CancellationToken cancellationToken) - => await _validator.GetValueAsync(checksum).ConfigureAwait(false); + return result.ToImmutable(); + } } internal sealed class ChecksumObjectCollection : IEnumerable diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index 58ed5c28a5928..6338cb8e0bdde 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -53,10 +53,10 @@ private static async Task TestAssetAsync(object data) var assetSource = new SimpleAssetSource(workspace.Services.GetService(), new Dictionary() { { checksum, data } }); var provider = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - var stored = await provider.GetAssetAsync(assetHint: AssetHint.None, checksum, CancellationToken.None); + var stored = await provider.GetAssetAsync(AssetPath.FullLookupForTesting, checksum, CancellationToken.None); Assert.Equal(data, stored); - var stored2 = await provider.GetAssetsAsync(assetHint: AssetHint.None, [checksum], CancellationToken.None); + var stored2 = await provider.GetAssetsAsync(AssetPath.FullLookupForTesting, new HashSet { checksum }, CancellationToken.None); Assert.Equal(1, stored2.Length); Assert.Equal(checksum, stored2[0].Item1); @@ -83,7 +83,7 @@ public async Task TestAssetSynchronization() var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); var service = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - await service.SynchronizeAssetsAsync(assetHint: AssetHint.None, new HashSet(map.Keys), results: null, CancellationToken.None); + await service.SynchronizeAssetsAsync(AssetPath.FullLookupForTesting, new HashSet(map.Keys), results: null, CancellationToken.None); foreach (var kv in map) { diff --git a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs index ef96a72e3debb..b6eff7bf64e92 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs @@ -175,7 +175,7 @@ private async Task UpdatePathsToRemoteFilesAsync(CollaborationSession session) #pragma warning disable CS8602 // Dereference of a possibly null reference. (Can localRoot be null here?) var splitRoot = localRoot.TrimEnd('\\').Split('\\'); #pragma warning restore CS8602 // Dereference of a possibly null reference. - splitRoot[splitRoot.Length - 1] = "~external"; + splitRoot[^1] = "~external"; var externalPath = string.Join("\\", splitRoot) + "\\"; remoteRootPaths.Add(localRoot); diff --git a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs index 32450aa15116f..37248a1e7031f 100644 --- a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs +++ b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs @@ -56,7 +56,13 @@ internal static class DocumentBasedFixAllProviderHelpers // TODO: consider computing this in parallel. var singleContextDocIdToNewRootOrText = await getFixedDocumentsAsync(fixAllContext, progressTracker).ConfigureAwait(false); - allContextsDocIdToNewRootOrText.AddRange(singleContextDocIdToNewRootOrText); + + // Note: it is safe to blindly add the dictionary for a particular context to the full dictionary. Each + // dictionary will only update documents within that context, and each context represents a distinct + // project, so these should all be distinct without collisions. However, to be very safe, we use an + // overwriting policy here to ensure nothing causes any problems here. + foreach (var kvp in singleContextDocIdToNewRootOrText) + allContextsDocIdToNewRootOrText[kvp.Key] = kvp.Value; } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs index 7f035466d93dd..24fbfecb4bc38 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs @@ -2,30 +2,120 @@ // 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.Runtime.Serialization; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Serialization; /// -/// Optional information passed with an asset synchronization request to allow the request to be scoped down to a -/// particular or . +/// Required information passed with an asset synchronization request to tell the host where to scope the request to. In +/// particular, this is often used to scope to a particular or to avoid +/// having to search the entire solution. /// [DataContract] -internal readonly struct AssetHint +internal readonly struct AssetPath { - public static readonly AssetHint None = default; + /// + /// Instance that will only look up solution-level data when searching for checksums. + /// + public static readonly AssetPath SolutionOnly = new(AssetPathKind.Solution); + + /// + /// Instance that will only look up solution-level, as well as the top level nodes for projects when searching for + /// checksums. It will not descend into projects. + /// + public static readonly AssetPath SolutionAndTopLevelProjectsOnly = new(AssetPathKind.Solution | AssetPathKind.TopLevelProjects); + + /// + /// Special instance, allowed only in tests/debug-asserts, that can do a full lookup across the entire checksum + /// tree. Should not be used in normal release-mode product code. + /// + public static readonly AssetPath FullLookupForTesting = new(AssetPathKind.Solution | AssetPathKind.TopLevelProjects | AssetPathKind.Projects | AssetPathKind.Documents | AssetPathKind.Testing); [DataMember(Order = 0)] - public readonly ProjectId? ProjectId; + private readonly AssetPathKind _kind; [DataMember(Order = 1)] + public readonly ProjectId? ProjectId; + [DataMember(Order = 2)] public readonly DocumentId? DocumentId; - private AssetHint(ProjectId? projectId, DocumentId? documentId) + private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? documentId = null) { + _kind = kind; ProjectId = projectId; DocumentId = documentId; + + // If this isn't a test lookup, and we're searching into projects or documents, then we must have at least a + // projectId to limit the search. If we don't, that risks very expensive searches where we look into *every* + // project in the solution for matches. + if ((kind & AssetPathKind.Testing) == 0) + { + if (IncludeProjects || IncludeDocuments) + Contract.ThrowIfNull(projectId); + } } - public static implicit operator AssetHint(ProjectId projectId) => new(projectId, documentId: null); - public static implicit operator AssetHint(DocumentId documentId) => new(documentId.ProjectId, documentId); + public bool IncludeSolution => (_kind & AssetPathKind.Solution) == AssetPathKind.Solution; + public bool IncludeTopLevelProjects => (_kind & AssetPathKind.TopLevelProjects) == AssetPathKind.TopLevelProjects; + public bool IncludeProjects => (_kind & AssetPathKind.Projects) == AssetPathKind.Projects; + public bool IncludeDocuments => (_kind & AssetPathKind.Documents) == AssetPathKind.Documents; + + /// + /// Searches only for information about this project. + /// + public static implicit operator AssetPath(ProjectId projectId) => new(AssetPathKind.Projects, projectId, documentId: null); + + /// + /// Searches only for information about this document. + /// + public static implicit operator AssetPath(DocumentId documentId) => new(AssetPathKind.Documents, documentId.ProjectId, documentId); + + /// + /// Searches the requested project, and all documents underneath it. Used only in tests. + /// + /// + /// + public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) + => new(AssetPathKind.Solution | AssetPathKind.Projects | AssetPathKind.Testing, projectId); + + /// + /// Searches the requested project, and all documents underneath it. used during normal sync when bulk syncing a + /// project. + /// + /// + /// + public static AssetPath ProjectAndDocuments(ProjectId projectId) + => new(AssetPathKind.Projects | AssetPathKind.Documents, projectId); + + [Flags] + private enum AssetPathKind + { + /// + /// Search solution-level information. + /// + Solution = 1 << 0, + + /// + /// Search projects, without descending into them. In effect, only finding direct ProjectStateChecksum children + /// of the solution. + /// + TopLevelProjects = 1 << 1, + + /// + /// Search projects for results. + /// + Projects = 1 << 2, + + /// + /// Search documents for results. + /// + Documents = 1 << 3, + + /// + /// Indicates that this is a special search performed during testing. These searches are allowed to search + /// everything for expediency purposes. + /// + Testing = 1 << 4, + } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index d99b463c915db..641eeba2963d1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -111,7 +111,7 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) public async Task FindAsync( SolutionCompilationState compilationState, ProjectCone? projectCone, - AssetHint assetHint, + AssetPath assetPath, HashSet searchingChecksumsLeft, Dictionary result, CancellationToken cancellationToken) @@ -120,33 +120,35 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - // verify input - if (searchingChecksumsLeft.Remove(this.Checksum)) - result[this.Checksum] = this; + if (assetPath.IncludeSolution) + { + if (searchingChecksumsLeft.Remove(this.Checksum)) + result[this.Checksum] = this; - if (searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) - result[this.SourceGeneratorExecutionVersionMap] = compilationState.SourceGeneratorExecutionVersionMap; + if (searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) + result[this.SourceGeneratorExecutionVersionMap] = compilationState.SourceGeneratorExecutionVersionMap; - if (searchingChecksumsLeft.Count == 0) - return; + if (searchingChecksumsLeft.Count == 0) + return; - if (compilationState.FrozenSourceGeneratedDocumentStates != null) - { - Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); + if (compilationState.FrozenSourceGeneratedDocumentStates != null) + { + Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); - // This could either be the checksum for the text (which we'll use our regular helper for first)... - await ChecksumCollection.FindAsync(compilationState.FrozenSourceGeneratedDocumentStates, assetHint.DocumentId, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + // This could either be the checksum for the text (which we'll use our regular helper for first)... + await ChecksumCollection.FindAsync(compilationState.FrozenSourceGeneratedDocumentStates, hintDocument: null, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - // ... or one of the identities. In this case, we'll use the fact that there's a 1:1 correspondence between the - // two collections we hold onto. - for (var i = 0; i < FrozenSourceGeneratedDocumentIdentities.Value.Count; i++) - { - var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; - if (searchingChecksumsLeft.Remove(identityChecksum)) + // ... or one of the identities. In this case, we'll use the fact that there's a 1:1 correspondence between the + // two collections we hold onto. + for (var i = 0; i < FrozenSourceGeneratedDocumentIdentities.Value.Count; i++) { - var id = FrozenSourceGeneratedDocuments!.Value.Ids[i]; - Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); - result[identityChecksum] = state.Identity; + var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; + if (searchingChecksumsLeft.Remove(identityChecksum)) + { + var id = FrozenSourceGeneratedDocuments!.Value.Ids[i]; + Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); + result[identityChecksum] = state.Identity; + } } } } @@ -157,13 +159,13 @@ public async Task FindAsync( // If we're not in a project cone, start the search at the top most state-checksum corresponding to the // entire solution. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var solutionChecksums)); - await solutionChecksums.FindAsync(solutionState, projectCone, assetHint, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } else { // Otherwise, grab the top-most state checksum for this cone and search within that. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(projectCone.RootProjectId, out var solutionChecksums)); - await solutionChecksums.FindAsync(solutionState, projectCone, assetHint, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } } } @@ -235,7 +237,7 @@ public static SolutionStateChecksums Deserialize(ObjectReader reader) public async Task FindAsync( SolutionState solution, ProjectCone? projectCone, - AssetHint assetHint, + AssetPath assetPath, HashSet searchingChecksumsLeft, Dictionary result, CancellationToken cancellationToken) @@ -244,39 +246,20 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - // verify input - if (searchingChecksumsLeft.Remove(Checksum)) - result[Checksum] = this; - - if (searchingChecksumsLeft.Remove(Attributes)) - result[Attributes] = solution.SolutionAttributes; - - ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); - - if (searchingChecksumsLeft.Count == 0) - return; - - if (assetHint.ProjectId != null) + if (assetPath.IncludeSolution) { - Contract.ThrowIfTrue( - projectCone != null && !projectCone.Contains(assetHint.ProjectId), - "Requesting an asset outside of the cone explicitly being asked for!"); + if (searchingChecksumsLeft.Remove(Checksum)) + result[Checksum] = this; - var projectState = solution.GetProjectState(assetHint.ProjectId); - if (projectState != null && - projectState.TryGetStateChecksums(out var projectStateChecksums)) - { - await projectStateChecksums.FindAsync(projectState, assetHint.DocumentId, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - } - } - else - { - Contract.ThrowIfTrue(assetHint.DocumentId != null); + if (searchingChecksumsLeft.Remove(Attributes)) + result[Attributes] = solution.SolutionAttributes; - // Before doing a depth-first-search *into* each project, first run across all the project at their top - // level. This ensures that when we are trying to sync the projects referenced by a SolutionStateChecksums' - // instance that we don't unnecessarily walk all documents looking just for those. + ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + } + if (assetPath.IncludeTopLevelProjects) + { + // Caller is trying to fetch the top level ProjectStateChecksums as well. Look for those without diving deeper. foreach (var (projectId, projectState) in solution.ProjectStates) { if (searchingChecksumsLeft.Count == 0) @@ -293,23 +276,45 @@ public async Task FindAsync( result[projectStateChecksums.Checksum] = projectStateChecksums; } } + } - // Now actually do the depth first search into each project. + if (searchingChecksumsLeft.Count == 0) + return; - foreach (var (projectId, projectState) in solution.ProjectStates) + if (assetPath.IncludeProjects || assetPath.IncludeDocuments) + { + if (assetPath.ProjectId is not null) { - if (searchingChecksumsLeft.Count == 0) - break; - - // If we're syncing a project cone, no point at all at looking at child projects of the solution that - // are not in that cone. - if (projectCone != null && !projectCone.Contains(projectId)) - continue; - - // It's possible not all all our projects have checksums. Specifically, we may have only been asked to - // compute the checksum tree for a subset of projects that were all that a feature needed. - if (projectState.TryGetStateChecksums(out var projectStateChecksums)) - await projectStateChecksums.FindAsync(projectState, hintDocument: null, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + // Dive into this project to search for the remaining checksums. + Contract.ThrowIfTrue( + projectCone != null && !projectCone.Contains(assetPath.ProjectId), + "Requesting an asset outside of the cone explicitly being asked for!"); + + var projectState = solution.GetProjectState(assetPath.ProjectId); + if (projectState != null && + projectState.TryGetStateChecksums(out var projectStateChecksums)) + { + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + } + } + else + { + // Full search, used for test purposes. + foreach (var (projectId, projectState) in solution.ProjectStates) + { + if (searchingChecksumsLeft.Count == 0) + break; + + // If we're syncing a project cone, no point at all at looking at child projects of the solution that + // are not in that cone. + if (projectCone != null && !projectCone.Contains(projectId)) + continue; + + // It's possible not all all our projects have checksums. Specifically, we may have only been asked to + // compute the checksum tree for a subset of projects that were all that a feature needed. + if (projectState.TryGetStateChecksums(out var projectStateChecksums)) + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + } } } } @@ -414,7 +419,7 @@ public static ProjectStateChecksums Deserialize(ObjectReader reader) public async Task FindAsync( ProjectState state, - DocumentId? hintDocument, + AssetPath assetPath, HashSet searchingChecksumsLeft, Dictionary result, CancellationToken cancellationToken) @@ -427,40 +432,48 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - if (searchingChecksumsLeft.Remove(Checksum)) + if (assetPath.IncludeProjects) { - result[Checksum] = this; - } + if (searchingChecksumsLeft.Remove(Checksum)) + { + result[Checksum] = this; + } - // It's normal for callers to just want to sync a single ProjectStateChecksum. So quickly check this, without - // doing all the expensive linear work below if we can bail out early here. - if (searchingChecksumsLeft.Count == 0) - return; + // It's normal for callers to just want to sync a single ProjectStateChecksum. So quickly check this, without + // doing all the expensive linear work below if we can bail out early here. + if (searchingChecksumsLeft.Count == 0) + return; - if (searchingChecksumsLeft.Remove(Info)) - { - result[Info] = state.ProjectInfo.Attributes; - } + if (searchingChecksumsLeft.Remove(Info)) + { + result[Info] = state.ProjectInfo.Attributes; + } - if (searchingChecksumsLeft.Remove(CompilationOptions)) - { - Contract.ThrowIfNull(state.CompilationOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); - result[CompilationOptions] = state.CompilationOptions; - } + if (searchingChecksumsLeft.Remove(CompilationOptions)) + { + Contract.ThrowIfNull(state.CompilationOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); + result[CompilationOptions] = state.CompilationOptions; + } - if (searchingChecksumsLeft.Remove(ParseOptions)) - { - Contract.ThrowIfNull(state.ParseOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); - result[ParseOptions] = state.ParseOptions; + if (searchingChecksumsLeft.Remove(ParseOptions)) + { + Contract.ThrowIfNull(state.ParseOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); + result[ParseOptions] = state.ParseOptions; + } + + ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken); + ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken); + ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); } - ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken); - ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken); - ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + if (assetPath.IncludeDocuments) + { + var hintDocument = assetPath.DocumentId; - await ChecksumCollection.FindAsync(state.DocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(state.AdditionalDocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(state.AnalyzerConfigDocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(state.DocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(state.AdditionalDocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(state.AnalyzerConfigDocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + } } } @@ -484,6 +497,13 @@ public void Serialize(ObjectWriter writer) this.Text.WriteTo(writer); } + public void AddAllTo(HashSet checksums) + { + checksums.AddIfNotNullChecksum(this.Checksum); + checksums.AddIfNotNullChecksum(this.Info); + checksums.AddIfNotNullChecksum(this.Text); + } + public static DocumentStateChecksums Deserialize(ObjectReader reader) { return new DocumentStateChecksums( diff --git a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs index 60dd23543517c..5482ce1b62a35 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.Remote.Testing; internal sealed class SimpleAssetSource(ISerializerService serializerService, IReadOnlyDictionary map) : IAssetSource { public ValueTask> GetAssetsAsync( - Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, ISerializerService deserializerService, CancellationToken cancellationToken) + Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService deserializerService, CancellationToken cancellationToken) { var results = new List(); diff --git a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTestBase.cs b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTestBase.cs index 91fc9b2b4328c..dc8986606a5e3 100644 --- a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTestBase.cs +++ b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTestBase.cs @@ -101,11 +101,11 @@ protected static int GetMethodInsertionPoint(VB.Syntax.ClassBlockSyntax classBlo { if (classBlock.Implements.Count > 0) { - return classBlock.Implements[classBlock.Implements.Count - 1].FullSpan.End; + return classBlock.Implements[^1].FullSpan.End; } else if (classBlock.Inherits.Count > 0) { - return classBlock.Inherits[classBlock.Inherits.Count - 1].FullSpan.End; + return classBlock.Inherits[^1].FullSpan.End; } else { diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 7ff66cc7fa912..5feb89710575b 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -2,6 +2,7 @@ // 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.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -20,21 +21,22 @@ internal abstract class AbstractAssetProvider /// /// return data of type T whose checksum is the given checksum /// - public abstract ValueTask GetAssetAsync(AssetHint assetHint, Checksum checksum, CancellationToken cancellationToken); + public abstract ValueTask GetAssetAsync(AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken); + public abstract ValueTask> GetAssetsAsync(AssetPath assetPath, HashSet checksums, CancellationToken cancellationToken); public async Task CreateSolutionInfoAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { - var solutionCompilationChecksums = await GetAssetAsync(AssetHint.None, solutionChecksum, cancellationToken).ConfigureAwait(false); - var solutionChecksums = await GetAssetAsync(AssetHint.None, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + var solutionCompilationChecksums = await GetAssetAsync(AssetPath.SolutionOnly, solutionChecksum, cancellationToken).ConfigureAwait(false); + var solutionChecksums = await GetAssetAsync(AssetPath.SolutionOnly, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); - var solutionAttributes = await GetAssetAsync(AssetHint.None, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); - await GetAssetAsync(AssetHint.None, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + var solutionAttributes = await GetAssetAsync(AssetPath.SolutionOnly, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(AssetPath.SolutionOnly, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder.GetInstance(solutionChecksums.Projects.Length, out var projects); foreach (var (projectChecksum, projectId) in solutionChecksums.Projects) projects.Add(await CreateProjectInfoAsync(projectId, projectChecksum, cancellationToken).ConfigureAwait(false)); - var analyzerReferences = await CreateCollectionAsync(AssetHint.None, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await GetAssetsAsync(AssetPath.SolutionOnly, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); return SolutionInfo.Create( solutionAttributes.Id, solutionAttributes.Version, solutionAttributes.FilePath, projects.ToImmutableAndClear(), analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId); @@ -42,19 +44,19 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu public async Task CreateProjectInfoAsync(ProjectId projectId, Checksum projectChecksum, CancellationToken cancellationToken) { - var projectChecksums = await GetAssetAsync(assetHint: projectId, projectChecksum, cancellationToken).ConfigureAwait(false); + var projectChecksums = await GetAssetAsync(assetPath: projectId, projectChecksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(projectId == projectChecksums.ProjectId); - var attributes = await GetAssetAsync(assetHint: projectId, projectChecksums.Info, cancellationToken).ConfigureAwait(false); + var attributes = await GetAssetAsync(assetPath: projectId, projectChecksums.Info, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(RemoteSupportedLanguages.IsSupported(attributes.Language)); var compilationOptions = attributes.FixUpCompilationOptions( - await GetAssetAsync(assetHint: projectId, projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); - var parseOptions = await GetAssetAsync(assetHint: projectId, projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(assetPath: projectId, projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); + var parseOptions = await GetAssetAsync(assetPath: projectId, projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); - var projectReferences = await CreateCollectionAsync(assetHint: projectId, projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); - var metadataReferences = await CreateCollectionAsync(assetHint: projectId, projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); - var analyzerReferences = await CreateCollectionAsync(assetHint: projectId, projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var projectReferences = await GetAssetsAsync(assetPath: projectId, projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); + var metadataReferences = await GetAssetsAsync(assetPath: projectId, projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await GetAssetsAsync(assetPath: projectId, projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); var documentInfos = await CreateDocumentInfosAsync(projectChecksums.Documents).ConfigureAwait(false); var additionalDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments).ConfigureAwait(false); @@ -89,11 +91,11 @@ async Task> CreateDocumentInfosAsync(ChecksumsAndId public async Task CreateDocumentInfoAsync( DocumentId documentId, Checksum documentChecksum, CancellationToken cancellationToken) { - var documentSnapshot = await GetAssetAsync(assetHint: documentId, documentChecksum, cancellationToken).ConfigureAwait(false); + var documentSnapshot = await GetAssetAsync(assetPath: documentId, documentChecksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(documentId != documentSnapshot.DocumentId); - var attributes = await GetAssetAsync(assetHint: documentId, documentSnapshot.Info, cancellationToken).ConfigureAwait(false); - var serializableSourceText = await GetAssetAsync(assetHint: documentId, documentSnapshot.Text, cancellationToken).ConfigureAwait(false); + var attributes = await GetAssetAsync(assetPath: documentId, documentSnapshot.Info, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await GetAssetAsync(assetPath: documentId, documentSnapshot.Text, cancellationToken).ConfigureAwait(false); var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); var textLoader = TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), attributes.FilePath)); @@ -102,17 +104,13 @@ public async Task CreateDocumentInfoAsync( return new DocumentInfo(attributes, textLoader, documentServiceProvider: null); } - public async Task> CreateCollectionAsync( - AssetHint assetHint, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class + public async Task> GetAssetsAsync( + AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class { - using var _ = ArrayBuilder.GetInstance(checksums.Count, out var assets); + using var _ = PooledHashSet.GetInstance(out var checksumSet); + checksumSet.AddAll(checksums.Children); - foreach (var checksum in checksums) - { - cancellationToken.ThrowIfCancellationRequested(); - assets.Add(await GetAssetAsync(assetHint, checksum, cancellationToken).ConfigureAwait(false)); - } - - return assets.ToImmutableAndClear(); + var results = await this.GetAssetsAsync(assetPath, checksumSet, cancellationToken).ConfigureAwait(false); + return results.SelectAsArray(static t => t.asset); } } diff --git a/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs b/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs index b57bf95fcb2b2..3c37c867d4b49 100644 --- a/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs @@ -22,9 +22,9 @@ internal interface ISolutionAssetProvider /// The writer to write the assets into. Implementations of this method must call on it (in the event of failure or success). Failing to do so will lead to hangs on /// the code that reads from the corresponding side of this. - /// Optional project and document ids to scope the search for checksums down to. This can + /// Optional project and document ids to scope the search for checksums down to. This can /// save substantially on performance by avoiding having to search the full solution tree to find matching items for /// a particular checksum. ValueTask WriteAssetsAsync( - PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken); + PipeWriter pipeWriter, Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 52a3f75670cc7..d4df3ec47e989 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -28,7 +28,7 @@ internal sealed class SolutionAssetProvider(SolutionServices services) : ISoluti public ValueTask WriteAssetsAsync( PipeWriter pipeWriter, Checksum solutionChecksum, - AssetHint assetHint, + AssetPath assetPath, ReadOnlyMemory checksums, CancellationToken cancellationToken) { @@ -39,9 +39,9 @@ public ValueTask WriteAssetsAsync( // ⚠ DO NOT AWAIT INSIDE THE USING. The Dispose method that restores ExecutionContext flow must run on the // same thread where SuppressFlow was originally run. using var _ = FlowControlHelper.TrySuppressFlow(); - return WriteAssetsSuppressedFlowAsync(pipeWriter, solutionChecksum, assetHint, checksums, cancellationToken); + return WriteAssetsSuppressedFlowAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken); - async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken) + async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, CancellationToken cancellationToken) { // The responsibility is on us (as per the requirements of RemoteCallback.InvokeAsync) to Complete the // pipewriter. This will signal to streamjsonrpc that the writer passed into it is complete, which will @@ -49,7 +49,7 @@ async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum s Exception? exception = null; try { - await WriteAssetsWorkerAsync(pipeWriter, solutionChecksum, assetHint, checksums, cancellationToken).ConfigureAwait(false); + await WriteAssetsWorkerAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) { @@ -65,7 +65,7 @@ async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum s private async ValueTask WriteAssetsWorkerAsync( PipeWriter pipeWriter, Checksum solutionChecksum, - AssetHint assetHint, + AssetPath assetPath, ReadOnlyMemory checksums, CancellationToken cancellationToken) { @@ -75,7 +75,7 @@ private async ValueTask WriteAssetsWorkerAsync( using var _ = Creator.CreateResultMap(out var resultMap); - await scope.AddAssetsAsync(assetHint, checksums, resultMap, cancellationToken).ConfigureAwait(false); + await scope.AddAssetsAsync(assetPath, checksums, resultMap, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index ce07e27e23cb1..e4b6ae9f169a8 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -45,7 +45,7 @@ public void Dispose() /// the storage. /// public async Task AddAssetsAsync( - AssetHint assetHint, + AssetPath assetPath, ReadOnlyMemory checksums, Dictionary assetMap, CancellationToken cancellationToken) @@ -58,14 +58,14 @@ public async Task AddAssetsAsync( var numberOfChecksumsToSearch = checksumsToFind.Count; Contract.ThrowIfTrue(checksumsToFind.Contains(Checksum.Null)); - await FindAssetsAsync(assetHint, checksumsToFind, assetMap, cancellationToken).ConfigureAwait(false); + await FindAssetsAsync(assetPath, checksumsToFind, assetMap, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(checksumsToFind.Count > 0); Contract.ThrowIfTrue(assetMap.Count != numberOfChecksumsToSearch); } private async Task FindAssetsAsync( - AssetHint assetHint, HashSet remainingChecksumsToFind, Dictionary result, CancellationToken cancellationToken) + AssetPath assetPath, HashSet remainingChecksumsToFind, Dictionary result, CancellationToken cancellationToken) { var solutionState = this.CompilationState; @@ -74,13 +74,13 @@ private async Task FindAssetsAsync( // If we're not in a project cone, start the search at the top most state-checksum corresponding to the // entire solution. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); } else { // Otherwise, grab the top-most state checksum for this cone and search within that. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(this.ProjectCone.RootProjectId, out var stateChecksums)); - await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); } } @@ -100,7 +100,7 @@ public async ValueTask GetAssetAsync(Checksum checksum, CancellationToke using var checksumPool = Creator.CreateChecksumSet(checksum); using var _ = Creator.CreateResultMap(out var resultPool); - await scope.FindAssetsAsync(AssetHint.None, checksumPool.Object, resultPool, cancellationToken).ConfigureAwait(false); + await scope.FindAssetsAsync(AssetPath.FullLookupForTesting, checksumPool.Object, resultPool, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(resultPool.Count != 1); var (resultingChecksum, value) = resultPool.First(); diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs index 841b07901f7a8..8e87958c46eb3 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -130,25 +131,5 @@ public async ValueTask GetRequiredAssetAsync(Checksum checksum, Cancella { return await _solutionAssetStorage._checksumToScope.Single().Value.GetTestAccessor().GetAssetAsync(checksum, cancellationToken).ConfigureAwait(false); } - - public bool IsPinned(Checksum checksum) - { - lock (_solutionAssetStorage._gate) - { - return _solutionAssetStorage._checksumToScope.TryGetValue(checksum, out var scope) && - scope.RefCount >= 1; - } - } - - public int PinnedScopesCount - { - get - { - lock (_solutionAssetStorage._gate) - { - return _solutionAssetStorage._checksumToScope.Count; - } - } - } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index fd72dfbe20d76..6fe15044796ee 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -31,7 +31,7 @@ internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionA private readonly IAssetSource _assetSource = assetSource; public override async ValueTask GetAssetAsync( - AssetHint assetHint, Checksum checksum, CancellationToken cancellationToken) + AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken) { Contract.ThrowIfTrue(checksum == Checksum.Null); if (_assetCache.TryGetAsset(checksum, out var asset)) @@ -41,19 +41,17 @@ public override async ValueTask GetAssetAsync( checksums.Add(checksum); using var _2 = PooledDictionary.GetInstance(out var results); - await this.SynchronizeAssetsAsync(assetHint, checksums, results, cancellationToken).ConfigureAwait(false); + await this.SynchronizeAssetsAsync(assetPath, checksums, results, cancellationToken).ConfigureAwait(false); return (T)results[checksum]; } - public async ValueTask> GetAssetsAsync( - AssetHint assetHint, HashSet checksums, CancellationToken cancellationToken) + public override async ValueTask> GetAssetsAsync( + AssetPath assetPath, HashSet checksums, CancellationToken cancellationToken) { using var _ = PooledDictionary.GetInstance(out var results); - // bulk synchronize checksums first - var syncer = new ChecksumSynchronizer(this); - await syncer.SynchronizeAssetsAsync(assetHint, checksums, results, cancellationToken).ConfigureAwait(false); + await this.SynchronizeAssetsAsync(assetPath, checksums, results, cancellationToken).ConfigureAwait(false); var result = new (Checksum checksum, T asset)[checksums.Count]; var index = 0; @@ -113,7 +111,7 @@ public async ValueTask SynchronizeProjectAssetsAsync(ProjectStateChecksums proje } public async ValueTask SynchronizeAssetsAsync( - AssetHint assetHint, HashSet checksums, Dictionary? results, CancellationToken cancellationToken) + AssetPath assetPath, HashSet checksums, Dictionary? results, CancellationToken cancellationToken) { Contract.ThrowIfTrue(checksums.Contains(Checksum.Null)); if (checksums.Count == 0) @@ -165,7 +163,7 @@ public async ValueTask SynchronizeAssetsAsync( if (missingChecksumsCount > 0) { var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); - var missingAssets = await RequestAssetsAsync(assetHint, missingChecksumsMemory, cancellationToken).ConfigureAwait(false); + var missingAssets = await RequestAssetsAsync(assetPath, missingChecksumsMemory, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(missingChecksumsMemory.Length != missingAssets.Length); @@ -193,7 +191,7 @@ void AddResult(Checksum checksum, object result) } private async ValueTask> RequestAssetsAsync( - AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken) + AssetPath assetPath, ReadOnlyMemory checksums, CancellationToken cancellationToken) { #if NETCOREAPP Contract.ThrowIfTrue(checksums.Span.Contains(Checksum.Null)); @@ -204,6 +202,6 @@ private async ValueTask> RequestAssetsAsync( if (checksums.Length == 0) return []; - return await _assetSource.GetAssetsAsync(_solutionChecksum, assetHint, checksums, _serializerService, cancellationToken).ConfigureAwait(false); + return await _assetSource.GetAssetsAsync(_solutionChecksum, assetPath, checksums, _serializerService, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs b/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs index b888a6044ce31..e6122d3009322 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs @@ -21,47 +21,49 @@ private readonly struct ChecksumSynchronizer(AssetProvider assetProvider) private readonly AssetProvider _assetProvider = assetProvider; public async ValueTask SynchronizeAssetsAsync( - AssetHint assetHint, + AssetPath assetPath, HashSet checksums, Dictionary? results, CancellationToken cancellationToken) { using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - await _assetProvider.SynchronizeAssetsAsync(assetHint, checksums, results, cancellationToken).ConfigureAwait(false); + await _assetProvider.SynchronizeAssetsAsync(assetPath, checksums, results, cancellationToken).ConfigureAwait(false); } } public async ValueTask SynchronizeSolutionAssetsAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { - SolutionStateChecksums solutionChecksumObject; + using var _1 = PooledDictionary.GetInstance(out var checksumToObjects); + + SolutionStateChecksums stateChecksums; using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - // this will make 4 round trip to data source (VS) to get all assets that belong to the given solution checksum + // first, get top level solution state for the given solution checksum + var compilationStateChecksums = await _assetProvider.GetAssetAsync( + assetPath: AssetPath.SolutionOnly, solutionChecksum, cancellationToken).ConfigureAwait(false); - // first, get solution checksum object for the given solution checksum - var solutionCompilationChecksumObject = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, solutionChecksum, cancellationToken).ConfigureAwait(false); - solutionChecksumObject = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, solutionCompilationChecksumObject.SolutionState, cancellationToken).ConfigureAwait(false); + using var _2 = PooledHashSet.GetInstance(out var checksums); - // second, get direct children of the solution - { - using var _ = PooledHashSet.GetInstance(out var checksums); + // second, get direct children of the solution compilation state. + compilationStateChecksums.AddAllTo(checksums); + await _assetProvider.SynchronizeAssetsAsync(assetPath: AssetPath.SolutionOnly, checksums, results: null, cancellationToken).ConfigureAwait(false); - checksums.Add(solutionCompilationChecksumObject.SourceGeneratorExecutionVersionMap); - solutionChecksumObject.AddAllTo(checksums); - await _assetProvider.SynchronizeAssetsAsync(assetHint: AssetHint.None, checksums, results: null, cancellationToken).ConfigureAwait(false); - } + // third, get direct children of the solution state. + stateChecksums = await _assetProvider.GetAssetAsync( + assetPath: AssetPath.SolutionOnly, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + + // Ask for solutions and top-level projects as the solution checksums will contain the checksums for + // the project states and we want to get that all in one batch. + checksums.Clear(); + stateChecksums.AddAllTo(checksums); + await _assetProvider.SynchronizeAssetsAsync(assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, checksums, checksumToObjects, cancellationToken).ConfigureAwait(false); } - // third and last get direct children for all projects and documents in the solution - foreach (var (projectChecksum, _) in solutionChecksumObject.Projects) + // fourth, get all projects and documents in the solution + foreach (var (projectChecksum, _) in stateChecksums.Projects) { - // These GetAssetAsync calls should be fast since they were just retrieved above. There's a small - // chance the asset-cache GC pass may have cleaned them up, but that should be exceedingly rare. - var projectStateChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, projectChecksum, cancellationToken).ConfigureAwait(false); + var projectStateChecksums = (ProjectStateChecksums)checksumToObjects[projectChecksum]; await SynchronizeProjectAssetsAsync(projectStateChecksums, cancellationToken).ConfigureAwait(false); } } @@ -91,28 +93,28 @@ private async ValueTask SynchronizeProjectAssets_NoLockAsync(ProjectStateChecksu // First synchronize all the top-level info about this project. await _assetProvider.SynchronizeAssetsAsync( - assetHint: projectChecksum.ProjectId, checksums, results: null, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.ProjectAndDocuments(projectChecksum.ProjectId), checksums, results: null, cancellationToken).ConfigureAwait(false); checksums.Clear(); // Then synchronize the info about all the documents within. - await CollectChecksumChildrenAsync(this, projectChecksum.Documents.Checksums).ConfigureAwait(false); - await CollectChecksumChildrenAsync(this, projectChecksum.AdditionalDocuments.Checksums).ConfigureAwait(false); - await CollectChecksumChildrenAsync(this, projectChecksum.AnalyzerConfigDocuments.Checksums).ConfigureAwait(false); + await CollectChecksumChildrenAsync(this, projectChecksum.Documents).ConfigureAwait(false); + await CollectChecksumChildrenAsync(this, projectChecksum.AdditionalDocuments).ConfigureAwait(false); + await CollectChecksumChildrenAsync(this, projectChecksum.AnalyzerConfigDocuments).ConfigureAwait(false); await _assetProvider.SynchronizeAssetsAsync( - assetHint: projectChecksum.ProjectId, checksums, results: null, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.ProjectAndDocuments(projectChecksum.ProjectId), checksums, results: null, cancellationToken).ConfigureAwait(false); - async ValueTask CollectChecksumChildrenAsync(ChecksumSynchronizer @this, ChecksumCollection collection) + async ValueTask CollectChecksumChildrenAsync(ChecksumSynchronizer @this, ChecksumsAndIds collection) { - foreach (var checksum in collection) + // This GetAssetsAsync call should be fast since they were just retrieved above. There's a small chance + // the asset-cache GC pass may have cleaned them up, but that should be exceedingly rare. + var allDocChecksums = await @this._assetProvider.GetAssetsAsync( + AssetPath.ProjectAndDocuments(projectChecksum.ProjectId), collection.Checksums, cancellationToken).ConfigureAwait(false); + foreach (var docChecksums in allDocChecksums) { - // These GetAssetAsync calls should be fast since they were just retrieved above. There's a small - // chance the asset-cache GC pass may have cleaned them up, but that should be exceedingly rare. - var checksumObject = await @this._assetProvider.GetAssetAsync( - assetHint: projectChecksum.ProjectId, checksum, cancellationToken).ConfigureAwait(false); - checksums.Add(checksumObject.Info); - checksums.Add(checksumObject.Text); + checksums.Add(docChecksums.Info); + checksums.Add(docChecksums.Text); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs index 840e043daa46a..d7fcab7984e13 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs @@ -16,5 +16,5 @@ namespace Microsoft.CodeAnalysis.Remote; internal interface IAssetSource { ValueTask> GetAssetsAsync( - Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, ISerializerService serializerService, CancellationToken cancellationToken); + Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService serializerService, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs index 75660559eabdc..d5b3c3be1ef77 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -158,6 +159,12 @@ public void ReportTelemetry() })); } + public void AddAllTo(HashSet solutions) + { + foreach (var node in _cacheNodes) + solutions.AddIfNotNull(node.Solution); + } + private sealed class CacheNode(TChecksum checksum) { public readonly TChecksum Checksum = checksum; diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index dcbe3accfd542..e8fadaeeeab01 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -3,14 +3,11 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Frozen; 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; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; @@ -39,12 +36,12 @@ private readonly struct SolutionCreator(HostServices hostServices, AssetProvider public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksum, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionChecksum, cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); // if either solution id or file path changed, then we consider it as new solution return _baseSolution.Id == newSolutionInfo.Id && _baseSolution.FilePath == newSolutionInfo.FilePath; @@ -61,9 +58,9 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca solution = solution.WithoutFrozenSourceGeneratedDocuments(); var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksum, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionChecksum, cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); var oldSolutionCompilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); var oldSolutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); @@ -71,7 +68,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca if (oldSolutionChecksums.Attributes != newSolutionChecksums.Attributes) { var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); // if either id or file path has changed, then this is not update Contract.ThrowIfFalse(solution.Id == newSolutionInfo.Id && solution.FilePath == newSolutionInfo.FilePath); @@ -85,8 +82,8 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca if (oldSolutionChecksums.AnalyzerReferences.Checksum != newSolutionChecksums.AnalyzerReferences.Checksum) { - solution = solution.WithAnalyzerReferences(await _assetProvider.CreateCollectionAsync( - assetHint: AssetHint.None, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); + solution = solution.WithAnalyzerReferences(await _assetProvider.GetAssetsAsync( + assetPath: AssetPath.SolutionOnly, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } if (newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.HasValue && @@ -99,12 +96,12 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca for (var i = 0; i < count; i++) { var identity = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); var documentStateChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Checksums[i], cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Checksums[i], cancellationToken).ConfigureAwait(false); - var serializableSourceText = await _assetProvider.GetAssetAsync(assetHint: newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i], documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync(assetPath: newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i], documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); var generationDateTime = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes[i]; var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); @@ -118,7 +115,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap) { var newVersions = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); // The execution version map will be for the entire solution on the host side. However, we may // only be syncing over a partial cone. In that case, filter down the version map we apply to @@ -218,13 +215,11 @@ private async Task UpdateProjectsAsync( oldProjectIdToStateChecksums.Add(projectId, oldProjectStateChecksums); } - // sync over the *info* about all the added/changed projects. We'll want the info so we can determine - // what actually changed. using var _5 = PooledHashSet.GetInstance(out var newChecksumsToSync); newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); var newProjectStateChecksums = await _assetProvider.GetAssetsAsync( - assetHint: AssetHint.None, newChecksumsToSync, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, newChecksumsToSync, cancellationToken).ConfigureAwait(false); foreach (var (checksum, newProjectStateChecksum) in newProjectStateChecksums) { @@ -315,35 +310,35 @@ private async Task UpdateProjectAsync(Project project, ProjectStateChe project = project.WithCompilationOptions( project.State.ProjectInfo.Attributes.FixUpCompilationOptions( await _assetProvider.GetAssetAsync( - assetHint: project.Id, newProjectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false))); + assetPath: project.Id, newProjectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false))); } // changed parse options if (oldProjectChecksums.ParseOptions != newProjectChecksums.ParseOptions) { project = project.WithParseOptions(await _assetProvider.GetAssetAsync( - assetHint: project.Id, newProjectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false)); + assetPath: project.Id, newProjectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false)); } // changed project references if (oldProjectChecksums.ProjectReferences.Checksum != newProjectChecksums.ProjectReferences.Checksum) { - project = project.WithProjectReferences(await _assetProvider.CreateCollectionAsync( - assetHint: project.Id, newProjectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false)); + project = project.WithProjectReferences(await _assetProvider.GetAssetsAsync( + assetPath: project.Id, newProjectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false)); } // changed metadata references if (oldProjectChecksums.MetadataReferences.Checksum != newProjectChecksums.MetadataReferences.Checksum) { - project = project.WithMetadataReferences(await _assetProvider.CreateCollectionAsync( - assetHint: project.Id, newProjectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false)); + project = project.WithMetadataReferences(await _assetProvider.GetAssetsAsync( + assetPath: project.Id, newProjectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false)); } // changed analyzer references if (oldProjectChecksums.AnalyzerReferences.Checksum != newProjectChecksums.AnalyzerReferences.Checksum) { - project = project.WithAnalyzerReferences(await _assetProvider.CreateCollectionAsync( - assetHint: project.Id, newProjectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); + project = project.WithAnalyzerReferences(await _assetProvider.GetAssetsAsync( + assetPath: project.Id, newProjectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } // changed analyzer references @@ -394,7 +389,7 @@ await _assetProvider.GetAssetAsync( private async Task UpdateProjectInfoAsync(Project project, Checksum infoChecksum, CancellationToken cancellationToken) { var newProjectAttributes = await _assetProvider.GetAssetAsync( - assetHint: project.Id, infoChecksum, cancellationToken).ConfigureAwait(false); + assetPath: project.Id, infoChecksum, cancellationToken).ConfigureAwait(false); // there is no API to change these once project is created Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Id == newProjectAttributes.Id); @@ -509,7 +504,7 @@ private async Task UpdateDocumentsAsync( newChecksumsToSync.AddRange(newDocumentIdToChecksum.Values); var documentStateChecksums = await _assetProvider.GetAssetsAsync( - assetHint: project.Id, newChecksumsToSync, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.ProjectAndDocuments(project.Id), newChecksumsToSync, cancellationToken).ConfigureAwait(false); foreach (var (checksum, documentStateChecksum) in documentStateChecksums) { @@ -604,7 +599,7 @@ private async Task UpdateDocumentAsync(TextDocument document, DocumentS if (oldDocumentChecksums.Text != newDocumentChecksums.Text) { var serializableSourceText = await _assetProvider.GetAssetAsync( - assetHint: document.Id, newDocumentChecksums.Text, cancellationToken).ConfigureAwait(false); + assetPath: document.Id, newDocumentChecksums.Text, cancellationToken).ConfigureAwait(false); var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); document = document.Kind switch @@ -622,7 +617,7 @@ private async Task UpdateDocumentAsync(TextDocument document, DocumentS private async Task UpdateDocumentInfoAsync(TextDocument document, Checksum infoChecksum, CancellationToken cancellationToken) { var newDocumentInfo = await _assetProvider.GetAssetAsync( - assetHint: document.Id, infoChecksum, cancellationToken).ConfigureAwait(false); + assetPath: document.Id, infoChecksum, cancellationToken).ConfigureAwait(false); // there is no api to change these once document is created Contract.ThrowIfFalse(document.State.Attributes.Id == newDocumentInfo.Id); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs index 61916c58da957..b6a17c45389b9 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs @@ -37,11 +37,11 @@ internal class RemoteWorkspaceManager /// allowing it to get too full. /// /// Also note that the asset cache will not remove items associated with the of the workspace it is created against. This ensures that the assets - /// associated with the solution that most closely corresponds to what the user is working with will stay pinned - /// on the remote side and not get purged just because the user stopped interactive for a while. This ensures - /// the next sync (which likely overlaps heavily with the current solution) will not force the same assets to be - /// resent. + /// cref="Workspace.CurrentSolution"/> of the workspace it is created against (as well as any recent in-flight + /// solutions). This ensures that the assets associated with the solution that most closely corresponds to what + /// the user is working with will stay pinned on the remote side and not get purged just because the user + /// stopped interactive for a while. This ensures the next sync (which likely overlaps heavily with the current + /// solution) will not force the same assets to be resent. /// /// /// CleanupInterval=30s gives what feels to be a reasonable non-aggressive amount of time to let the cache diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs index a5890aba70d40..119d58327f58f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs @@ -4,6 +4,9 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote @@ -111,5 +114,28 @@ private void CheckCacheInvariants_NoLock() Contract.ThrowIfTrue(solutionChecksum != solution.SolutionChecksum); } } + + /// + /// Gets all the solution instances this remote workspace knows about because of the primary solution or any + /// in-flight operations. + /// + public async ValueTask AddPinnedSolutionsAsync(HashSet solutions, CancellationToken cancellationToken) + { + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + // Ensure everything in the workspace's current solution is pinned. We def don't want any of its data + // dropped from the checksum->asset cache. + solutions.Add(this.CurrentSolution); + + // Also the data for the last 'current solution' this workspace had that we actually got an OOP request + // for. this is commonly the same as CurrentSolution, but technically could be slightly behind if the + // primary solution just got updated. + solutions.AddIfNotNull(_lastRequestedPrimaryBranchSolution.solution); + + // Also add the last few forked solutions we were asked about. As with the above solutions, there's a + // reasonable chance it will refer to data needed by future oop calls. + _lastRequestedAnyBranchSolutions.AddAllTo(solutions); + } + } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs index c167b7162380b..9d7ddc0d3a3b7 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs @@ -11,230 +11,259 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +internal sealed class SolutionAssetCache { - internal sealed class SolutionAssetCache + static SolutionAssetCache() { - static SolutionAssetCache() - { - // CRITICAL: The size SharedStopwatch is the size of a TimeSpan (which itself is the size of a long). This - // allows stopwatches to be atomically overwritten, without a concern for torn writes, as long as we're - // running on 64bit machines. Make sure this value doesn't change as that will cause these current - // consumers to be invalid. - RoslynDebug.Assert(Marshal.SizeOf(typeof(SharedStopwatch)) == 8); - } + // CRITICAL: The size SharedStopwatch is the size of a TimeSpan (which itself is the size of a long). This + // allows stopwatches to be atomically overwritten, without a concern for torn writes, as long as we're + // running on 64bit machines. Make sure this value doesn't change as that will cause these current + // consumers to be invalid. + RoslynDebug.Assert(Marshal.SizeOf(typeof(SharedStopwatch)) == 8); + } + + /// + /// Workspace we are associated with. When we purge items from teh cache, we will avoid any items associated + /// with the items in its 'CurrentSolution'. + /// + private readonly RemoteWorkspace? _remoteWorkspace; + + /// + /// Time interval we check storage for cleanup + /// + private readonly TimeSpan _cleanupIntervalTimeSpan; + + /// + /// Time span data can sit inside of cache () without being used. + /// after that, it will be removed from the cache. + /// + private readonly TimeSpan _purgeAfterTimeSpan; + + /// + /// Time we will wait after the last activity before doing explicit GC cleanup. + /// We monitor all resource access and service call to track last activity time. + /// + /// We do this since 64bit process can hold onto quite big unused memory when + /// OOP is running as AnyCpu + /// + private readonly TimeSpan _gcAfterTimeSpan; + + private readonly ConcurrentDictionary _assets = new(concurrencyLevel: 4, capacity: 10); + + private DateTime _lastGCRun; + private DateTime _lastActivityTime; + + // constructor for testing + public SolutionAssetCache() + { + } + + /// + /// Create central data cache + /// + /// time interval to clean up + /// time unused data can sit in the cache + /// time we wait before it call GC since last activity + public SolutionAssetCache(RemoteWorkspace? remoteWorkspace, TimeSpan cleanupInterval, TimeSpan purgeAfter, TimeSpan gcAfter) + { + _remoteWorkspace = remoteWorkspace; + _cleanupIntervalTimeSpan = cleanupInterval; + _purgeAfterTimeSpan = purgeAfter; + _gcAfterTimeSpan = gcAfter; + + _lastActivityTime = DateTime.UtcNow; + _lastGCRun = DateTime.UtcNow; + + Task.Run(CleanAssetsAsync, CancellationToken.None); + } + + public object GetOrAdd(Checksum checksum, object value) + { + UpdateLastActivityTime(); + + var entry = _assets.GetOrAdd(checksum, new Entry(value)); + Update(entry); + return entry.Object; + } + + public bool TryGetAsset(Checksum checksum, [MaybeNullWhen(false)] out T value) + { + UpdateLastActivityTime(); - /// - /// Workspace we are associated with. When we purge items from teh cache, we will avoid any items associated - /// with the items in its 'CurrentSolution'. - /// - private readonly RemoteWorkspace? _remoteWorkspace; - - /// - /// Time interval we check storage for cleanup - /// - private readonly TimeSpan _cleanupIntervalTimeSpan; - - /// - /// Time span data can sit inside of cache () without being used. - /// after that, it will be removed from the cache. - /// - private readonly TimeSpan _purgeAfterTimeSpan; - - /// - /// Time we will wait after the last activity before doing explicit GC cleanup. - /// We monitor all resource access and service call to track last activity time. - /// - /// We do this since 64bit process can hold onto quite big unused memory when - /// OOP is running as AnyCpu - /// - private readonly TimeSpan _gcAfterTimeSpan; - - private readonly ConcurrentDictionary _assets = new(concurrencyLevel: 4, capacity: 10); - - private DateTime _lastGCRun; - private DateTime _lastActivityTime; - - // constructor for testing - public SolutionAssetCache() + using (Logger.LogBlock(FunctionId.AssetStorage_TryGetAsset, Checksum.GetChecksumLogInfo, checksum, CancellationToken.None)) { + if (!_assets.TryGetValue(checksum, out var entry)) + { + value = default; + return false; + } + + // Update timestamp + Update(entry); + + value = (T)entry.Object; + return true; } + } + + public bool ContainsAsset(Checksum checksum) + => _assets.ContainsKey(checksum); + + public void UpdateLastActivityTime() + => _lastActivityTime = DateTime.UtcNow; - /// - /// Create central data cache - /// - /// time interval to clean up - /// time unused data can sit in the cache - /// time we wait before it call GC since last activity - public SolutionAssetCache(RemoteWorkspace? remoteWorkspace, TimeSpan cleanupInterval, TimeSpan purgeAfter, TimeSpan gcAfter) + private static void Update(Entry entry) + { + // Stopwatch wraps a TimeSpan (which is only 64bits) (asserted in our shared constructor). so this + // assignment can be done safely without a concern for torn writes on 64 systems. + // + // Note: on 32 bit systems there could be an issue here both with a torn write/read or torn write/write. We + // think that's probably ok as a torn read only leads to suboptimal behavior (dropping something early, or + // keeping something around till the next purge), and a torn write should likely still lead to reasonable + // data being written as both writers will likely still write something reasonable once both writes go + // through. e.g. if you have a writer writing 1234-5678 and one writing 1235-0000, then getting 1235-5678 + // or 1234-0000 is still fine as a final outcome. + entry.Stopwatch = SharedStopwatch.StartNew(); + } + + private async Task CleanAssetsAsync() + { + // Todo: associate this with a real CancellationToken that can shutdown this work. + var cancellationToken = CancellationToken.None; + while (!cancellationToken.IsCancellationRequested) { - _remoteWorkspace = remoteWorkspace; - _cleanupIntervalTimeSpan = cleanupInterval; - _purgeAfterTimeSpan = purgeAfter; - _gcAfterTimeSpan = gcAfter; + await CleanAssetsWorkerAsync(cancellationToken).ConfigureAwait(false); - _lastActivityTime = DateTime.UtcNow; - _lastGCRun = DateTime.UtcNow; + ForceGC(); - Task.Run(CleanAssetsAsync, CancellationToken.None); + await Task.Delay(_cleanupIntervalTimeSpan, cancellationToken).ConfigureAwait(false); } + } - public object GetOrAdd(Checksum checksum, object value) + private void ForceGC() + { + // if there was no activity since last GC run. we don't have anything to do + if (_lastGCRun >= _lastActivityTime) { - UpdateLastActivityTime(); - - var entry = _assets.GetOrAdd(checksum, new Entry(value)); - Update(entry); - return entry.Object; + return; } - public bool TryGetAsset(Checksum checksum, [MaybeNullWhen(false)] out T value) + var current = DateTime.UtcNow; + if (current - _lastActivityTime < _gcAfterTimeSpan) { - UpdateLastActivityTime(); + // we are having activities. + return; + } - using (Logger.LogBlock(FunctionId.AssetStorage_TryGetAsset, Checksum.GetChecksumLogInfo, checksum, CancellationToken.None)) + using (Logger.LogBlock(FunctionId.AssetStorage_ForceGC, CancellationToken.None)) + { + // we didn't have activity for 5 min. spend some time to drop + // unused memory + for (var i = 0; i < 3; i++) { - if (!_assets.TryGetValue(checksum, out var entry)) - { - value = default; - return false; - } - - // Update timestamp - Update(entry); - - value = (T)entry.Object; - return true; + GC.Collect(); } } - public bool ContainsAsset(Checksum checksum) - => _assets.ContainsKey(checksum); - - public void UpdateLastActivityTime() - => _lastActivityTime = DateTime.UtcNow; + // update gc run time + _lastGCRun = current; + } - private static void Update(Entry entry) + private async ValueTask CleanAssetsWorkerAsync(CancellationToken cancellationToken) + { + if (_assets.IsEmpty) { - // Stopwatch wraps a TimeSpan (which is only 64bits) (asserted in our shared constructor). so this - // assignment can be done safely without a concern for torn writes on 64 systems. - // - // Note: on 32 bit systems there could be an issue here both with a torn write/read or torn write/write. We - // think that's probably ok as a torn read only leads to suboptimal behavior (dropping something early, or - // keeping something around till the next purge), and a torn write should likely still lead to reasonable - // data being written as both writers will likely still write something reasonable once both writes go - // through. e.g. if you have a writer writing 1234-5678 and one writing 1235-0000, then getting 1235-5678 - // or 1234-0000 is still fine as a final outcome. - entry.Stopwatch = SharedStopwatch.StartNew(); + // no asset, nothing to do. + return; } - private async Task CleanAssetsAsync() + using (Logger.LogBlock(FunctionId.AssetStorage_CleanAssets, cancellationToken)) { - // Todo: associate this with a real CancellationToken that can shutdown this work. - var cancellationToken = CancellationToken.None; - while (!cancellationToken.IsCancellationRequested) + // Ensure that if our remote workspace has a current solution, that we don't purge any items associated + // with that solution. + using var _1 = PooledHashSet.GetInstance(out var pinnedChecksums); + + foreach (var (checksum, entry) in _assets) { - await CleanAssetsWorkerAsync(cancellationToken).ConfigureAwait(false); + // If not enough time has passed, keep in the cache. + if (entry.Stopwatch.Elapsed <= _purgeAfterTimeSpan) + continue; + + // If this is a checksum we want to pin, do not remove it. + if (pinnedChecksums.Count == 0) + await AddPinnedChecksumsAsync(pinnedChecksums, cancellationToken).ConfigureAwait(false); - ForceGC(); + if (pinnedChecksums.Contains(checksum)) + continue; - await Task.Delay(_cleanupIntervalTimeSpan, cancellationToken).ConfigureAwait(false); + _assets.TryRemove(checksum, out _); } } + } - private void ForceGC() - { - // if there was no activity since last GC run. we don't have anything to do - if (_lastGCRun >= _lastActivityTime) - { - return; - } + private async ValueTask AddPinnedChecksumsAsync(HashSet pinnedChecksums, CancellationToken cancellationToken) + { + if (_remoteWorkspace is null) + return; - var current = DateTime.UtcNow; - if (current - _lastActivityTime < _gcAfterTimeSpan) - { - // we are having activities. - return; - } + using var _1 = PooledHashSet.GetInstance(out var pinnedSolutions); - using (Logger.LogBlock(FunctionId.AssetStorage_ForceGC, CancellationToken.None)) - { - // we didn't have activity for 5 min. spend some time to drop - // unused memory - for (var i = 0; i < 3; i++) - { - GC.Collect(); - } - } + // Collect all the solutions the remote workspace has pinned. + await _remoteWorkspace.AddPinnedSolutionsAsync(pinnedSolutions, cancellationToken).ConfigureAwait(false); - // update gc run time - _lastGCRun = current; - } + // Then add all relevant info from those pinned solutions so the set that we will not let go of. + foreach (var pinnedSolution in pinnedSolutions) + await AddPinnedChecksumsAsync(pinnedSolution).ConfigureAwait(false); - private async ValueTask CleanAssetsWorkerAsync(CancellationToken cancellationToken) + return; + + async ValueTask AddPinnedChecksumsAsync(Solution pinnedSolution) { - if (_assets.IsEmpty) - { - // no asset, nothing to do. - return; - } + // Get the checksums for the local solution. Note that this will ensure that all child checksums are + // computed. As such, we can just use TryGetXXX below to get them. + var compilationState = pinnedSolution.CompilationState; + var compilationStateChecksums = await compilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + compilationStateChecksums.AddAllTo(pinnedChecksums); + + var solutionState = compilationState.SolutionState; + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var stateChecksums)); + stateChecksums.AddAllTo(pinnedChecksums); - using (Logger.LogBlock(FunctionId.AssetStorage_CleanAssets, cancellationToken)) + foreach (var (_, projectState) in solutionState.ProjectStates) { - // Ensure that if our remote workspace has a current solution, that we don't purge any items associated - // with that solution. - PooledHashSet? pinnedChecksums = null; - try - { - foreach (var (checksum, entry) in _assets) - { - // If not enough time has passed, keep in the cache. - if (entry.Stopwatch.Elapsed <= _purgeAfterTimeSpan) - continue; - - // If this is a checksum we want to pin, do not remove it. - if (pinnedChecksums == null) - { - pinnedChecksums = PooledHashSet.GetInstance(); - await AddPinnedChecksumsAsync(pinnedChecksums, cancellationToken).ConfigureAwait(false); - } - - if (pinnedChecksums.Contains(checksum)) - continue; - - _assets.TryRemove(checksum, out _); - } - } - finally - { - pinnedChecksums?.Free(); - } + Contract.ThrowIfFalse(projectState.TryGetStateChecksums(out var projectStateChecksums)); + projectStateChecksums.AddAllTo(pinnedChecksums); + + AddAll(projectState.DocumentStates); + AddAll(projectState.AdditionalDocumentStates); + AddAll(projectState.AnalyzerConfigDocumentStates); } } - private async ValueTask AddPinnedChecksumsAsync(HashSet pinnedChecksums, CancellationToken cancellationToken) + void AddAll(TextDocumentStates states) where TState : TextDocumentState { - if (_remoteWorkspace is null) - return; - - var checksums = await _remoteWorkspace.CurrentSolution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - checksums.AddAllTo(pinnedChecksums); + foreach (var (_, documentState) in states.States) + { + Contract.ThrowIfFalse(documentState.TryGetStateChecksums(out var documentChecksums)); + documentChecksums.AddAllTo(pinnedChecksums); + } } + } - private sealed class Entry - { - public SharedStopwatch Stopwatch = SharedStopwatch.StartNew(); + private sealed class Entry + { + public SharedStopwatch Stopwatch = SharedStopwatch.StartNew(); - // This can't change for same checksum - public readonly object Object; + // This can't change for same checksum + public readonly object Object; - public Entry(object @object) - { - Object = @object; - } + public Entry(object @object) + { + Object = @object; } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index 4917033beae78..d81767fbddda4 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -18,7 +18,7 @@ internal sealed class SolutionAssetSource(ServiceBrokerClient client) : IAssetSo public async ValueTask> GetAssetsAsync( Checksum solutionChecksum, - AssetHint assetHint, + AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService serializerService, CancellationToken cancellationToken) @@ -30,7 +30,7 @@ public async ValueTask> GetAssetsAsync( _client, SolutionAssetProvider.ServiceDescriptor, (callback, cancellationToken) => callback.InvokeAsync( - (proxy, pipeWriter, cancellationToken) => proxy.WriteAssetsAsync(pipeWriter, solutionChecksum, assetHint, checksums, cancellationToken), + (proxy, pipeWriter, cancellationToken) => proxy.WriteAssetsAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken), (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums.Length, serializerService, cancellationToken), cancellationToken), cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index e942c5e44f5fd..d12f4b38a7eeb 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -104,7 +104,7 @@ async Task>> GetAssetFromAssetServiceAsync(I foreach (var checksum in checksums) { items.Add(new KeyValuePair(checksum, await assetService.GetAssetAsync( - assetHint: AssetHint.None, checksum, CancellationToken.None).ConfigureAwait(false))); + AssetPath.FullLookupForTesting, checksum, CancellationToken.None).ConfigureAwait(false))); } return items; @@ -115,9 +115,9 @@ async Task> GetAllChildrenChecksumsAsync(Checksum solutionChec var set = new HashSet(); var solutionCompilationChecksums = await assetService.GetAssetAsync( - assetHint: AssetHint.None, solutionChecksum, CancellationToken.None).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, solutionChecksum, CancellationToken.None).ConfigureAwait(false); var solutionChecksums = await assetService.GetAssetAsync( - assetHint: AssetHint.None, solutionCompilationChecksums.SolutionState, CancellationToken.None).ConfigureAwait(false); + assetPath: AssetPath.SolutionOnly, solutionCompilationChecksums.SolutionState, CancellationToken.None).ConfigureAwait(false); solutionCompilationChecksums.AddAllTo(set); solutionChecksums.AddAllTo(set); @@ -125,7 +125,7 @@ async Task> GetAllChildrenChecksumsAsync(Checksum solutionChec foreach (var (projectChecksum, projectId) in solutionChecksums.Projects) { var projectChecksums = await assetService.GetAssetAsync( - assetHint: projectId, projectChecksum, CancellationToken.None).ConfigureAwait(false); + assetPath: projectId, projectChecksum, CancellationToken.None).ConfigureAwait(false); projectChecksums.AddAllTo(set); await AddDocumentsAsync(projectId, projectChecksums.Documents, set).ConfigureAwait(false); @@ -141,7 +141,7 @@ async Task AddDocumentsAsync(ProjectId projectId, ChecksumsAndIds do foreach (var (documentChecksum, documentId) in documents) { var documentChecksums = await assetService.GetAssetAsync( - assetHint: documentId, documentChecksum, CancellationToken.None).ConfigureAwait(false); + assetPath: documentId, documentChecksum, CancellationToken.None).ConfigureAwait(false); AddAllTo(documentChecksums, checksums); } } @@ -161,8 +161,8 @@ private static void AddAllTo(DocumentStateChecksums documentStateChecksums, Hash } /// - /// create checksum to correspoing object map from solution - /// this map should contain every parts of solution that can be used to re-create the solution back + /// create checksum to corresponding object map from solution this map should contain every parts of solution + /// that can be used to re-create the solution back /// public static async Task> GetAssetMapAsync(this Solution solution, CancellationToken cancellationToken) { @@ -197,17 +197,17 @@ public static async Task AppendAssetMapAsync( if (projectId == null) { var compilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, assetHint: AssetHint.None, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); foreach (var frozenSourceGeneratedDocumentState in solution.CompilationState.FrozenSourceGeneratedDocumentStates?.States.Values ?? []) { var documentChecksums = await frozenSourceGeneratedDocumentState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, assetHint: AssetHint.None, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); } var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(solutionChecksums.ProjectCone != null); - await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone: null, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); foreach (var project in solution.Projects) await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); @@ -215,11 +215,11 @@ public static async Task AppendAssetMapAsync( else { var (compilationChecksums, projectCone) = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone, assetHint: projectId, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(projectCone.Equals(solutionChecksums.ProjectCone)); - await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, assetHint: projectId, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); var project = solution.GetRequiredProject(projectId); await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); @@ -236,7 +236,7 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary tokenText.Length > 0 && char.IsWhiteSpace(tokenText[0]); private static bool HasTrailingWhitespace(string tokenText) - => tokenText.Length > 0 && char.IsWhiteSpace(tokenText[tokenText.Length - 1]); + => tokenText.Length > 0 && char.IsWhiteSpace(tokenText[^1]); public string GetBannerText(SyntaxNode documentationCommentTriviaSyntax, int maxBannerLength, CancellationToken cancellationToken) => GetBannerText((TDocumentationCommentTriviaSyntax)documentationCommentTriviaSyntax, maxBannerLength, cancellationToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.SymbolKeyWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.SymbolKeyWriter.cs index 372759c1b67c3..fa33b1cebae1c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.SymbolKeyWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.SymbolKeyWriter.cs @@ -516,7 +516,7 @@ public void PushMethod(IMethodSymbol method) public void PopMethod(IMethodSymbol method) { Contract.ThrowIfTrue(_methodSymbolStack.Count == 0); - Contract.ThrowIfFalse(method.Equals(_methodSymbolStack[_methodSymbolStack.Count - 1])); + Contract.ThrowIfFalse(method.Equals(_methodSymbolStack[^1])); _methodSymbolStack.RemoveAt(_methodSymbolStack.Count - 1); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EditDistance.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EditDistance.cs index 086cb0e922331..e32225e85e0a0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EditDistance.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EditDistance.cs @@ -178,7 +178,7 @@ private static int GetEditDistanceWorker(ReadOnlySpan source, ReadOnlySpan // First: // Determine the common prefix/suffix portions of the strings. We don't even need to // consider them as they won't add anything to the edit cost. - while (source.Length > 0 && source[source.Length - 1] == target[target.Length - 1]) + while (source.Length > 0 && source[^1] == target[^1]) { source = source[..^1]; target = target[..^1]; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EnumMemberGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EnumMemberGenerator.cs index 59e97d94131cf..4a55bdecb8d6f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EnumMemberGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EnumMemberGenerator.cs @@ -40,7 +40,7 @@ internal static EnumDeclarationSyntax AddEnumMemberTo(EnumDeclarationSyntax dest { var lastMember = members.Last(); var trailingTrivia = lastMember.GetTrailingTrivia(); - members[members.Count - 1] = lastMember.WithTrailingTrivia(); + members[^1] = lastMember.WithTrailingTrivia(); members.Add(Token(SyntaxKind.CommaToken).WithTrailingTrivia(trailingTrivia)); members.Add(member); }