Skip to content

Commit

Permalink
Merge pull request #60465 from CyrusNajmabadi/projectConeSync
Browse files Browse the repository at this point in the history
Change project-cone-sync to not dump projects outside of the cone.
  • Loading branch information
CyrusNajmabadi authored Apr 1, 2022
2 parents 7359848 + 07a51c4 commit fc1e0ed
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 195 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public async Task TestAssetSynchronization()
// build checksum
await solution.State.GetChecksumAsync(CancellationToken.None);

var map = await solution.GetAssetMapAsync(includeProjectCones: true, CancellationToken.None);
var map = await solution.GetAssetMapAsync(CancellationToken.None);

using var remoteWorkspace = CreateRemoteWorkspace();

Expand Down Expand Up @@ -101,7 +101,7 @@ public async Task TestSolutionSynchronization()
// build checksum
await solution.State.GetChecksumAsync(CancellationToken.None);

var map = await solution.GetAssetMapAsync(includeProjectCones: true, CancellationToken.None);
var map = await solution.GetAssetMapAsync(CancellationToken.None);

using var remoteWorkspace = CreateRemoteWorkspace();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public async Task TestRemoteHostSynchronize()
var solution = workspace.CurrentSolution;

await UpdatePrimaryWorkspace(client, solution);
await VerifyAssetStorageAsync(client, solution, includeProjectCones: true);
await VerifyAssetStorageAsync(client, solution);

var remoteWorkpace = client.GetRemoteWorkspace();

Expand All @@ -80,7 +80,7 @@ public async Task TestRemoteHostTextSynchronize()

// sync base solution
await UpdatePrimaryWorkspace(client, solution);
await VerifyAssetStorageAsync(client, solution, includeProjectCones: true);
await VerifyAssetStorageAsync(client, solution);

