From f32ae1beae6854cb201110861510a078d744531c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 2 Apr 2024 23:30:42 -0700 Subject: [PATCH 1/8] Sync solution contents consistently --- .../Host/RemoteWorkspace.SolutionCreator.cs | 14 ------ .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 44 ++++++++++++------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index ee8f0ed78198d..c3d2f2d463258 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -34,20 +34,6 @@ private readonly struct SolutionCreator(HostServices hostServices, AssetProvider private readonly AssetProvider _assetProvider = assetService; private readonly Solution _baseSolution = baseSolution; - public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) - { - var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksum, cancellationToken).ConfigureAwait(false); - var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); - - var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, 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; - } - public async Task CreateSolutionAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { try diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 3ac0f34a4bf9e..b8b2d67b3c8e7 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -228,27 +228,41 @@ private async Task ComputeDisconnectedSolutionAsync( { try { - // Try to create the solution snapshot incrementally off of the workspaces CurrentSolution first. - var updater = new SolutionCreator(Services.HostServices, assetProvider, this.CurrentSolution); - if (await updater.IsIncrementalUpdateAsync(solutionChecksum, cancellationToken).ConfigureAwait(false)) - { - return await updater.CreateSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); - } - else - { - // Otherwise, this is a different solution, or the first time we're creating this solution. Bulk - // sync over all assets for it. - await assetProvider.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); + var solutionToUpdate = await GetOrCreateSolutionToUpdateAsync(this.CurrentSolution).ConfigureAwait(false); - // get new solution info and options - var solutionInfo = await assetProvider.CreateSolutionInfoAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); - return CreateSolutionFromInfo(solutionInfo); - } + // Now, bring that solution in line with the snapshot defined by solutionChecksum. + var updater = new SolutionCreator(Services.HostServices, assetProvider, solutionToUpdate); + return await updater.CreateSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { throw ExceptionUtilities.Unreachable(); } + + async Task GetOrCreateSolutionToUpdateAsync(Solution currentSolution) + { + // See if we can just incrementally update the current solution. + if (await IsIncrementalUpdateAsync(currentSolution).ConfigureAwait(false)) + return currentSolution; + + // If not, have to create a new, fresh, solution instance to update. + var solutionInfo = await assetProvider.CreateSolutionInfoAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); + return CreateSolutionFromInfo(solutionInfo); + } + + async Task IsIncrementalUpdateAsync(Solution currentSolution) + { + var newSolutionCompilationChecksums = await assetProvider.GetAssetAsync( + assetHint: AssetHint.None, solutionChecksum, cancellationToken).ConfigureAwait(false); + var newSolutionChecksums = await assetProvider.GetAssetAsync( + assetHint: AssetHint.None, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + + var newSolutionInfo = await assetProvider.GetAssetAsync( + assetHint: AssetHint.None, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + + // if either solution id or file path changed, then we consider it as new solution + return currentSolution.Id == newSolutionInfo.Id && currentSolution.FilePath == newSolutionInfo.FilePath; + } } private Solution CreateSolutionFromInfo(SolutionInfo solutionInfo) From f5d3c3e6287e6b2fc729a15f32187bcf2a984840 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 3 Apr 2024 00:43:36 -0700 Subject: [PATCH 2/8] Extract --- .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index b8b2d67b3c8e7..643039ff3e1c1 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -204,6 +204,24 @@ async ValueTask DecrementInFlightCountAsync(InFlightSolution inFlightSolution) } } + private async Task IsIncrementalUpdateAsync( + AssetProvider assetProvider, + Checksum solutionChecksum, + Solution currentSolution, + CancellationToken cancellationToken) + { + var newSolutionCompilationChecksums = await assetProvider.GetAssetAsync( + assetHint: AssetHint.None, solutionChecksum, cancellationToken).ConfigureAwait(false); + var newSolutionChecksums = await assetProvider.GetAssetAsync( + assetHint: AssetHint.None, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + + var newSolutionInfo = await assetProvider.GetAssetAsync( + assetHint: AssetHint.None, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + + // if either solution id or file path changed, then we consider it as new solution + return currentSolution.Id == newSolutionInfo.Id && currentSolution.FilePath == newSolutionInfo.FilePath; + } + /// /// Create an appropriate instance corresponding to the passed in. Note: this method changes no Workspace state and exists purely to @@ -242,27 +260,13 @@ private async Task ComputeDisconnectedSolutionAsync( async Task GetOrCreateSolutionToUpdateAsync(Solution currentSolution) { // See if we can just incrementally update the current solution. - if (await IsIncrementalUpdateAsync(currentSolution).ConfigureAwait(false)) + if (await IsIncrementalUpdateAsync(assetProvider, solutionChecksum, currentSolution, cancellationToken).ConfigureAwait(false)) return currentSolution; // If not, have to create a new, fresh, solution instance to update. var solutionInfo = await assetProvider.CreateSolutionInfoAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); return CreateSolutionFromInfo(solutionInfo); } - - async Task IsIncrementalUpdateAsync(Solution currentSolution) - { - var newSolutionCompilationChecksums = await assetProvider.GetAssetAsync( - assetHint: AssetHint.None, solutionChecksum, cancellationToken).ConfigureAwait(false); - var newSolutionChecksums = await assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); - - var newSolutionInfo = await assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); - - // if either solution id or file path changed, then we consider it as new solution - return currentSolution.Id == newSolutionInfo.Id && currentSolution.FilePath == newSolutionInfo.FilePath; - } } private Solution CreateSolutionFromInfo(SolutionInfo solutionInfo) From 97c7ef5411ccc4b2375899e3f8baf15284a82c22 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 3 Apr 2024 00:45:30 -0700 Subject: [PATCH 3/8] Extract --- .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 643039ff3e1c1..fafcc9ed10d00 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.VisualStudio.Telemetry; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using static Microsoft.VisualStudio.Threading.ThreadingTools; @@ -204,7 +205,7 @@ async ValueTask DecrementInFlightCountAsync(InFlightSolution inFlightSolution) } } - private async Task IsIncrementalUpdateAsync( + private static async Task IsIncrementalUpdateAsync( AssetProvider assetProvider, Checksum solutionChecksum, Solution currentSolution, @@ -222,6 +223,21 @@ private async Task IsIncrementalUpdateAsync( return currentSolution.Id == newSolutionInfo.Id && currentSolution.FilePath == newSolutionInfo.FilePath; } + private async Task GetOrCreateSolutionToUpdateAsync( + AssetProvider assetProvider, + Checksum solutionChecksum, + CancellationToken cancellationToken) + { + // See if we can just incrementally update the current solution. + var currentSolution = this.CurrentSolution; + if (await IsIncrementalUpdateAsync(assetProvider, solutionChecksum, currentSolution, cancellationToken).ConfigureAwait(false)) + return currentSolution; + + // If not, have to create a new, fresh, solution instance to update. + var solutionInfo = await assetProvider.CreateSolutionInfoAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); + return CreateSolutionFromInfo(solutionInfo); + } + /// /// Create an appropriate instance corresponding to the passed in. Note: this method changes no Workspace state and exists purely to @@ -246,7 +262,8 @@ private async Task ComputeDisconnectedSolutionAsync( { try { - var solutionToUpdate = await GetOrCreateSolutionToUpdateAsync(this.CurrentSolution).ConfigureAwait(false); + var solutionToUpdate = await GetOrCreateSolutionToUpdateAsync( + assetProvider, solutionChecksum, cancellationToken).ConfigureAwait(false); // Now, bring that solution in line with the snapshot defined by solutionChecksum. var updater = new SolutionCreator(Services.HostServices, assetProvider, solutionToUpdate); @@ -256,17 +273,6 @@ private async Task ComputeDisconnectedSolutionAsync( { throw ExceptionUtilities.Unreachable(); } - - async Task GetOrCreateSolutionToUpdateAsync(Solution currentSolution) - { - // See if we can just incrementally update the current solution. - if (await IsIncrementalUpdateAsync(assetProvider, solutionChecksum, currentSolution, cancellationToken).ConfigureAwait(false)) - return currentSolution; - - // If not, have to create a new, fresh, solution instance to update. - var solutionInfo = await assetProvider.CreateSolutionInfoAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); - return CreateSolutionFromInfo(solutionInfo); - } } private Solution CreateSolutionFromInfo(SolutionInfo solutionInfo) From fb039e9dd29b43c28ddd6deda155d74e4849957e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 11:06:55 -0700 Subject: [PATCH 4/8] Inline --- .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 91d68cd7f9264..2a237dbc5e8ed 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -206,24 +206,6 @@ async ValueTask DecrementInFlightCountAsync(InFlightSolution inFlightSolution) } } - private static async Task IsIncrementalUpdateAsync( - AssetProvider assetProvider, - Checksum solutionChecksum, - Solution currentSolution, - CancellationToken cancellationToken) - { - var newSolutionCompilationChecksums = await assetProvider.GetAssetAsync( - AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); - var newSolutionChecksums = await assetProvider.GetAssetAsync( - AssetPathKind.SolutionStateChecksums, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); - - var newSolutionInfo = await assetProvider.GetAssetAsync( - AssetPathKind.SolutionAttributes, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); - - // if either solution id or file path changed, then we consider it as new solution - return currentSolution.Id == newSolutionInfo.Id && currentSolution.FilePath == newSolutionInfo.FilePath; - } - private async Task GetOrCreateSolutionToUpdateAsync( AssetProvider assetProvider, Checksum solutionChecksum, @@ -231,12 +213,26 @@ private async Task GetOrCreateSolutionToUpdateAsync( { // See if we can just incrementally update the current solution. var currentSolution = this.CurrentSolution; - if (await IsIncrementalUpdateAsync(assetProvider, solutionChecksum, currentSolution, cancellationToken).ConfigureAwait(false)) + if (await IsIncrementalUpdateAsync().ConfigureAwait(false)) return currentSolution; // If not, have to create a new, fresh, solution instance to update. var solutionInfo = await assetProvider.CreateSolutionInfoAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); return CreateSolutionFromInfo(solutionInfo); + + async Task IsIncrementalUpdateAsync() + { + var newSolutionCompilationChecksums = await assetProvider.GetAssetAsync( + AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); + var newSolutionChecksums = await assetProvider.GetAssetAsync( + AssetPathKind.SolutionStateChecksums, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + + var newSolutionInfo = await assetProvider.GetAssetAsync( + AssetPathKind.SolutionAttributes, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + + // if either solution id or file path changed, then we consider it as new solution + return currentSolution.Id == newSolutionInfo.Id && currentSolution.FilePath == newSolutionInfo.FilePath; + } } /// From cfed4b3c9041f682624140620e51b42f1119a009 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 2 Jul 2024 10:07:04 -0700 Subject: [PATCH 5/8] Fix oop syncing --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index c1eeef2674377..2afedfce05534 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -45,7 +45,7 @@ await this.GetAssetHelper().GetAssetsAsync( cancellationToken).ConfigureAwait(false); var analyzerReferences = await this.GetAssetsArrayAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); - var fallbackAnalyzerOptions = await GetAssetAsync>(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.FallbackAnalyzerOptions, cancellationToken).ConfigureAwait(false); + var fallbackAnalyzerOptions = await GetAssetAsync>(AssetPathKind.SolutionFallbackAnalyzerOptions, solutionChecksums.FallbackAnalyzerOptions, cancellationToken).ConfigureAwait(false); // Fetch the projects in parallel. var projects = await Task.WhenAll(projectsTasks).ConfigureAwait(false); From 5a01bf64f43e2b84f7b172f4704022aae2d7e72d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 2 Jul 2024 10:53:18 -0700 Subject: [PATCH 6/8] Underlying tracker should only fetch contents from remote underlying tracker --- .../IRemoteSourceGenerationService.cs | 17 ++++++++++++-- ...pilationTracker.CompilationTrackerState.cs | 6 ++--- ...tionCompilationState.CompilationTracker.cs | 22 +++++++++++-------- ...onState.CompilationTrackerGeneratorInfo.cs | 2 +- ...tionState.CompilationTracker_Generators.cs | 7 +++--- ...eneratedFileReplacingCompilationTracker.cs | 11 +++++++++- ...ionCompilationState.ICompilationTracker.cs | 16 ++++++++++++++ .../Solution/SolutionCompilationState.cs | 18 ++++++++++++--- .../RemoteSourceGenerationService.cs | 13 +++++------ 9 files changed, 83 insertions(+), 29 deletions(-) diff --git a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs index 659de283e8849..035be31f49f39 100644 --- a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs +++ b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs @@ -12,6 +12,11 @@ namespace Microsoft.CodeAnalysis.SourceGeneration; +internal readonly record struct RegularCompilationTrackerSourceGenerationInfo( + SourceGeneratedDocumentIdentity DocumentIdentity, + SourceGeneratedDocumentContentIdentity ContentIdentity, + DateTime GenerationDateTime); + internal interface IRemoteSourceGenerationService { /// @@ -21,7 +26,11 @@ internal interface IRemoteSourceGenerationService /// compare that to the prior generated documents it has to see if it can reuse those directly, or if it needs to /// remove any documents no longer around, add any new documents, or change the contents of any existing documents. /// - ValueTask> GetSourceGenerationInfoAsync( + /// + /// Should only be called by the "RegularCompilationTracker", and should only return data from its view of the + /// world. Not from the view of a "GeneratedFileReplacingCompilationTracker". + /// + ValueTask> GetRegularCompilationTrackerSourceGenerationInfoAsync( Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken); /// @@ -29,7 +38,11 @@ internal interface IRemoteSourceGenerationService /// Should only be called by the host for documents it does not know about, or documents whose checksum contents are /// different than the last time the document was queried. /// - ValueTask> GetContentsAsync( + /// + /// Should only be called by the "RegularCompilationTracker", and should only return data from its view of the + /// world. Not from the view of a "GeneratedFileReplacingCompilationTracker". + /// + ValueTask> GetRegularCompilationTrackerContentsAsync( Checksum solutionChecksum, ProjectId projectId, ImmutableArray documentIds, CancellationToken cancellationToken); /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs index b8cb90e3b3deb..2b704cf822af2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs @@ -14,11 +14,11 @@ namespace Microsoft.CodeAnalysis; internal partial class SolutionCompilationState { - private partial class CompilationTracker + private partial class RegularCompilationTracker { /// - /// The base type of all states. The state of a starts at null, and then will progress through the other states until it + /// The base type of all states. The state of a starts at null, and then will progress through the other states until it /// finally reaches . /// private abstract class CompilationTrackerState diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index 571703a96d05f..488198f28cc33 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -27,7 +27,7 @@ internal partial class SolutionCompilationState /// compilation for that project. As the compilation is being built, the partial results are /// stored as well so that they can be used in the 'in progress' workspace snapshot. /// - private partial class CompilationTracker : ICompilationTracker + private sealed partial class RegularCompilationTracker : ICompilationTracker { private static readonly Func s_logBuildCompilationAsync = state => string.Join(",", state.AssemblyName, state.DocumentStates.Count); @@ -55,7 +55,7 @@ private partial class CompilationTracker : ICompilationTracker /// private readonly bool _validateStates; - private CompilationTracker( + private RegularCompilationTracker( ProjectState project, CompilationTrackerState? state, in SkeletonReferenceCache skeletonReferenceCacheToClone) @@ -76,7 +76,7 @@ private CompilationTracker( /// Creates a tracker for the provided project. The tracker will be in the 'empty' state /// and will have no extra information beyond the project itself. /// - public CompilationTracker(ProjectState project) + public RegularCompilationTracker(ProjectState project) : this(project, state: null, skeletonReferenceCacheToClone: new()) { } @@ -131,7 +131,7 @@ public ICompilationTracker Fork( // it since some change has happened, and we may now need to run generators. Contract.ThrowIfTrue(forkedTrackerState is FinalCompilationTrackerState); Contract.ThrowIfFalse(forkedTrackerState is null or InProgressState); - return new CompilationTracker( + return new RegularCompilationTracker( newProjectState, forkedTrackerState, skeletonReferenceCacheToClone: _skeletonReferenceCache); @@ -729,7 +729,7 @@ public ICompilationTracker WithCreateCreationPolicy(bool forceRegeneration) _ => throw ExceptionUtilities.UnexpectedValue(state.GetType()), }; - return new CompilationTracker( + return new RegularCompilationTracker( this.ProjectState, newState, skeletonReferenceCacheToClone: _skeletonReferenceCache); @@ -749,7 +749,7 @@ public ICompilationTracker WithDoNotCreateCreationPolicy(CancellationToken cance var newFinalState = finalState.WithCreationPolicy(desiredCreationPolicy); return newFinalState == finalState ? this - : new CompilationTracker(this.ProjectState, newFinalState, skeletonReferenceCacheToClone: _skeletonReferenceCache); + : new RegularCompilationTracker(this.ProjectState, newFinalState, skeletonReferenceCacheToClone: _skeletonReferenceCache); } // Non-final state currently. Produce an in-progress-state containing the forked change. Note: we @@ -795,7 +795,7 @@ public ICompilationTracker WithDoNotCreateCreationPolicy(CancellationToken cance // Safe cast to appease NRT system. var lazyCompilationWithGeneratedDocuments = (Lazy)lazyCompilationWithoutGeneratedDocuments!; - return new CompilationTracker( + return new RegularCompilationTracker( frozenProjectState, new InProgressState( desiredCreationPolicy, @@ -821,7 +821,7 @@ public ICompilationTracker WithDoNotCreateCreationPolicy(CancellationToken cance var compilationWithGeneratedDocuments = new Lazy(() => compilationWithoutGeneratedDocuments.Value.AddSyntaxTrees( generatorInfo.Documents.States.Values.Select(state => state.GetSyntaxTree(cancellationToken)))); - return new CompilationTracker( + return new RegularCompilationTracker( frozenProjectState, new InProgressState( desiredCreationPolicy, @@ -837,7 +837,11 @@ public ICompilationTracker WithDoNotCreateCreationPolicy(CancellationToken cance } } - public async ValueTask> GetSourceGeneratedDocumentStatesAsync( + public ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) + // Just defer to the core function that creates these. They are the right values for both of these calls. + => GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync(compilationState, cancellationToken); + + public async ValueTask> GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) { // If we don't have any generators, then we know we have no generated files, so we can skip the computation entirely. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTrackerGeneratorInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTrackerGeneratorInfo.cs index fe74dc7c45240..1dad8f3d96d70 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTrackerGeneratorInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTrackerGeneratorInfo.cs @@ -6,7 +6,7 @@ namespace Microsoft.CodeAnalysis; internal partial class SolutionCompilationState { - private partial class CompilationTracker + private partial class RegularCompilationTracker { /// /// The best generated documents we have for the current state. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs index 2814e591ac75c..6dcc1bc268471 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis; internal partial class SolutionCompilationState { - private partial class CompilationTracker : ICompilationTracker + private partial class RegularCompilationTracker : ICompilationTracker { private async Task<(Compilation compilationWithGeneratedFiles, CompilationTrackerGeneratorInfo nextGeneratorInfo)> AddExistingOrComputeNewGeneratorInfoAsync( CreationPolicy creationPolicy, @@ -96,7 +96,8 @@ private partial class CompilationTracker : ICompilationTracker var infosOpt = await connection.TryInvokeAsync( compilationState, projectId, - (service, solutionChecksum, cancellationToken) => service.GetSourceGenerationInfoAsync(solutionChecksum, projectId, cancellationToken), + (service, solutionChecksum, cancellationToken) => service.GetRegularCompilationTrackerSourceGenerationInfoAsync( + solutionChecksum, projectId, cancellationToken), cancellationToken).ConfigureAwait(false); if (!infosOpt.HasValue) @@ -158,7 +159,7 @@ private partial class CompilationTracker : ICompilationTracker var generatedSourcesOpt = await connection.TryInvokeAsync( compilationState, projectId, - (service, solutionChecksum, cancellationToken) => service.GetContentsAsync( + (service, solutionChecksum, cancellationToken) => service.GetRegularCompilationTrackerContentsAsync( solutionChecksum, projectId, documentsToAddOrUpdate.ToImmutable(), cancellationToken), cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index f176a6b378116..eef55a0951986 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -18,7 +18,7 @@ internal partial class SolutionCompilationState /// to return a generated document with a specific content, regardless of what the generator actually produces. In other words, it says /// "take the compilation this other thing produced, and pretend the generator gave this content, even if it wouldn't." /// - private class GeneratedFileReplacingCompilationTracker : ICompilationTracker + private sealed class GeneratedFileReplacingCompilationTracker : ICompilationTracker { private readonly TextDocumentStates _replacementDocumentStates; @@ -185,6 +185,15 @@ public async ValueTask> GetSour return newStates; } + public ValueTask> GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) + { + // Just defer to the underlying tracker. The caller only wants the innermost generated documents, not any + // frozen docs we'll overlay on top of it. The caller will already know about those frozen documents and + // will do its own overlay on these results. + return this.UnderlyingTracker.GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync(compilationState, cancellationToken); + } + public Task HasSuccessfullyLoadedAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs index 73c4ec43554f9..92c97cb4b292c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs @@ -55,7 +55,23 @@ bool ContainsAssemblyOrModuleOrDynamic( Task GetDependentSemanticVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); Task GetDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); + /// + /// Gets the *final* view of the generated documents for this tracker. If this is a this will be the true generated documents for this tracker (generated by + /// our underlying ). If this is a , then this will be the generated documents of its , along with all of its replacement + /// frozen documents overlaid on top. + /// ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); + + /// + /// Equivalent to , but only returning from the innermost + /// underlying . Any frozen generated documents in a will *not* be overlaid on top of this. + /// + ValueTask> GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); + ValueTask> GetSourceGeneratorDiagnosticsAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); ValueTask GetSourceGeneratorRunResultAsync(SolutionCompilationState solution, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 38322e7bc9fd0..6048f7694f8cb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -950,13 +950,13 @@ private SolutionCompilationState UpdateAnalyzerConfigDocumentState(StateChange s private bool TryGetCompilationTracker(ProjectId projectId, [NotNullWhen(returnValue: true)] out ICompilationTracker? tracker) => _projectIdToTrackerMap.TryGetValue(projectId, out tracker); - private static readonly Func s_createCompilationTrackerFunction = CreateCompilationTracker; + private static readonly Func s_createCompilationTrackerFunction = CreateCompilationTracker; - private static CompilationTracker CreateCompilationTracker(ProjectId projectId, SolutionState solution) + private static RegularCompilationTracker CreateCompilationTracker(ProjectId projectId, SolutionState solution) { var projectState = solution.GetProjectState(projectId); Contract.ThrowIfNull(projectState); - return new CompilationTracker(projectState); + return new RegularCompilationTracker(projectState); } private ICompilationTracker GetCompilationTracker(ProjectId projectId) @@ -1037,6 +1037,18 @@ public ValueTask> GetSourceGene : new(TextDocumentStates.Empty); } + /// + /// Equivalent to , but only returning from the underlying . + /// + internal ValueTask> GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync( + ProjectState project, CancellationToken cancellationToken) + { + return project.SupportsCompilation + ? GetCompilationTracker(project.Id).GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync(this, cancellationToken) + : new(TextDocumentStates.Empty); + } + public ValueTask> GetSourceGeneratorDiagnosticsAsync( ProjectState project, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index fa6ca45e97d70..993f3a5e4cc91 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; @@ -30,32 +29,32 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr => new RemoteSourceGenerationService(arguments); } - public ValueTask> GetSourceGenerationInfoAsync( + public ValueTask> GetRegularCompilationTrackerSourceGenerationInfoAsync( Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => { var project = solution.GetRequiredProject(projectId); - var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); + var documentStates = await solution.CompilationState.GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - var result = new FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>(documentStates.States.Count); + var result = new FixedSizeArrayBuilder(documentStates.States.Count); foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); - result.Add((state.Identity, state.GetContentIdentity(), state.GenerationDateTime)); + result.Add(new(state.Identity, state.GetContentIdentity(), state.GenerationDateTime)); } return result.MoveToImmutable(); }, cancellationToken); } - public ValueTask> GetContentsAsync( + public ValueTask> GetRegularCompilationTrackerContentsAsync( Checksum solutionChecksum, ProjectId projectId, ImmutableArray documentIds, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => { var project = solution.GetRequiredProject(projectId); - var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); + var documentStates = await solution.CompilationState.GetRegularCompilationTrackerSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); var result = new FixedSizeArrayBuilder(documentIds.Length); foreach (var id in documentIds) From dedfe2de859d65534521b2f49c6c499f3a856bad Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 2 Jul 2024 10:58:19 -0700 Subject: [PATCH 7/8] serialization --- .../SourceGeneration/IRemoteSourceGenerationService.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs index 035be31f49f39..1a6517862a9db 100644 --- a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs +++ b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs @@ -12,10 +12,11 @@ namespace Microsoft.CodeAnalysis.SourceGeneration; +[DataContract] internal readonly record struct RegularCompilationTrackerSourceGenerationInfo( - SourceGeneratedDocumentIdentity DocumentIdentity, - SourceGeneratedDocumentContentIdentity ContentIdentity, - DateTime GenerationDateTime); + [property: DataMember(Order = 0)] SourceGeneratedDocumentIdentity DocumentIdentity, + [property: DataMember(Order = 1)] SourceGeneratedDocumentContentIdentity ContentIdentity, + [property: DataMember(Order = 2)] DateTime GenerationDateTime); internal interface IRemoteSourceGenerationService { From b1560c99d2496f57b270c509eee853c4bd7ba6b3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 2 Jul 2024 11:13:05 -0700 Subject: [PATCH 8/8] Add docs --- ...CompilationState.CompilationTracker_Generators.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs index 6dcc1bc268471..7a44a7216b47f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs @@ -91,7 +91,10 @@ private partial class RegularCompilationTracker : ICompilationTracker using var connection = client.CreateConnection(callbackTarget: null); using var _ = await RemoteKeepAliveSession.CreateAsync(compilationState, cancellationToken).ConfigureAwait(false); - // First, grab the info from our external host about the generated documents it has for this project. + // First, grab the info from our external host about the generated documents it has for this project. Note: + // we ourselves are the innermost "RegularCompilationTracker" responsible for actually running generators. + // As such, our call to the oop side reflects that by asking for only *its* innermost + // RegularCompilationTracker to do the same. var projectId = this.ProjectState.Id; var infosOpt = await connection.TryInvokeAsync( compilationState, @@ -154,8 +157,11 @@ private partial class RegularCompilationTracker : ICompilationTracker : (compilationWithStaleGeneratedTrees, oldGeneratedDocuments); } - // Either we generated a different number of files, and/or we had contents of files that changed. Ensure - // we know the contents of any new/changed files. + // Either we generated a different number of files, and/or we had contents of files that changed. Ensure we + // know the contents of any new/changed files. Note: we ourselves are the innermost + // "RegularCompilationTracker" responsible for actually running generators. As such, our call to the oop + // side reflects that by asking for the source gen contents produced by *its* innermost + // RegularCompilationTracker. var generatedSourcesOpt = await connection.TryInvokeAsync( compilationState, projectId,