Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
ec96fdf
in progress
CyrusNajmabadi Feb 14, 2024
e10f539
Merge branch 'extractHelper' into frozenPartialOuter
CyrusNajmabadi Feb 14, 2024
e779049
Revert
CyrusNajmabadi Feb 14, 2024
9474247
fix tests
CyrusNajmabadi Feb 14, 2024
47d67dc
Remove
CyrusNajmabadi Feb 14, 2024
0ae7572
Merge branch 'extractHelper' into frozenPartialOuter
CyrusNajmabadi Feb 14, 2024
da1d505
Fixup
CyrusNajmabadi Feb 14, 2024
33c13c9
Merge branch 'main' into frozenPartialOuter
CyrusNajmabadi Feb 14, 2024
0f14496
Simplify
CyrusNajmabadi Feb 14, 2024
f0d8d5d
Better assert
CyrusNajmabadi Feb 14, 2024
90d4413
Tweak
CyrusNajmabadi Feb 14, 2024
d9cf134
tweak
CyrusNajmabadi Feb 14, 2024
85b289e
Merge branch 'frozenPartialSyntaxTree2' into frozenPartialOuter
CyrusNajmabadi Feb 16, 2024
0d618a7
simplify
CyrusNajmabadi Feb 16, 2024
b04c2c3
inline
CyrusNajmabadi Feb 16, 2024
b4acb04
simplify
CyrusNajmabadi Feb 16, 2024
907f5e1
Simplify
CyrusNajmabadi Feb 16, 2024
f63f083
Fix
CyrusNajmabadi Feb 16, 2024
1abfc99
Fix
CyrusNajmabadi Feb 16, 2024
33dcff4
Merge remote-tracking branch 'upstream/main' into frozenPartialOuter
CyrusNajmabadi Feb 16, 2024
18075f8
fast path
CyrusNajmabadi Feb 16, 2024
e1549e5
flip
CyrusNajmabadi Feb 16, 2024
196779b
vert
CyrusNajmabadi Feb 16, 2024
c612602
vert
CyrusNajmabadi Feb 16, 2024
3a242b5
vert
CyrusNajmabadi Feb 16, 2024
e093933
Add docs
CyrusNajmabadi Feb 16, 2024
c90b066
Merge branch 'addRemoveRefactoring' into frozenPartialOuter
CyrusNajmabadi Feb 16, 2024
8632ff0
move
CyrusNajmabadi Feb 16, 2024
a3a51af
Less work
CyrusNajmabadi Feb 16, 2024
77fc5b7
Merge branch 'addRemoveRefactoring' into frozenPartialOuter
CyrusNajmabadi Feb 16, 2024
752cfee
Merge remote-tracking branch 'upstream/main' into frozenPartialOuter
CyrusNajmabadi Feb 16, 2024
268acfe
Merge remote-tracking branch 'upstream/main' into frozenPartialOuter
CyrusNajmabadi Feb 20, 2024
06bcc73
Move back to asynclazy
CyrusNajmabadi Feb 20, 2024
5b5e9b5
Move back to asynclazy
CyrusNajmabadi Feb 20, 2024
ec4c22d
Be async
CyrusNajmabadi Feb 20, 2024
2d0994b
Check all document states
CyrusNajmabadi Feb 20, 2024
165c5c8
Break up
CyrusNajmabadi Feb 20, 2024
fd19961
Simplify
CyrusNajmabadi Feb 20, 2024
edcda05
Collection exprs
CyrusNajmabadi Feb 20, 2024
ef76245
comments
CyrusNajmabadi Feb 20, 2024
6044e88
filepaths not ids
CyrusNajmabadi Feb 20, 2024
b2f88b3
REvert
CyrusNajmabadi Feb 20, 2024
8818c50
Docs
CyrusNajmabadi Feb 20, 2024
29af9c5
Consistency
CyrusNajmabadi Feb 20, 2024
1ca0682
Use helpers
CyrusNajmabadi Feb 20, 2024
c87d1d7
Merge remote-tracking branch 'upstream/main' into frozenPartialOuter
CyrusNajmabadi Feb 21, 2024
cb4b4e8
Merge branch 'main' into frozenPartialOuter
CyrusNajmabadi Feb 21, 2024
01535b0
fix
CyrusNajmabadi Feb 21, 2024
67a1797
Fix
CyrusNajmabadi Feb 21, 2024
ccf6878
revert
CyrusNajmabadi Feb 21, 2024
d17921b
revert
CyrusNajmabadi Feb 21, 2024
35f714e
Docs
CyrusNajmabadi Feb 21, 2024
5c10a29
Revert
CyrusNajmabadi Feb 21, 2024
8a58525
Merge branch 'main' into frozenPartialOuter
CyrusNajmabadi Feb 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,24 @@ public partial class Solution
/// <summary>
/// Result of calling <see cref="WithFrozenPartialCompilationsAsync"/>.
/// </summary>
private AsyncLazy<Solution> CachedFrozenSolution { get; init; }
private readonly AsyncLazy<Solution> _cachedFrozenSolution;

