diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index ac99bcc2d9fc3..a648ff1d14494 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -258,9 +258,9 @@ internal void SolutionCompilationStateEqual(SolutionCompilationStateChecksums so if (solutionObject1.FrozenSourceGeneratedDocumentIdentities.HasValue) AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocumentIdentities.Value, solutionObject2.FrozenSourceGeneratedDocumentIdentities!.Value); - Assert.Equal(solutionObject1.FrozenSourceGeneratedDocuments.HasValue, solutionObject2.FrozenSourceGeneratedDocuments.HasValue); - if (solutionObject1.FrozenSourceGeneratedDocuments.HasValue) - AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocuments.Value, solutionObject2.FrozenSourceGeneratedDocuments!.Value); + Assert.Equal(solutionObject1.FrozenSourceGeneratedDocumentTexts.HasValue, solutionObject2.FrozenSourceGeneratedDocumentTexts.HasValue); + if (solutionObject1.FrozenSourceGeneratedDocumentTexts.HasValue) + AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocumentTexts.Value, solutionObject2.FrozenSourceGeneratedDocumentTexts!.Value); } internal void SolutionStateEqual(SolutionStateChecksums solutionObject1, SolutionStateChecksums solutionObject2) diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index c80be5d4721fe..5824e81d1dcda 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Serialization; @@ -137,7 +138,11 @@ public async Task TestProjectSynchronization() var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); var service = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - await service.SynchronizeProjectAssetsAsync(await project.State.GetStateChecksumsAsync(CancellationToken.None), CancellationToken.None); + + using var _ = ArrayBuilder.GetInstance(out var allProjectChecksums); + allProjectChecksums.Add(await project.State.GetStateChecksumsAsync(CancellationToken.None)); + + await service.SynchronizeProjectAssetsAsync(allProjectChecksums, CancellationToken.None); TestUtils.VerifyAssetStorage(map, storage); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs deleted file mode 100644 index 634f641fa9948..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.Serialization; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Serialization; - -/// -/// 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 AssetPath -{ - /// - /// 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)] - private readonly AssetPathKind _kind; - [DataMember(Order = 1)] - public readonly ProjectId? ProjectId; - [DataMember(Order = 2)] - public readonly DocumentId? DocumentId; - - private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? documentId = null) - { - _kind = kind; - ProjectId = projectId; - DocumentId = 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/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs new file mode 100644 index 0000000000000..38de8a081c3ef --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -0,0 +1,158 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.Serialization; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Serialization; + +/// +/// 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 AssetPath +{ + /// + /// 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 = AssetPathKind.SolutionCompilationState | AssetPathKind.SolutionState | AssetPathKind.Projects | AssetPathKind.Documents; + + [DataMember(Order = 0)] + private readonly AssetPathKind _kind; + + /// + /// If not null, the search should only descend into the single project with this id. + /// + [DataMember(Order = 1)] + public readonly ProjectId? ProjectId; + + /// + /// If not null, the search should only descend into the single document with this id. + /// + [DataMember(Order = 2)] + public readonly DocumentId? DocumentId; + + public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? documentId = null) + { + _kind = kind; + ProjectId = projectId; + DocumentId = documentId; + } + + public AssetPath(AssetPathKind kind, ProjectId? projectId) + : this(kind, projectId, documentId: null) + { + } + + public AssetPath(AssetPathKind kind, DocumentId? documentId) + : this(kind, documentId?.ProjectId, documentId) + { + } + + public bool IncludeSolutionCompilationState => (_kind & AssetPathKind.SolutionCompilationState) != 0; + public bool IncludeSolutionState => (_kind & AssetPathKind.SolutionState) != 0; + public bool IncludeProjects => (_kind & AssetPathKind.Projects) != 0; + public bool IncludeDocuments => (_kind & AssetPathKind.Documents) != 0; + + public bool IncludeSolutionCompilationStateChecksums => (_kind & AssetPathKind.SolutionCompilationStateChecksums) != 0; + public bool IncludeSolutionSourceGeneratorExecutionVersionMap => (_kind & AssetPathKind.SolutionSourceGeneratorExecutionVersionMap) != 0; + public bool IncludeSolutionFrozenSourceGeneratedDocumentIdentities => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentIdentities) != 0; + public bool IncludeSolutionFrozenSourceGeneratedDocumentText => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentText) != 0; + + public bool IncludeSolutionStateChecksums => (_kind & AssetPathKind.SolutionStateChecksums) != 0; + public bool IncludeSolutionAttributes => (_kind & AssetPathKind.SolutionAttributes) != 0; + public bool IncludeSolutionAnalyzerReferences => (_kind & AssetPathKind.SolutionAnalyzerReferences) != 0; + + public bool IncludeProjectStateChecksums => (_kind & AssetPathKind.ProjectStateChecksums) != 0; + public bool IncludeProjectAttributes => (_kind & AssetPathKind.ProjectAttributes) != 0; + public bool IncludeProjectCompilationOptions => (_kind & AssetPathKind.ProjectCompilationOptions) != 0; + public bool IncludeProjectParseOptions => (_kind & AssetPathKind.ProjectParseOptions) != 0; + public bool IncludeProjectProjectReferences => (_kind & AssetPathKind.ProjectProjectReferences) != 0; + public bool IncludeProjectMetadataReferences => (_kind & AssetPathKind.ProjectMetadataReferences) != 0; + public bool IncludeProjectAnalyzerReferences => (_kind & AssetPathKind.ProjectAnalyzerReferences) != 0; + + public bool IncludeDocumentStateChecksums => (_kind & AssetPathKind.DocumentStateChecksums) != 0; + public bool IncludeDocumentAttributes => (_kind & AssetPathKind.DocumentAttributes) != 0; + public bool IncludeDocumentText => (_kind & AssetPathKind.DocumentText) != 0; + + public static implicit operator AssetPath(AssetPathKind kind) => new(kind); + + /// + /// Searches only for information about this project. + /// + public static implicit operator AssetPath(ProjectId projectId) => new(AssetPathKind.Projects, projectId); + + /// + /// Searches only for information about this document. + /// + public static implicit operator AssetPath(DocumentId documentId) => new(AssetPathKind.Documents, documentId); + + /// + /// Searches the requested project, and all documents underneath it. Used only in tests. + /// + /// + /// + public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) + => new(AssetPathKind.SolutionCompilationState | AssetPathKind.SolutionState | AssetPathKind.Projects, projectId); + + /// + /// Searches all documents within the specified project. + /// + /// + /// + public static AssetPath DocumentsInProject(ProjectId projectId) + => new(AssetPathKind.Documents, projectId); +} + +[Flags] +internal enum AssetPathKind +{ + SolutionCompilationStateChecksums = 1 << 0, + SolutionSourceGeneratorExecutionVersionMap = 1 << 2, + SolutionFrozenSourceGeneratedDocumentIdentities = 1 << 3, + SolutionFrozenSourceGeneratedDocumentText = 1 << 4, + + // Keep a gap so we can easily add more solution compilation state kinds + SolutionStateChecksums = 1 << 10, + SolutionAttributes = 1 << 11, + SolutionAnalyzerReferences = 1 << 12, + + // Keep a gap so we can easily add more solution kinds + ProjectStateChecksums = 1 << 15, + ProjectAttributes = 1 << 16, + ProjectCompilationOptions = 1 << 17, + ProjectParseOptions = 1 << 18, + ProjectProjectReferences = 1 << 19, + ProjectMetadataReferences = 1 << 20, + ProjectAnalyzerReferences = 1 << 21, + + // Keep a gap so we can easily add more project kinds + DocumentStateChecksums = 1 << 25, + DocumentAttributes = 1 << 26, + DocumentText = 1 << 27, + + /// + /// Search solution-compilation-state level information. + /// + SolutionCompilationState = SolutionCompilationStateChecksums | SolutionSourceGeneratorExecutionVersionMap | SolutionFrozenSourceGeneratedDocumentIdentities | SolutionFrozenSourceGeneratedDocumentText, + + /// + /// Search solution-state level information. + /// + SolutionState = SolutionStateChecksums | SolutionAttributes | SolutionAnalyzerReferences, + + /// + /// Search projects for results. All project-level information will be searched. + /// + Projects = ProjectStateChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, + + /// + /// Search documents for results. + /// + Documents = DocumentStateChecksums | DocumentAttributes | DocumentText, +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index 0a5b669b9ae69..c394b17331d26 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -98,9 +98,6 @@ public static Checksum ReadFrom(ObjectReader reader) public static Func, string> GetChecksumsLogInfo { get; } = checksums => string.Join("|", checksums.Select(c => c.ToString())); - public static Func GetProjectChecksumsLogInfo { get; } - = checksums => checksums.Checksum.ToString(); - // Explicitly implement this method as default jit for records on netfx doesn't properly devirtualize the // standard calls to EqualityComparer.Default.Equals public bool Equals(Checksum other) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 80e04bb9ed8e8..e2a2d6fef2a46 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -55,19 +55,20 @@ public void AddAllTo(HashSet checksums) [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1333566", AllowGenericEnumeration = false)] internal static async Task FindAsync( + AssetPath assetPath, TextDocumentStates documentStates, - DocumentId? hintDocument, HashSet searchingChecksumsLeft, Dictionary result, CancellationToken cancellationToken) where TState : TextDocumentState { + var hintDocument = assetPath.DocumentId; if (hintDocument != null) { var state = documentStates.GetState(hintDocument); if (state != null) { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } } else @@ -80,7 +81,7 @@ internal static async Task FindAsync( Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index 8b0f788488c64..af37ec21138f8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -125,16 +125,17 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo } ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; - ChecksumsAndIds? frozenSourceGeneratedDocuments = null; + ChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (FrozenSourceGeneratedDocumentStates != null) { var serializer = this.SolutionState.Services.GetRequiredService(); - var identityChecksums = FrozenSourceGeneratedDocumentStates - .SelectAsArray(static (s, arg) => arg.serializer.CreateChecksum(s.Identity, cancellationToken: arg.cancellationToken), (serializer, cancellationToken)); + var identityChecksums = FrozenSourceGeneratedDocumentStates.SelectAsArray( + static (s, arg) => arg.serializer.CreateChecksum(s.Identity, cancellationToken: arg.cancellationToken), (serializer, cancellationToken)); + + frozenSourceGeneratedDocumentTexts = await FrozenSourceGeneratedDocumentStates.GetTextChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); frozenSourceGeneratedDocumentIdentities = new ChecksumCollection(identityChecksums); - frozenSourceGeneratedDocuments = await FrozenSourceGeneratedDocumentStates.GetChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); frozenSourceGeneratedDocumentGenerationDateTimes = FrozenSourceGeneratedDocumentStates.SelectAsArray(d => d.GenerationDateTime); } @@ -146,8 +147,8 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo var compilationStateChecksums = new SolutionCompilationStateChecksums( solutionStateChecksum, versionMapChecksum, + frozenSourceGeneratedDocumentTexts, frozenSourceGeneratedDocumentIdentities, - frozenSourceGeneratedDocuments, frozenSourceGeneratedDocumentGenerationDateTimes); return (compilationStateChecksums, projectCone); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 1dcfee3b27bed..0c4478084e0a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Resources; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -21,19 +22,20 @@ internal sealed class SolutionCompilationStateChecksums public SolutionCompilationStateChecksums( Checksum solutionState, Checksum sourceGeneratorExecutionVersionMap, + // These arrays are all the same length if present, and reference the same documents in the same order. + ChecksumsAndIds? frozenSourceGeneratedDocumentTexts, ChecksumCollection? frozenSourceGeneratedDocumentIdentities, - ChecksumsAndIds? frozenSourceGeneratedDocuments, ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes) { // For the frozen source generated document info, we expect two either have both checksum collections or neither, and they // should both be the same length as there is a 1:1 correspondence between them. - Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities.HasValue == frozenSourceGeneratedDocuments.HasValue); - Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocuments?.Length); + Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities.HasValue == frozenSourceGeneratedDocumentTexts.HasValue); + Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocumentTexts?.Length); SolutionState = solutionState; SourceGeneratorExecutionVersionMap = sourceGeneratorExecutionVersionMap; + FrozenSourceGeneratedDocumentTexts = frozenSourceGeneratedDocumentTexts; FrozenSourceGeneratedDocumentIdentities = frozenSourceGeneratedDocumentIdentities; - FrozenSourceGeneratedDocuments = frozenSourceGeneratedDocuments; FrozenSourceGeneratedDocumentGenerationDateTimes = frozenSourceGeneratedDocumentGenerationDateTimes; // note: intentionally not mixing in FrozenSourceGeneratedDocumentGenerationDateTimes as that is not part of the @@ -42,14 +44,18 @@ public SolutionCompilationStateChecksums( SolutionState, SourceGeneratorExecutionVersionMap, FrozenSourceGeneratedDocumentIdentities?.Checksum ?? Checksum.Null, - FrozenSourceGeneratedDocuments?.Checksum ?? Checksum.Null); + frozenSourceGeneratedDocumentTexts?.Checksum ?? Checksum.Null); } public Checksum Checksum { get; } public Checksum SolutionState { get; } public Checksum SourceGeneratorExecutionVersionMap { get; } + + /// + /// Checksums of the SourceTexts of the frozen documents directly. Not checksums of their DocumentStates. + /// + public ChecksumsAndIds? FrozenSourceGeneratedDocumentTexts { get; } public ChecksumCollection? FrozenSourceGeneratedDocumentIdentities { get; } - public ChecksumsAndIds? FrozenSourceGeneratedDocuments { get; } // note: intentionally not part of the identity contract of this type. public ImmutableArray FrozenSourceGeneratedDocumentGenerationDateTimes { get; } @@ -60,7 +66,7 @@ public void AddAllTo(HashSet checksums) checksums.AddIfNotNullChecksum(this.SolutionState); checksums.AddIfNotNullChecksum(this.SourceGeneratorExecutionVersionMap); this.FrozenSourceGeneratedDocumentIdentities?.AddAllTo(checksums); - this.FrozenSourceGeneratedDocuments?.Checksums.AddAllTo(checksums); + this.FrozenSourceGeneratedDocumentTexts?.Checksums.AddAllTo(checksums); } public void Serialize(ObjectWriter writer) @@ -74,8 +80,8 @@ public void Serialize(ObjectWriter writer) writer.WriteBoolean(this.FrozenSourceGeneratedDocumentIdentities.HasValue); if (FrozenSourceGeneratedDocumentIdentities.HasValue) { + this.FrozenSourceGeneratedDocumentTexts!.Value.WriteTo(writer); this.FrozenSourceGeneratedDocumentIdentities.Value.WriteTo(writer); - this.FrozenSourceGeneratedDocuments!.Value.WriteTo(writer); writer.WriteArray(this.FrozenSourceGeneratedDocumentGenerationDateTimes, static (w, d) => w.WriteInt64(d.Ticks)); } } @@ -87,22 +93,22 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) var sourceGeneratorExecutionVersionMap = Checksum.ReadFrom(reader); var hasFrozenSourceGeneratedDocuments = reader.ReadBoolean(); + ChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; - ChecksumsAndIds? frozenSourceGeneratedDocuments = null; ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (hasFrozenSourceGeneratedDocuments) { + frozenSourceGeneratedDocumentTexts = ChecksumsAndIds.ReadFrom(reader); frozenSourceGeneratedDocumentIdentities = ChecksumCollection.ReadFrom(reader); - frozenSourceGeneratedDocuments = ChecksumsAndIds.ReadFrom(reader); frozenSourceGeneratedDocumentGenerationDateTimes = reader.ReadArray(r => new DateTime(r.ReadInt64())); } var result = new SolutionCompilationStateChecksums( solutionState: solutionState, sourceGeneratorExecutionVersionMap: sourceGeneratorExecutionVersionMap, + frozenSourceGeneratedDocumentTexts, frozenSourceGeneratedDocumentIdentities, - frozenSourceGeneratedDocuments, frozenSourceGeneratedDocumentGenerationDateTimes); Contract.ThrowIfFalse(result.Checksum == checksum); return result; @@ -120,34 +126,59 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - if (assetPath.IncludeSolution) + if (assetPath.IncludeSolutionCompilationState) { - if (searchingChecksumsLeft.Remove(this.Checksum)) + if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) result[this.Checksum] = this; - if (searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) + if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) result[this.SourceGeneratorExecutionVersionMap] = compilationState.SourceGeneratorExecutionVersionMap; - if (searchingChecksumsLeft.Count == 0) - return; - if (compilationState.FrozenSourceGeneratedDocumentStates != null) { Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); + Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentTexts.HasValue); // 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); + if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentText) + { + await ChecksumCollection.FindAsync( + new AssetPath(AssetPathKind.DocumentText, assetPath.ProjectId, assetPath.DocumentId), + compilationState.FrozenSourceGeneratedDocumentStates, 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++) + if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentIdentities) { - var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; - if (searchingChecksumsLeft.Remove(identityChecksum)) + var documentId = assetPath.DocumentId; + if (documentId != null) + { + // If the caller is asking for a specific document, we can just look it up directly. + var index = FrozenSourceGeneratedDocumentTexts.Value.Ids.IndexOf(documentId); + if (index >= 0) + { + var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value.Children[index]; + if (searchingChecksumsLeft.Remove(identityChecksum)) + { + Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(documentId, out var state)); + result[identityChecksum] = state.Identity; + } + } + } + else { - var id = FrozenSourceGeneratedDocuments!.Value.Ids[i]; - Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); - result[identityChecksum] = state.Identity; + // Otherwise, we'll have to search through all of them. + for (var i = 0; i < FrozenSourceGeneratedDocumentIdentities.Value.Count; i++) + { + var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; + if (searchingChecksumsLeft.Remove(identityChecksum)) + { + var id = FrozenSourceGeneratedDocumentTexts.Value.Ids[i]; + Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); + result[identityChecksum] = state.Identity; + } + } } } } @@ -246,42 +277,16 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - if (assetPath.IncludeSolution) + if (assetPath.IncludeSolutionState) { - if (searchingChecksumsLeft.Remove(Checksum)) + if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) result[Checksum] = this; - if (searchingChecksumsLeft.Remove(Attributes)) + if (assetPath.IncludeSolutionAttributes && searchingChecksumsLeft.Remove(Attributes)) result[Attributes] = solution.SolutionAttributes; - 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) - 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; - - if (projectState.TryGetStateChecksums(out var projectStateChecksums)) - { - if (searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) - result[projectStateChecksums.Checksum] = projectStateChecksums; - - if (searchingChecksumsLeft.Remove(projectStateChecksums.Info)) - result[projectStateChecksums.Info] = projectState.Attributes; - - if (searchingChecksumsLeft.Remove(projectStateChecksums.CompilationOptions)) - result[projectStateChecksums.CompilationOptions] = projectState.CompilationOptions!; - } - } + if (assetPath.IncludeSolutionAnalyzerReferences) + ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); } if (searchingChecksumsLeft.Count == 0) @@ -305,21 +310,25 @@ public async Task FindAsync( } else { - // Full search, used for test purposes. + // Check all projects for the remaining checksums. + foreach (var (projectId, projectState) in solution.ProjectStates) { + cancellationToken.ThrowIfCancellationRequested(); + + // If we have no more checksums, can immediately bail out. 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); + if (!projectState.TryGetStateChecksums(out var projectStateChecksums)) + continue; + + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } } } @@ -440,41 +449,33 @@ public async Task FindAsync( if (assetPath.IncludeProjects) { - if (searchingChecksumsLeft.Remove(Checksum)) + if (assetPath.IncludeProjectStateChecksums && 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; - - if (searchingChecksumsLeft.Remove(Info)) + if (assetPath.IncludeProjectAttributes && 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 (assetPath.IncludeProjectCompilationOptions && searchingChecksumsLeft.Remove(CompilationOptions)) + result[CompilationOptions] = state.CompilationOptions ?? throw new InvalidOperationException("We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); - 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 (assetPath.IncludeProjectParseOptions && searchingChecksumsLeft.Remove(ParseOptions)) + result[ParseOptions] = state.ParseOptions ?? throw new InvalidOperationException("We should not be trying to serialize a project with no parse options; RemoteSupportedLanguages.IsSupported should have filtered it out."); + + if (assetPath.IncludeProjectProjectReferences) + ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, 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.IncludeProjectMetadataReferences) + ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken); + + if (assetPath.IncludeProjectAnalyzerReferences) + 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(assetPath, state.DocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } } } @@ -515,6 +516,7 @@ public static DocumentStateChecksums Deserialize(ObjectReader reader) } public async Task FindAsync( + AssetPath assetPath, TextDocumentState state, HashSet searchingChecksumsLeft, Dictionary result, @@ -524,20 +526,14 @@ public async Task FindAsync( cancellationToken.ThrowIfCancellationRequested(); - if (searchingChecksumsLeft.Remove(Checksum)) - { + if (assetPath.IncludeDocumentStateChecksums && searchingChecksumsLeft.Remove(Checksum)) result[Checksum] = this; - } - if (searchingChecksumsLeft.Remove(Info)) - { + if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) result[Info] = state.Attributes; - } - if (searchingChecksumsLeft.Remove(Text)) - { + if (assetPath.IncludeDocumentText && searchingChecksumsLeft.Remove(Text)) result[Text] = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); - } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index d1740d26d7500..0a0b27d970ab8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -295,8 +295,27 @@ public int Compare(DocumentId? x, DocumentId? y) public async ValueTask> GetChecksumsAndIdsAsync(CancellationToken cancellationToken) { - var documentChecksumTasks = SelectAsArray(static (state, token) => state.GetChecksumAsync(token), cancellationToken); - var documentChecksums = new ChecksumCollection(await documentChecksumTasks.WhenAll().ConfigureAwait(false)); + var documentTextChecksums = await SelectAsArrayAsync( + static async (state, _, cancellationToken) => await state.GetChecksumAsync(cancellationToken).ConfigureAwait(false), + arg: default(VoidResult), + cancellationToken).ConfigureAwait(false); + + var documentChecksums = new ChecksumCollection(documentTextChecksums); + return new(documentChecksums, SelectAsArray(static s => s.Id)); + } + + public async ValueTask> GetTextChecksumsAndIdsAsync(CancellationToken cancellationToken) + { + var documentTextChecksums = await SelectAsArrayAsync( + static async (state, _, cancellationToken) => + { + var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + return stateChecksums.Text; + }, + arg: default(VoidResult), + cancellationToken).ConfigureAwait(false); + + var documentChecksums = new ChecksumCollection(documentTextChecksums); return new(documentChecksums, SelectAsArray(static s => s.Id)); } diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 73c56bf579f7c..1365609946c5a 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -27,17 +27,17 @@ internal abstract class AbstractAssetProvider public async Task CreateSolutionInfoAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { - var solutionCompilationChecksums = await GetAssetAsync(AssetPath.SolutionOnly, solutionChecksum, cancellationToken).ConfigureAwait(false); - var solutionChecksums = await GetAssetAsync(AssetPath.SolutionOnly, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + var solutionCompilationChecksums = await GetAssetAsync(AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); + var solutionChecksums = await GetAssetAsync(AssetPathKind.SolutionStateChecksums, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); - var solutionAttributes = await GetAssetAsync(AssetPath.SolutionOnly, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); - await GetAssetAsync(AssetPath.SolutionOnly, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + var solutionAttributes = await GetAssetAsync(AssetPathKind.SolutionAttributes, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, 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 GetAssetsAsync(AssetPath.SolutionOnly, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await GetAssetsAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); return SolutionInfo.Create( solutionAttributes.Id, solutionAttributes.Version, solutionAttributes.FilePath, projects.ToImmutableAndClear(), analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId); @@ -45,19 +45,19 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu public async Task CreateProjectInfoAsync(ProjectId projectId, Checksum projectChecksum, CancellationToken cancellationToken) { - var projectChecksums = await GetAssetAsync(assetPath: projectId, projectChecksum, cancellationToken).ConfigureAwait(false); + var projectChecksums = await GetAssetAsync(new(AssetPathKind.ProjectStateChecksums, projectId), projectChecksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(projectId == projectChecksums.ProjectId); - var attributes = await GetAssetAsync(assetPath: projectId, projectChecksums.Info, cancellationToken).ConfigureAwait(false); + var attributes = await GetAssetAsync(new(AssetPathKind.ProjectAttributes, projectId), projectChecksums.Info, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(RemoteSupportedLanguages.IsSupported(attributes.Language)); var compilationOptions = attributes.FixUpCompilationOptions( - await GetAssetAsync(assetPath: projectId, projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); - var parseOptions = await GetAssetAsync(assetPath: projectId, projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(new(AssetPathKind.ProjectCompilationOptions, projectId), projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); + var parseOptions = await GetAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, 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 projectReferences = await GetAssetsAsync(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); + var metadataReferences = await GetAssetsAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await GetAssetsAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); var documentInfos = await CreateDocumentInfosAsync(projectChecksums.Documents).ConfigureAwait(false); var additionalDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments).ConfigureAwait(false); @@ -92,11 +92,11 @@ async Task> CreateDocumentInfosAsync(ChecksumsAndId public async Task CreateDocumentInfoAsync( DocumentId documentId, Checksum documentChecksum, CancellationToken cancellationToken) { - var documentSnapshot = await GetAssetAsync(assetPath: documentId, documentChecksum, cancellationToken).ConfigureAwait(false); + var documentSnapshot = await GetAssetAsync(new(AssetPathKind.DocumentStateChecksums, documentId), documentChecksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(documentId != documentSnapshot.DocumentId); - var attributes = await GetAssetAsync(assetPath: documentId, documentSnapshot.Info, cancellationToken).ConfigureAwait(false); - var serializableSourceText = await GetAssetAsync(assetPath: documentId, documentSnapshot.Text, cancellationToken).ConfigureAwait(false); + var attributes = await GetAssetAsync(new(AssetPathKind.DocumentAttributes, documentId), documentSnapshot.Info, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await GetAssetAsync(new(AssetPathKind.DocumentText, 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)); diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index ad5f1ca91a42e..2682fe95f3d3f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -8,6 +8,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; @@ -22,7 +23,7 @@ namespace Microsoft.CodeAnalysis.Remote; internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionAssetCache assetCache, IAssetSource assetSource, ISerializerService serializerService) : AbstractAssetProvider { - private const int PooledChecksumArraySize = 256; + private const int PooledChecksumArraySize = 1024; private static readonly ObjectPool s_checksumPool = new(() => new Checksum[PooledChecksumArraySize], 16); private readonly Checksum _solutionChecksum = solutionChecksum; @@ -57,6 +58,10 @@ public override async ValueTask GetAssetsAsync( await this.SynchronizeAssetsAsync(assetPath, checksums, callback, arg, cancellationToken).ConfigureAwait(false); } + /// + /// This is the function called when we are not doing an incremental update, but are instead doing a bulk + /// full sync. + /// public async ValueTask SynchronizeSolutionAssetsAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { var timer = SharedStopwatch.StartNew(); @@ -84,17 +89,18 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() { // first, get top level solution state for the given solution checksum var compilationStateChecksums = await this.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, solutionChecksum, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); using var _1 = PooledHashSet.GetInstance(out var checksums); // second, get direct children of the solution compilation state. compilationStateChecksums.AddAllTo(checksums); - await this.SynchronizeAssetsAsync(assetPath: AssetPath.SolutionOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + await this.SynchronizeAssetsAsync( + assetPath: AssetPathKind.SolutionCompilationState, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); // third, get direct children of the solution state. var stateChecksums = await this.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.SolutionStateChecksums, 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. @@ -103,22 +109,31 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() using var _2 = PooledDictionary.GetInstance(out var checksumToObjects); + // Note: this search will be optimized on the host side. It will search through the solution level values, + // and then the top level project-state-checksum values only. No other project data or document data will be + // looked at. await this.SynchronizeAssetsAsync>( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, + assetPath: AssetPathKind.SolutionState | AssetPathKind.ProjectStateChecksums, checksums, static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); + using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); + // fourth, get all projects and documents in the solution - foreach (var (projectChecksum, _) in stateChecksums.Projects) + foreach (var (projectChecksum, projectId) in stateChecksums.Projects) { var projectStateChecksums = (ProjectStateChecksums)checksumToObjects[projectChecksum]; - await SynchronizeProjectAssetsAsync(projectStateChecksums, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfTrue(projectStateChecksums.ProjectId != projectId); + allProjectStateChecksums.Add(projectStateChecksums); } + + await SynchronizeProjectAssetsAsync(allProjectStateChecksums, cancellationToken).ConfigureAwait(false); } } - public async ValueTask SynchronizeProjectAssetsAsync(ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) + public async ValueTask SynchronizeProjectAssetsAsync( + ArrayBuilder allProjectChecksums, CancellationToken cancellationToken) { // this will pull in assets that belong to the given project checksum to this remote host. this one is not // supposed to be used for functionality but only for perf. that is why it doesn't return anything. to get @@ -129,59 +144,147 @@ public async ValueTask SynchronizeProjectAssetsAsync(ProjectStateChecksums proje // GetAssetAsync call will most likely cache hit. it is most likely since we might change cache heuristic in // future which make data to live a lot shorter in the cache, and the data might get expired before one actually // consume the data. - using (Logger.LogBlock(FunctionId.AssetService_SynchronizeProjectAssetsAsync, Checksum.GetProjectChecksumsLogInfo, projectChecksums, cancellationToken)) + using (Logger.LogBlock(FunctionId.AssetService_SynchronizeProjectAssetsAsync, message: null, cancellationToken)) { - await SynchronizeProjectAssetsWorkerAsync().ConfigureAwait(false); + // It's common to have two usage patterns of SynchronizeProjectAssetsAsync. Bulk syncing the majority of the + // solution over, or just syncing a single project (or small set of projects) in response to a small change + // (like a user edit). For the bulk case, we want to make sure we're doing as few round trips as possible, + // getting as much of the data we can in each call. For the single project case though, we don't want to + // have the host have to search the entire solution graph for data we know it contained within just that + // project. + // + // So, we split up our strategy here based on how many projects we're syncing. If it's 4 or less, we just + // sync each project individually, passing the data to the host so it can limit its search to just that + // project. If it's more than that, we do it in bulk, knowing that as we're searching for a ton of + // data, it's fine for the host to do a full pass for each of the data types we're looking for. + if (allProjectChecksums.Count <= 4) + { + // Still sync the N projects in parallel. + using var _ = ArrayBuilder.GetInstance(allProjectChecksums.Count, out var tasks); + foreach (var singleProjectChecksums in allProjectChecksums) + { + // Make a fresh singleton array, containing just this project checksum, and pass into the helper + // below. That way we can have just a single helper for actually doing the syncing, regardless of if + // we are are doing a single project or multiple. + ArrayBuilder.GetInstance(capacity: 1, out var tempBuffer); + tempBuffer.Add(singleProjectChecksums); + + // We want to synchronize the assets just for this project. So we can pass the ProjectId as a hint + // to limit the search on the host side. + tasks.Add(SynchronizeProjectAssetsWorkerAsync( + tempBuffer, singleProjectChecksums.ProjectId, freeArrayBuilder: true, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + else + { + // We want to synchronize all assets in bulk. Because of this, we can't narrow the search on the host + // side to a particular ProjectId. + await SynchronizeProjectAssetsWorkerAsync( + allProjectChecksums, + projectId: null, + freeArrayBuilder: false, cancellationToken).ConfigureAwait(false); + } } + } - async ValueTask SynchronizeProjectAssetsWorkerAsync() + private async Task SynchronizeProjectAssetsWorkerAsync( + ArrayBuilder allProjectChecksums, ProjectId? projectId, bool freeArrayBuilder, CancellationToken cancellationToken) + { + try { - // get children of project checksum objects at once - using var _ = PooledHashSet.GetInstance(out var checksums); + await Task.Yield(); - checksums.Add(projectChecksums.Info); - checksums.Add(projectChecksums.CompilationOptions); - checksums.Add(projectChecksums.ParseOptions); - AddAll(checksums, projectChecksums.ProjectReferences); - AddAll(checksums, projectChecksums.MetadataReferences); - AddAll(checksums, projectChecksums.AnalyzerReferences); - AddAll(checksums, projectChecksums.Documents.Checksums); - AddAll(checksums, projectChecksums.AdditionalDocuments.Checksums); - AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); + using var _ = ArrayBuilder.GetInstance(out var tasks); - // First synchronize all the top-level info about this project. - await this.SynchronizeAssetsAsync( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + // Make parallel requests for all the project data across all projects at once. For each request, pass + // in the appropriate info to let the search avoid looking at data unnecessarily. + tasks.Add(SynchronizeProjectAssetAsync(new(AssetPathKind.ProjectAttributes, projectId), static p => p.Info)); + tasks.Add(SynchronizeProjectAssetAsync(new(AssetPathKind.ProjectCompilationOptions, projectId), static p => p.CompilationOptions)); + tasks.Add(SynchronizeProjectAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), static p => p.ParseOptions)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(new(AssetPathKind.ProjectProjectReferences, projectId), static p => p.ProjectReferences)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), static p => p.MetadataReferences)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), static p => p.AnalyzerReferences)); - checksums.Clear(); + // Then sync each project's documents in parallel with each other. + foreach (var projectChecksums in allProjectChecksums) + tasks.Add(SynchronizeProjectDocumentsAsync(projectChecksums)); + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + finally + { + if (freeArrayBuilder) + allProjectChecksums.Free(); + } - // Then synchronize the info about all the documents within. - await CollectChecksumChildrenAsync(checksums, projectChecksums.Documents).ConfigureAwait(false); - await CollectChecksumChildrenAsync(checksums, projectChecksums.AdditionalDocuments).ConfigureAwait(false); - await CollectChecksumChildrenAsync(checksums, projectChecksums.AnalyzerConfigDocuments).ConfigureAwait(false); + return; - await this.SynchronizeAssetsAsync( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + static void AddAll(HashSet checksums, ChecksumCollection checksumCollection) + { + foreach (var checksum in checksumCollection) + checksums.Add(checksum); } - async ValueTask CollectChecksumChildrenAsync(HashSet checksums, ChecksumsAndIds collection) + Task SynchronizeProjectAssetAsync(AssetPath assetPath, Func getChecksum) + => SynchronizeProjectAssetOrCollectionAsync>( + assetPath, + static (projectStateChecksums, checksums, getChecksum) => checksums.Add(getChecksum(projectStateChecksums)), + getChecksum); + + Task SynchronizeProjectAssetCollectionAsync(AssetPath assetPath, Func getChecksums) + => SynchronizeProjectAssetOrCollectionAsync>( + assetPath, + static (projectStateChecksums, checksums, getChecksums) => AddAll(checksums, getChecksums(projectStateChecksums)), + getChecksums); + + async Task SynchronizeProjectAssetOrCollectionAsync( + AssetPath assetPath, Action, TArg> addAllChecksums, TArg arg) { - // 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.GetAssetsAsync( - AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), collection.Checksums, cancellationToken).ConfigureAwait(false); - foreach (var docChecksums in allDocChecksums) + await Task.Yield(); + using var _ = PooledHashSet.GetInstance(out var checksums); + + foreach (var projectChecksums in allProjectChecksums) + addAllChecksums(projectChecksums, checksums, arg); + + await SynchronizeAssetsAsync(assetPath, checksums).ConfigureAwait(false); + } + + async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksums) + { + await Task.Yield(); + using var _1 = PooledHashSet.GetInstance(out var checksums); + + AddAll(checksums, projectChecksums.Documents.Checksums); + AddAll(checksums, projectChecksums.AdditionalDocuments.Checksums); + AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); + + // First, fetch all the DocumentStateChecksums for all the documents in the project. + using var _2 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); + await this.SynchronizeAssetsAsync>( + assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), checksums, + static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), + allDocumentStateChecksums, + cancellationToken).ConfigureAwait(false); + + // Now go and fetch the info and text for all of those documents. + checksums.Clear(); + foreach (var docChecksums in allDocumentStateChecksums) { checksums.Add(docChecksums.Info); checksums.Add(docChecksums.Text); } - } - static void AddAll(HashSet checksums, ChecksumCollection checksumCollection) - { - foreach (var checksum in checksumCollection) - checksums.Add(checksum); + // We know we only need to search the documents in this particular project for those info/text values. So + // pass in the right path hint to limit the search on the host side to just the document in this project. + await SynchronizeAssetsAsync( + assetPath: new(AssetPathKind.DocumentAttributes | AssetPathKind.DocumentText, projectChecksums.ProjectId), + checksums).ConfigureAwait(false); } + + ValueTask SynchronizeAssetsAsync(AssetPath assetPath, HashSet checksums) + => this.SynchronizeAssetsAsync(assetPath, checksums, callback: null, arg: default, cancellationToken); } public async ValueTask SynchronizeAssetsAsync( diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index d9c8cbaaf0a6f..b78af045a4138 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -37,12 +37,12 @@ private readonly struct SolutionCreator(HostServices hostServices, AssetProvider public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionChecksum, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, newSolutionChecksum, cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionAttributes, 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; @@ -59,9 +59,9 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca solution = solution.WithoutFrozenSourceGeneratedDocuments(); var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionChecksum, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, newSolutionChecksum, cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); var oldSolutionCompilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); var oldSolutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); @@ -69,7 +69,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca if (oldSolutionChecksums.Attributes != newSolutionChecksums.Attributes) { var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionAttributes, 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); @@ -84,39 +84,43 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca if (oldSolutionChecksums.AnalyzerReferences.Checksum != newSolutionChecksums.AnalyzerReferences.Checksum) { solution = solution.WithAnalyzerReferences(await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.SolutionOnly, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); + AssetPathKind.SolutionAnalyzerReferences, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } if (newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.HasValue && - newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.HasValue && + newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentTexts.HasValue && !newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes.IsDefault) { - var count = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value.Count; - var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); + var newSolutionFrozenSourceGeneratedDocumentIdentities = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value; + var newSolutionFrozenSourceGeneratedDocumentTexts = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentTexts.Value; + var count = newSolutionFrozenSourceGeneratedDocumentTexts.Checksums.Count; + using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); for (var i = 0; i < count; i++) { - var identity = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); + var frozenDocumentId = newSolutionFrozenSourceGeneratedDocumentTexts.Ids[i]; + var frozenDocumentTextChecksum = newSolutionFrozenSourceGeneratedDocumentTexts.Checksums[i]; + var frozenDocumentIdentity = newSolutionFrozenSourceGeneratedDocumentIdentities[i]; - var documentStateChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Checksums[i], cancellationToken).ConfigureAwait(false); + var identity = await _assetProvider.GetAssetAsync( + new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentIdentities, frozenDocumentId), frozenDocumentIdentity, cancellationToken).ConfigureAwait(false); - var serializableSourceText = await _assetProvider.GetAssetAsync(assetPath: newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i], documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync( + new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentText, frozenDocumentId), frozenDocumentTextChecksum, cancellationToken).ConfigureAwait(false); var generationDateTime = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes[i]; var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); frozenDocuments.Add((identity, generationDateTime, text)); } - solution = solution.WithFrozenSourceGeneratedDocuments(frozenDocuments.ToImmutable()); + solution = solution.WithFrozenSourceGeneratedDocuments(frozenDocuments.ToImmutableAndClear()); } if (oldSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap != newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap) { var newVersions = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, 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 @@ -220,7 +224,7 @@ private async Task UpdateProjectsAsync( newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); await _assetProvider.GetAssetsAsync>( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, newChecksumsToSync, + assetPath: AssetPathKind.ProjectStateChecksums, newChecksumsToSync, static (checksum, newProjectStateChecksum, newProjectIdToStateChecksums) => { Contract.ThrowIfTrue(checksum != newProjectStateChecksum.Checksum); @@ -253,27 +257,36 @@ private async Task UpdateProjectsAsync( projectItemChecksums.Add(newProjectChecksums.Info); await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.ProjectAttributes, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); projectItemChecksums.Clear(); foreach (var (_, newProjectChecksums) in newProjectIdToStateChecksums) projectItemChecksums.Add(newProjectChecksums.CompilationOptions); await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.ProjectCompilationOptions, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } using var _2 = ArrayBuilder.GetInstance(out var projectInfos); + using var _3 = ArrayBuilder.GetInstance(out var projectStateChecksumsToAdd); // added project foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) { if (!oldProjectIdToStateChecksums.ContainsKey(projectId)) - { - // bulk sync added project assets fully since we'll definitely need that data, and we won't want - // to make tons of intermediary calls for it. + projectStateChecksumsToAdd.Add(newProjectChecksums); + } + + // bulk sync added project assets fully since we'll definitely need that data, and we can fetch more + // efficiently in bulk and in parallel. + await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); - await _assetProvider.SynchronizeProjectAssetsAsync(newProjectChecksums, cancellationToken).ConfigureAwait(false); + foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) + { + if (!oldProjectIdToStateChecksums.ContainsKey(projectId)) + { + // Now make a ProjectInfo corresponding to the new project checksums. This should be fast due + // to the bulk sync we just performed above. var projectInfo = await _assetProvider.CreateProjectInfoAsync(projectId, newProjectChecksums.Checksum, cancellationToken).ConfigureAwait(false); projectInfos.Add(projectInfo); } @@ -295,7 +308,7 @@ await _assetProvider.GetAssetsAsync( } } - using var _3 = ArrayBuilder.GetInstance(out var projectsToRemove); + using var _4 = ArrayBuilder.GetInstance(out var projectsToRemove); // removed project foreach (var (projectId, _) in oldProjectIdToStateChecksums) @@ -535,7 +548,7 @@ private async Task UpdateDocumentsAsync( newChecksumsToSync.AddRange(newDocumentIdToChecksum.Values); await _assetProvider.GetAssetsAsync>( - assetPath: AssetPath.ProjectAndDocuments(project.Id), newChecksumsToSync, + assetPath: AssetPath.DocumentsInProject(project.Id), newChecksumsToSync, static (checksum, documentStateChecksum, newDocumentIdToStateChecksums) => { Contract.ThrowIfTrue(checksum != documentStateChecksum.Checksum); @@ -549,7 +562,9 @@ await _assetProvider.GetAssetsAsync 2) { - await _assetProvider.SynchronizeProjectAssetsAsync(projectChecksums, cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var allProjectChecksums); + allProjectChecksums.Add(projectChecksums); + await _assetProvider.SynchronizeProjectAssetsAsync(allProjectChecksums, cancellationToken).ConfigureAwait(false); } return await UpdateDocumentsAsync(project, addDocuments, removeDocuments, oldDocumentIdToStateChecksums, newDocumentIdToStateChecksums, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index d12f4b38a7eeb..c1f05df5cf279 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -115,9 +115,9 @@ async Task> GetAllChildrenChecksumsAsync(Checksum solutionChec var set = new HashSet(); var solutionCompilationChecksums = await assetService.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, solutionChecksum, CancellationToken.None).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, CancellationToken.None).ConfigureAwait(false); var solutionChecksums = await assetService.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, solutionCompilationChecksums.SolutionState, CancellationToken.None).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, solutionCompilationChecksums.SolutionState, CancellationToken.None).ConfigureAwait(false); solutionCompilationChecksums.AddAllTo(set); solutionChecksums.AddAllTo(set); @@ -241,7 +241,7 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary