-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove the BranchId concept from teh workspace #57132
Changes from 4 commits
0199590
2715c69
d6001fc
6812155
d1f781c
9aae5f7
7782d4b
effa0bf
d83c686
fea7fc9
ff2362b
7155386
19748d1
57c7932
e3f9cbc
48c9652
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
using System.Diagnostics; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
@@ -48,16 +49,13 @@ public Factory() | |
|
||
private readonly object _gate = new(); | ||
|
||
/// <summary> | ||
/// Cached compile time solution corresponding to the <see cref="Workspace.PrimaryBranchId"/> | ||
/// </summary> | ||
private (int DesignTimeSolutionVersion, BranchId DesignTimeSolutionBranch, Solution CompileTimeSolution)? _primaryBranchCompileTimeCache; | ||
|
||
/// <summary> | ||
/// Cached compile time solution for a forked branch. This is used primarily by LSP cases where | ||
/// we fork the workspace solution and request diagnostics for the forked solution. | ||
/// </summary> | ||
private (int DesignTimeSolutionVersion, BranchId DesignTimeSolutionBranch, Solution CompileTimeSolution)? _forkedBranchCompileTimeCache; | ||
#if NETCOREAPP | ||
private readonly ConditionalWeakTable<Solution, Solution> _designTimeToCompileTimeSoution = new(); | ||
#else | ||
// Framework lacks both a .Clear() method. So for Framework we simulate that by just overwriting this with a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Food for thought for a different PR: Is this a potential worthy data structure for us to eventually have? Something like a |
||
// new instance. This happens under a lock, so everyone sees a consistent dictionary. | ||
private ConditionalWeakTable<Solution, Solution> _designTimeToCompileTimeSoution = new(); | ||
#endif | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sadness. Framework lacks a 'clear' method on CWT. |
||
|
||
public CompileTimeSolutionProvider(Workspace workspace) | ||
{ | ||
|
@@ -67,8 +65,11 @@ public CompileTimeSolutionProvider(Workspace workspace) | |
{ | ||
lock (_gate) | ||
{ | ||
_primaryBranchCompileTimeCache = null; | ||
_forkedBranchCompileTimeCache = null; | ||
#if NETCOREAPP | ||
_designTimeToCompileTimeSoution.Clear(); | ||
#else | ||
_designTimeToCompileTimeSoution = new(); | ||
#endif | ||
} | ||
} | ||
}; | ||
|
@@ -82,10 +83,8 @@ public Solution GetCompileTimeSolution(Solution designTimeSolution) | |
{ | ||
lock (_gate) | ||
{ | ||
var cachedCompileTimeSolution = GetCachedCompileTimeSolution(designTimeSolution); | ||
|
||
// Design time solution hasn't changed since we calculated the last compile-time solution: | ||
if (cachedCompileTimeSolution != null) | ||
if (_designTimeToCompileTimeSoution.TryGetValue(designTimeSolution, out var cachedCompileTimeSolution) && | ||
cachedCompileTimeSolution != null) | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
return cachedCompileTimeSolution; | ||
} | ||
|
@@ -123,42 +122,17 @@ public Solution GetCompileTimeSolution(Solution designTimeSolution) | |
.RemoveAnalyzerConfigDocuments(configIdsToRemove.ToImmutable()) | ||
.RemoveDocuments(documentIdsToRemove.ToImmutable()); | ||
|
||
UpdateCachedCompileTimeSolution(designTimeSolution, compileTimeSolution); | ||
#if NETCOREAPP | ||
_designTimeToCompileTimeSoution.AddOrUpdate(designTimeSolution, compileTimeSolution); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're under a lock here, is it possible for this to ever race with something else given we checked earlier? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nope. not possible. have rewritten with the understanding that this is a lock. |
||
#else | ||
_designTimeToCompileTimeSoution.Remove(designTimeSolution); | ||
_designTimeToCompileTimeSoution.Add(designTimeSolution, compileTimeSolution); | ||
#endif | ||
|
||
return compileTimeSolution; | ||
} | ||
} | ||
|
||
private Solution? GetCachedCompileTimeSolution(Solution designTimeSolution) | ||
{ | ||
// If the design time solution is for the primary branch, retrieve the last cached solution for it. | ||
// Otherwise this is a forked solution, so retrieve the last forked compile time solution we calculated. | ||
var cachedCompileTimeSolution = designTimeSolution.BranchId == _workspace.PrimaryBranchId ? _primaryBranchCompileTimeCache : _forkedBranchCompileTimeCache; | ||
|
||
// Verify that the design time solution has not changed since the last calculated compile time solution and that | ||
// the design time solution branch matches the branch of the design time solution we calculated the compile time solution for. | ||
if (cachedCompileTimeSolution != null | ||
&& designTimeSolution.WorkspaceVersion == cachedCompileTimeSolution.Value.DesignTimeSolutionVersion | ||
&& designTimeSolution.BranchId == cachedCompileTimeSolution.Value.DesignTimeSolutionBranch) | ||
{ | ||
return cachedCompileTimeSolution.Value.CompileTimeSolution; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private void UpdateCachedCompileTimeSolution(Solution designTimeSolution, Solution compileTimeSolution) | ||
{ | ||
if (designTimeSolution.BranchId == _workspace.PrimaryBranchId) | ||
{ | ||
_primaryBranchCompileTimeCache = (designTimeSolution.WorkspaceVersion, designTimeSolution.BranchId, compileTimeSolution); | ||
} | ||
else | ||
{ | ||
_forkedBranchCompileTimeCache = (designTimeSolution.WorkspaceVersion, designTimeSolution.BranchId, compileTimeSolution); | ||
} | ||
} | ||
|
||
// Copied from | ||
// https://github.com/dotnet/sdk/blob/main/src/RazorSdk/SourceGenerators/RazorSourceGenerator.Helpers.cs#L32 | ||
private static string GetIdentifierFromPath(string filePath) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,8 +54,6 @@ public static async Task PrecalculateAsync(Document document, CancellationToken | |
|
||
using (Logger.LogBlock(FunctionId.SyntaxTreeIndex_Precalculate, cancellationToken)) | ||
{ | ||
Debug.Assert(document.IsFromPrimaryBranch()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not an unreasonable assert. we would prefer some assurances that our precalculation step is only running on the non-forked solution of some workspace. However, if that turns out to not be the case, it's not the end of the world as this ensure subsystem is checksum'ed based anyways. |
||
|
||
var checksum = await GetChecksumAsync(document, cancellationToken).ConfigureAwait(false); | ||
|
||
// Check if we've already created and persisted the index for this document. | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,15 +17,12 @@ namespace Microsoft.CodeAnalysis | |
internal class MetadataOnlyReference | ||
{ | ||
// version based cache | ||
private static readonly ConditionalWeakTable<BranchId, ConditionalWeakTable<ProjectId, MetadataOnlyReferenceSet>> s_cache | ||
= new(); | ||
private static readonly ConditionalWeakTable<SolutionState, ConditionalWeakTable<ProjectId, MetadataOnlyReferenceSet>> s_cache = new(); | ||
|
||
// snapshot based cache | ||
private static readonly ConditionalWeakTable<Compilation, MetadataOnlyReferenceSet> s_snapshotCache | ||
= new(); | ||
private static readonly ConditionalWeakTable<Compilation, MetadataOnlyReferenceSet> s_snapshotCache = new(); | ||
|
||
private static readonly ConditionalWeakTable<BranchId, ConditionalWeakTable<ProjectId, MetadataOnlyReferenceSet>>.CreateValueCallback s_createReferenceSetMap = | ||
_ => new ConditionalWeakTable<ProjectId, MetadataOnlyReferenceSet>(); | ||
private static readonly ConditionalWeakTable<SolutionState, ConditionalWeakTable<ProjectId, MetadataOnlyReferenceSet>>.CreateValueCallback s_createReferenceSetMap = _ => new(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. previous logic attempted to use BranchId here for reasons i honestly can't understand. Havin this be keyed by solution woudl be broken as we wouldn't be able to cache this across simple solution changes. However, we would like to be able to cache this for a particular lifetime of a solution as long as possible. Note: critically, this is not jsut an arbitrary "i will cache a projectid to a metadatarefset forever". instead, that set holds onto things like the 'dependent semantic version' of the project in that solution. So the references are only valid as long as no top level semantic information changed. |
||
|
||
internal static MetadataReference GetOrBuildReference( | ||
SolutionState solution, | ||
|
@@ -64,7 +61,7 @@ internal static MetadataReference GetOrBuildReference( | |
// okay, proceed with whatever image we have | ||
|
||
// now, remove existing set | ||
var mapFromBranch = s_cache.GetValue(solution.BranchId, s_createReferenceSetMap); | ||
var mapFromBranch = s_cache.GetValue(solution, s_createReferenceSetMap); | ||
mapFromBranch.Remove(projectReference.ProjectId); | ||
|
||
// create new one | ||
|
@@ -106,31 +103,22 @@ internal static bool TryGetReference( | |
// okay, now use version based cache that can live multiple compilation as long as there is no semantic changes. | ||
|
||
// get one for the branch | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (TryGetReferenceFromBranch(solution.BranchId, projectReference, finalOrDeclarationCompilation, version, out reference)) | ||
if (TryGetReferenceFromBranch(solution, projectReference, finalOrDeclarationCompilation, version, out reference)) | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me if this will ever trigger now if the first map of compilation -> reference didn't have one, then I don't think we'd ever end up with a case where this other map has one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we still need this map, should it instead be keyed on ProjectId or something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Talked with Jason offline. This entire type was rejiggered. It's now instance state on compilatino-tracker. Allowing it to both share data across forks, but also not have cahnges to branched solutions unintentionally leak backward to affect the main solution. |
||
{ | ||
solution.Workspace.LogTestMessage($"Found already cached metadata for the branch and version {version}"); | ||
return true; | ||
} | ||
|
||
// see whether we can use primary branch one | ||
var primaryBranchId = solution.Workspace.PrimaryBranchId; | ||
if (solution.BranchId != primaryBranchId && | ||
TryGetReferenceFromBranch(primaryBranchId, projectReference, finalOrDeclarationCompilation, version, out reference)) | ||
{ | ||
solution.Workspace.LogTestMessage($"Found already cached metadata for the primary branch and version {version}"); | ||
return true; | ||
} | ||
|
||
// noop, we don't have any | ||
reference = null; | ||
return false; | ||
} | ||
|
||
private static bool TryGetReferenceFromBranch( | ||
BranchId branchId, ProjectReference projectReference, Compilation finalOrDeclarationCompilation, VersionStamp version, out MetadataReference reference) | ||
SolutionState state, ProjectReference projectReference, Compilation finalOrDeclarationCompilation, VersionStamp version, out MetadataReference reference) | ||
{ | ||
// get map for the branch | ||
var mapFromBranch = s_cache.GetValue(branchId, s_createReferenceSetMap); | ||
var mapFromBranch = s_cache.GetValue(state, s_createReferenceSetMap); | ||
// if we have one, return it | ||
if (mapFromBranch.TryGetValue(projectReference.ProjectId, out var referenceSet) && | ||
(version == VersionStamp.Default || referenceSet.Version == version)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NTaylorMullen instead of a version+branch pointing to the CompileTimeSolution, i instead just use a CWT mapping teh actual designTimeSolution to the compileTimeSolution. As a CWT it will be released whenever people stop holding onto this solution.
Note: i checked with @dibarbet and for LSP as long as edits aren't coming in, we'll have the same solution instance, so thsi CWT should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really appreciate you checking the LSP side of this too ❤️