// get basic info
var oldDocument = solution.Projects.First().Documents.First();
Expand Down Expand Up @@ -186,7 +186,7 @@ private static async Task<AssetProvider> GetAssetProviderAsync(Workspace workspa
await solution.State.GetChecksumAsync(CancellationToken.None);

map ??= new Dictionary<Checksum, object>();
await solution.AppendAssetMapAsync(includeProjectCones: true, map, CancellationToken.None);
await solution.AppendAssetMapAsync(map, CancellationToken.None);

var sessionId = 0;
var storage = new SolutionAssetCache();
Expand Down Expand Up @@ -265,7 +265,7 @@ public async Task TestUnknownProject()
var remoteWorkspace = client.GetRemoteWorkspace();

await UpdatePrimaryWorkspace(client, solution);
await VerifyAssetStorageAsync(client, solution, includeProjectCones: true);
await VerifyAssetStorageAsync(client, solution);

// Only C# and VB projects are supported in Remote workspace.
// See "RemoteSupportedLanguages.IsSupported"
Expand Down Expand Up @@ -300,7 +300,7 @@ public async Task TestRemoteHostSynchronizeIncrementalUpdate(bool applyInBatch)

// verify initial setup
await UpdatePrimaryWorkspace(client, solution);
await VerifyAssetStorageAsync(client, solution, includeProjectCones: false);
await VerifyAssetStorageAsync(client, solution);

solution = WithChangedOptionsFromRemoteWorkspace(solution, remoteWorkspace);

Expand Down Expand Up @@ -346,7 +346,7 @@ public async Task TestIncrementalUpdateHandlesReferenceReversal()

// verify initial setup
await UpdatePrimaryWorkspace(client, solution);
await VerifyAssetStorageAsync(client, solution, includeProjectCones: false);
await VerifyAssetStorageAsync(client, solution);

Assert.Equal(
await solution.State.GetChecksumAsync(CancellationToken.None),
Expand Down Expand Up @@ -492,9 +492,9 @@ private static void VerifyStates(Solution solution1, Solution solution2, string
}
}

private static async Task VerifyAssetStorageAsync(InProcRemoteHostClient client, Solution solution, bool includeProjectCones)
private static async Task VerifyAssetStorageAsync(InProcRemoteHostClient client, Solution solution)
{
var map = await solution.GetAssetMapAsync(includeProjectCones, CancellationToken.None);
var map = await solution.GetAssetMapAsync(CancellationToken.None);

var storage = client.TestData.WorkspaceManager.SolutionAssetCache;

Expand Down
162 changes: 89 additions & 73 deletions src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs

Large diffs are not rendered by default.

11 changes: 1 addition & 10 deletions src/Workspaces/Core/Portable/Remote/PinnedSolutionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,16 @@ internal sealed class PinnedSolutionInfo
[DataMember(Order = 3)]
public readonly Checksum SolutionChecksum;

/// <summary>
/// An optional project that we are pinning information for. This is used for features that only need
/// information for a project (and its dependencies) and not the entire solution.
/// </summary>
[DataMember(Order = 4)]
public readonly ProjectId? ProjectId;

public PinnedSolutionInfo(
int scopeId,
bool fromPrimaryBranch,
int workspaceVersion,
Checksum solutionChecksum,
ProjectId? projectId)
Checksum solutionChecksum)
{
ScopeId = scopeId;
FromPrimaryBranch = fromPrimaryBranch;
WorkspaceVersion = workspaceVersion;
SolutionChecksum = solutionChecksum;
ProjectId = projectId;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,29 @@ private async Task<SolutionStateChecksums> ComputeChecksumsAsync(
{
using (Logger.LogBlock(FunctionId.SolutionState_ComputeChecksumsAsync, FilePath, cancellationToken))
{
// get states by id order to have deterministic checksum. Limit to the requested set of projects
// if applicable.
// get states by id order to have deterministic checksum. Limit expensive computation to the
// requested set of projects if applicable.
var orderedProjectIds = ChecksumCache.GetOrCreate(ProjectIds, _ => ProjectIds.OrderBy(id => id.Id).ToImmutableArray());
var projectChecksumTasks = orderedProjectIds.Where(id => projectsToInclude == null || projectsToInclude.Contains(id))
.Select(id => ProjectStates[id])
.Where(s => RemoteSupportedLanguages.IsSupported(s.Language))
.Select(s => s.GetChecksumAsync(cancellationToken))
.ToArray();
var projectChecksumTasks = orderedProjectIds
.Select(id => (state: ProjectStates[id], mustCompute: projectsToInclude == null || projectsToInclude.Contains(id)))
.Where(t => RemoteSupportedLanguages.IsSupported(t.state.Language))
.Select(async t =>
{
// if it's a project that's specifically in the sync'ed cone, include this checksum so that
// this project definitely syncs over.
if (t.mustCompute)
return await t.state.GetChecksumAsync(cancellationToken).ConfigureAwait(false);

// If it's a project that is not in the cone, still try to get the latest checksum for it if
// we have it. That way we don't send over a checksum *without* that project, causing the
// OOP side to throw that project away (along with all the compilation info stored with it).
if (t.state.TryGetStateChecksums(out var stateChecksums))
return stateChecksums.Checksum;

// We have never computed the checksum for this project. Don't send anything for it.
return null;
})
.ToArray();

var serializer = _solutionServices.Workspace.Services.GetRequiredService<ISerializerService>();
var attributesChecksum = serializer.CreateChecksum(SolutionAttributes, cancellationToken);
Expand All @@ -146,7 +161,13 @@ private async Task<SolutionStateChecksums> ComputeChecksumsAsync(
_ => new ChecksumCollection(AnalyzerReferences.Select(r => serializer.CreateChecksum(r, cancellationToken)).ToArray()));

var projectChecksums = await Task.WhenAll(projectChecksumTasks).ConfigureAwait(false);
return new SolutionStateChecksums(attributesChecksum, optionsChecksum, new ChecksumCollection(projectChecksums), analyzerReferenceChecksums, frozenSourceGeneratedDocumentIdentityChecksum, frozenSourceGeneratedDocumentTextChecksum);
return new SolutionStateChecksums(
attributesChecksum,
optionsChecksum,
new ChecksumCollection(projectChecksums.WhereNotNull().ToArray()),
analyzerReferenceChecksums,
frozenSourceGeneratedDocumentIdentityChecksum,
frozenSourceGeneratedDocumentTextChecksum);
}
}
catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
Expand Down
3 changes: 1 addition & 2 deletions src/Workspaces/Remote/Core/SolutionAssetStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ private async ValueTask<Scope> StoreAssetsAsync(Solution solution, ProjectId? pr
id,
fromPrimaryBranch: solutionState.BranchId == solutionState.Workspace.PrimaryBranchId,
solutionState.WorkspaceVersion,
solutionChecksum,
projectId);
solutionChecksum);

Contract.ThrowIfFalse(_solutionStates.TryAdd(id, (solutionState, context)));

Expand Down
73 changes: 1 addition & 72 deletions src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ internal sealed partial class RemoteWorkspace : Workspace
/// </summary>
private volatile Tuple<Checksum, Solution>? _lastRequestedSolutionWithChecksum;

/// <summary>
/// The last partial solution snapshot corresponding to a particular project-cone requested by a service.
/// </summary>
private readonly ConcurrentDictionary<ProjectId, StrongBox<(Checksum checksum, Solution solution)>> _lastRequestedProjectIdToSolutionWithChecksum = new();

/// <summary>
/// Guards setting current workspace solution.
/// </summary>
Expand Down Expand Up @@ -93,20 +88,12 @@ public async Task UpdatePrimaryBranchSolutionAsync(AssetProvider assetProvider,
}
}

public ValueTask<Solution> GetSolutionAsync(
public async ValueTask<Solution> GetSolutionAsync(
AssetProvider assetProvider,
Checksum solutionChecksum,
bool fromPrimaryBranch,
int workspaceVersion,
ProjectId? projectId,
CancellationToken cancellationToken)
{
return projectId == null
? GetFullSolutionAsync(assetProvider, solutionChecksum, fromPrimaryBranch, workspaceVersion, cancellationToken)
: GetProjectSubsetSolutionAsync(assetProvider, solutionChecksum, projectId, cancellationToken);
}

private async ValueTask<Solution> GetFullSolutionAsync(AssetProvider assetProvider, Checksum solutionChecksum, bool fromPrimaryBranch, int workspaceVersion, CancellationToken cancellationToken)
{
var availableSolution = TryGetAvailableSolution(solutionChecksum);
if (availableSolution != null)
Expand Down Expand Up @@ -226,64 +213,6 @@ private async Task<Solution> CreateFullSolution_NoLockAsync(
return null;
}

private ValueTask<Solution> GetProjectSubsetSolutionAsync(
AssetProvider assetProvider,
Checksum solutionChecksum,
ProjectId projectId,
CancellationToken cancellationToken)
{
// Attempt to just read without incurring any other costs.
if (_lastRequestedProjectIdToSolutionWithChecksum.TryGetValue(projectId, out var box) &&
box.Value.checksum == solutionChecksum)
{
return new(box.Value.Item2);
}

return GetProjectSubsetSolutionSlowAsync(box?.Value.solution ?? CurrentSolution, assetProvider, solutionChecksum, projectId, cancellationToken);

async ValueTask<Solution> GetProjectSubsetSolutionSlowAsync(
Solution baseSolution,
AssetProvider assetProvider,
Checksum solutionChecksum,
ProjectId projectId,
CancellationToken cancellationToken)
{
try
{
var updater = new SolutionCreator(Services.HostServices, assetProvider, baseSolution, cancellationToken);

// check whether solution is update to the given base solution
Solution result;
if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false))
{
// create updated solution off the baseSolution
result = await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false);
}
else
{
// we need new solution. bulk sync all asset for the solution first.
await assetProvider.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);

// get new solution info and options
var (solutionInfo, options) = await assetProvider.CreateSolutionInfoAndOptionsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);

var workspace = new TemporaryWorkspace(Services.HostServices, WorkspaceKind.RemoteTemporaryWorkspace, solutionInfo, options);
result = workspace.CurrentSolution;
}

// Cache the result of our computation. Note: this is simply a last caller wins strategy. However,
// in general this should be fine as we're primarily storing this to make future calls to synchronize
// this project cone fast.
_lastRequestedProjectIdToSolutionWithChecksum[projectId] = new((solutionChecksum, result));
return result;
}
catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
}
}

/// <summary>
/// Adds an entire solution to the workspace, replacing any existing solution.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public ValueTask<Solution> GetSolutionAsync(ServiceBrokerClient client, PinnedSo
var assetSource = new SolutionAssetSource(client);
var workspace = GetWorkspace();
var assetProvider = workspace.CreateAssetProvider(solutionInfo, SolutionAssetCache, assetSource);
return workspace.GetSolutionAsync(assetProvider, solutionInfo.SolutionChecksum, solutionInfo.FromPrimaryBranch, solutionInfo.WorkspaceVersion, solutionInfo.ProjectId, cancellationToken);
return workspace.GetSolutionAsync(assetProvider, solutionInfo.SolutionChecksum, solutionInfo.FromPrimaryBranch, solutionInfo.WorkspaceVersion, cancellationToken);
}

private sealed class SimpleAssemblyLoader : IAssemblyLoader
Expand Down
Loading

0 comments on commit fc1e0ed

Please sign in to comment.