/// <summary>
/// Mapping of DocumentId to the frozen solution we produced for it the last time we were queried. This
/// instance should be used as its own lock when reading or writing to it.
/// </summary>
private readonly Dictionary<DocumentId, AsyncLazy<Solution>> _documentIdToFrozenSolution = [];

private Solution(SolutionCompilationState compilationState)
private Solution(
SolutionCompilationState compilationState,
AsyncLazy<Solution>? cachedFrozenSolution = null)
{
_projectIdToProjectMap = [];
_compilationState = compilationState;

this.CachedFrozenSolution = AsyncLazy.Create(ComputeFrozenSolution);
_cachedFrozenSolution = cachedFrozenSolution ?? new AsyncLazy<Solution>(
c => Task.FromResult(ComputeFrozenSolution(c)),
c => ComputeFrozenSolution(c));
Comment on lines +51 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this use your other helper? If this is coming in a queued PR then feel free to resolve this comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do!

}

internal Solution(
Expand Down Expand Up @@ -1458,8 +1462,12 @@ public Solution WithAnalyzerConfigDocumentTextLoader(DocumentId documentId, Text
/// Returns a solution instance where every project is frozen at whatever current state it is in
/// </summary>
/// <param name="cancellationToken"></param>
internal Solution WithFrozenPartialCompilations(CancellationToken cancellationToken)
=> _cachedFrozenSolution.GetValue(cancellationToken);

/// <inheritdoc cref="WithFrozenPartialCompilations"/>
internal Task<Solution> WithFrozenPartialCompilationsAsync(CancellationToken cancellationToken)
=> this.CachedFrozenSolution.GetValueAsync(cancellationToken);
=> _cachedFrozenSolution.GetValueAsync(cancellationToken);

private Solution ComputeFrozenSolution(CancellationToken cancellationToken)
{
Expand All @@ -1468,11 +1476,11 @@ private Solution ComputeFrozenSolution(CancellationToken cancellationToken)
return this;

var newCompilationState = this.CompilationState.WithFrozenPartialCompilations(cancellationToken);
var frozenSolution = new Solution(newCompilationState)
{

var frozenSolution = new Solution(
newCompilationState,
// Set the frozen solution to be its own frozen solution. Freezing multiple times is a no-op.
CachedFrozenSolution = this.CachedFrozenSolution,
};
cachedFrozenSolution: _cachedFrozenSolution);

return frozenSolution;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,153 +186,6 @@ ImmutableList<TranslationAction> UpdatePendingTranslationActions(
}
}

public ICompilationTracker FreezePartialState(CancellationToken cancellationToken)
{
var state = this.ReadState();

// If we're already finalized then just return what we have, and with the frozen bit flipped so that
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this work got effectively inlined into this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the diff is awful. i recommend not looking at it inlined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. because the diff was so awful, i moved this method much lower. so it just shows as a full add.

// any future forks keep things frozen.
if (state is FinalCompilationTrackerState finalState)
{
// If we're finalized and already frozen, we can just use ourselves.
return finalState.IsFrozen
? this
: new CompilationTracker(this.ProjectState, finalState.WithIsFrozen(), this.SkeletonReferenceCache.Clone());
}

Contract.ThrowIfFalse(state is null or InProgressState);

GetPartialCompilationState(
state,
out var inProgressProject,
out var compilationWithoutGeneratedDocuments,
out var compilationWithGeneratedDocuments,
out var generatorInfo,
cancellationToken);

// Transition us to a frozen in progress state. With the best compilations up to this point, but no
// more pending actions, and with the frozen bit flipped so that any future forks keep things
// frozen.
var inProgressState = InProgressState.Create(
isFrozen: true,
compilationWithoutGeneratedDocuments,
generatorInfo,
compilationWithGeneratedDocuments,
pendingTranslationActions: []);

return new CompilationTracker(inProgressProject, inProgressState, this.SkeletonReferenceCache.Clone());
}

public ICompilationTracker FreezePartialStateWithDocument(
DocumentState docState,
CancellationToken cancellationToken)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no more freezing partial state with documents. instead, we just freeze the whole state, and then add/update the doc of interest back at the outer level

{
var state = this.ReadState();

// If we're already finalized, and no change has been made to this document. Then just return what we
// have (just with the frozen bit flipped so that any future forks keep things frozen).
if (state is FinalCompilationTrackerState finalState &&
this.ProjectState.DocumentStates.TryGetState(docState.Id, out var oldState) &&
oldState == docState)
{
// If we're finalized, already frozen and have the document being asked for, we can just use ourselves.
return finalState.IsFrozen
? this
: new CompilationTracker(this.ProjectState, finalState.WithIsFrozen(), this.SkeletonReferenceCache.Clone());
}

// Otherwise, we're not finalized, or the document has changed. We'll create an in-progress state
// to represent the new state of the project, with all the necessary translation steps to
// incorporate the new document.

GetPartialCompilationState(
state,
out var oldProjectState,
out var compilationWithoutGeneratedDocuments,
out var compilationWithGeneratedDocuments,
out var generatorInfo,
cancellationToken);

TranslationAction pendingAction = oldProjectState.DocumentStates.TryGetState(docState.Id, out oldState)
// The document had been previously parsed and it's there, so we can update it with our current
// state. Note if no compilation existed GetPartialCompilationState would have produced an empty
// one, and removed any documents, so inProgressProject.DocumentStates would have been empty
// originally.
? new TranslationAction.TouchDocumentAction(oldProjectState, oldProjectState.UpdateDocument(docState, contentChanged: true), oldState, docState)
// The document wasn't present in the original snapshot at all, and we just need to add the
// document.
: new TranslationAction.AddDocumentsAction(oldProjectState, oldProjectState.AddDocuments([docState]), [docState]);

// Transition us to a frozen in progress state. With the best compilations up to this point, and the
// pending actions we'll need to perform on it to get to the final state.
var inProgressState = InProgressState.Create(
isFrozen: true,
compilationWithoutGeneratedDocuments,
generatorInfo,
compilationWithGeneratedDocuments,
[pendingAction]);

return new CompilationTracker(pendingAction.NewProjectState, inProgressState, this.SkeletonReferenceCache.Clone());
}

/// <summary>
/// Tries to get the latest snapshot of the compilation without waiting for it to be fully built. This
/// method takes advantage of the progress side-effect produced during <see
/// cref="GetOrBuildFinalStateAsync"/>. It will either return the already built compilation, any in-progress
/// compilation or any known old compilation in that order of preference. The compilation state that is
/// returned will have a compilation that is retained so that it cannot disappear.
/// </summary>
private void GetPartialCompilationState(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole method got inlined.

CompilationTrackerState? state,
out ProjectState inProgressProject,
out Compilation compilationWithoutGeneratedDocuments,
out Compilation compilationWithGeneratedDocuments,
out CompilationTrackerGeneratorInfo generatorInfo,
CancellationToken cancellationToken)
{
if (state is null)
{
inProgressProject = this.ProjectState.RemoveAllDocuments();
generatorInfo = CompilationTrackerGeneratorInfo.Empty;

compilationWithoutGeneratedDocuments = this.CreateEmptyCompilation();
compilationWithGeneratedDocuments = compilationWithoutGeneratedDocuments;
}
else if (state is FinalCompilationTrackerState finalState)
{
inProgressProject = this.ProjectState;
generatorInfo = finalState.GeneratorInfo;

compilationWithoutGeneratedDocuments = finalState.CompilationWithoutGeneratedDocuments;
compilationWithGeneratedDocuments = finalState.FinalCompilationWithGeneratedDocuments;

}
else if (state is InProgressState inProgressState)
{
generatorInfo = inProgressState.GeneratorInfo;
inProgressProject = inProgressState is { PendingTranslationActions: [var translationAction, ..] }
? translationAction.OldProjectState
: this.ProjectState;

compilationWithoutGeneratedDocuments = inProgressState.CompilationWithoutGeneratedDocuments;

// Parse the generated documents in parallel.
Parallel.ForEach(
generatorInfo.Documents.States.Values,
new ParallelOptions { CancellationToken = cancellationToken },
state => state.GetSyntaxTree(cancellationToken));
cancellationToken.ThrowIfCancellationRequested();

// Retrieving the syntax trees will be free now that we computed them above.
compilationWithGeneratedDocuments = compilationWithoutGeneratedDocuments.AddSyntaxTrees(
generatorInfo.Documents.States.Values.Select(state => state.GetSyntaxTree(cancellationToken)));
}
else
{
throw ExceptionUtilities.UnexpectedValue(state.GetType());
}
}

/// <summary>
/// Gets the final compilation if it is available.
/// </summary>
Expand Down Expand Up @@ -780,6 +633,86 @@ private async Task<bool> HasSuccessfullyLoadedSlowAsync(
return finalState.HasSuccessfullyLoaded;
}

public ICompilationTracker FreezePartialState(CancellationToken cancellationToken)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's teh new method, in all its simplified and inlined glory.

{
var state = this.ReadState();

// If we're finalized and already frozen, we can just use ourselves.
if (state is FinalCompilationTrackerState { IsFrozen: true } finalState)
return this;

var projectState = state switch
{
// If we don't have an existing state, then transition to a project state without any data.
null => this.ProjectState.RemoveAllDocuments(),

FinalCompilationTrackerState => this.ProjectState,

// If we have an in progress state with no steps, then we're just at the current project state.
InProgressState { PendingTranslationActions: [] } => this.ProjectState,

// Otherwise, reset us to whatever state the InProgressState had currently transitioned to.
InProgressState inProgressState => inProgressState.PendingTranslationActions.First().OldProjectState,

_ => throw ExceptionUtilities.UnexpectedValue(state.GetType()),
};

var frozenState = GetFrozenCompilationState();
Contract.ThrowIfFalse(frozenState.IsFrozen);
return new CompilationTracker(projectState, frozenState, this.SkeletonReferenceCache.Clone());

CompilationTrackerState GetFrozenCompilationState()
{
if (state is FinalCompilationTrackerState finalState)
{
// Checked by caller.
Contract.ThrowIfTrue(finalState.IsFrozen);
// If we're already finalized then just return what we have, and with the frozen bit flipped so
// that any future forks keep things frozen.
return finalState.WithIsFrozen();
}

// Non-final state currently. Produce an in-progress-state containing the forked change. Note: we
// transition to in-progress-state here (and not final-state) as we still want to leverage all the
// final-state-transition logic contained in FinalizeCompilationAsync (for example, properly setting
// up all references).
if (state is null)
{
// We have no data at all. Create a frozen empty project/compilation to represent this state.
var compilationWithoutGeneratedDocuments = this.CreateEmptyCompilation();
var compilationWithGeneratedDocuments = compilationWithoutGeneratedDocuments;

return InProgressState.Create(
isFrozen: true,
compilationWithoutGeneratedDocuments,
CompilationTrackerGeneratorInfo.Empty,
compilationWithGeneratedDocuments,
pendingTranslationActions: []);
}
else if (state is InProgressState inProgressState)
{
// Grab whatever is in the in-progress-state so far, add any generated docs, and snap
// us to a frozen state with that information.
var generatorInfo = inProgressState.GeneratorInfo;

var compilationWithoutGeneratedDocuments = inProgressState.CompilationWithoutGeneratedDocuments;
var compilationWithGeneratedDocuments = compilationWithoutGeneratedDocuments.AddSyntaxTrees(
generatorInfo.Documents.States.Values.Select(state => state.GetSyntaxTree(cancellationToken)));

return InProgressState.Create(
isFrozen: true,
compilationWithoutGeneratedDocuments,
generatorInfo,
compilationWithGeneratedDocuments,
pendingTranslationActions: []);
}
else
{
throw ExceptionUtilities.UnexpectedValue(state.GetType());
}
}
}

public async ValueTask<TextDocumentStates<SourceGeneratedDocumentState>> GetSourceGeneratedDocumentStatesAsync(
SolutionCompilationState compilationState, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,6 @@ public ICompilationTracker FreezePartialState(CancellationToken cancellationToke
return new GeneratedFileReplacingCompilationTracker(UnderlyingTracker.FreezePartialState(cancellationToken), replacementDocumentStates);
}

public ICompilationTracker FreezePartialStateWithDocument(DocumentState docState, CancellationToken cancellationToken)
{
// Because we override SourceGeneratedDocument.WithFrozenPartialSemantics directly, we shouldn't be able to get here.
throw ExceptionUtilities.Unreachable();
}

public async Task<Compilation> GetCompilationAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken)
{
// Fast path if we've definitely already done this before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ private interface ICompilationTracker
Task<Compilation> GetCompilationAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken);

ICompilationTracker FreezePartialState(CancellationToken cancellationToken);
ICompilationTracker FreezePartialStateWithDocument(DocumentState docState, CancellationToken cancellationToken);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i def like ICompTracker getting simpler. And i think the semantics of 'Freeze the entire state' are much clearer than 'freeze the state, but ensure we have this docState in it'.


Task<VersionStamp> GetDependentVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken);
Task<VersionStamp> GetDependentSemanticVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken);
Expand Down
Loading