提交 9c6b6181 编写于 作者: H Heejae Chang 提交者: GitHub

Merge pull request #19311 from heejaechang/BulkSync

Made OOP to handle initial solution case better and made it to have better perf.
......@@ -65,7 +65,7 @@ protected override async Task<Session> TryCreateServiceSessionAsync(string servi
// this is the back channel the system uses to move data between VS and remote host
var snapshotStream = getSnapshotAsync.Value == null ? null : await _inprocServices.RequestServiceAsync(WellKnownServiceHubServices.SnapshotService, cancellationToken).ConfigureAwait(false);
// get stream from service hub to communicate service specific information
// get stream from service hub to communicate service specific information
// this is what consumer actually use to communicate information
var serviceStream = await _inprocServices.RequestServiceAsync(serviceName, cancellationToken).ConfigureAwait(false);
......
......@@ -42,12 +42,12 @@ public async Task InvokeAsync(string targetName, params object[] arguments)
{
await _rpc.InvokeAsync(targetName, arguments).ConfigureAwait(false);
}
catch (ObjectDisposedException)
catch
{
// object disposed exception can be thrown from StreamJsonRpc if JsonRpc is disposed in the middle of read/write.
// the way we added cancellation support to the JsonRpc which doesn't support cancellation natively
// can cause this exception to happen. newer version supports cancellation token natively, but
// we can't use it now, so we will catch object disposed exception and check cancellation token
// any exception can be thrown from StreamJsonRpc if JsonRpc is disposed in the middle of read/write.
// until we move to newly added cancellation support in JsonRpc, we will catch exception and translate to
// cancellation exception here. if any exception is thrown unrelated to cancellation, then we will rethrow
// the exception
_cancellationToken.ThrowIfCancellationRequested();
throw;
}
......@@ -61,12 +61,12 @@ public async Task<T> InvokeAsync<T>(string targetName, params object[] arguments
{
return await _rpc.InvokeAsync<T>(targetName, arguments).ConfigureAwait(false);
}
catch (ObjectDisposedException)
catch
{
// object disposed exception can be thrown from StreamJsonRpc if JsonRpc is disposed in the middle of read/write.
// the way we added cancellation support to the JsonRpc which doesn't support cancellation natively
// can cause this exception to happen. newer version supports cancellation token natively, but
// we can't use it now, so we will catch object disposed exception and check cancellation token
// any exception can be thrown from StreamJsonRpc if JsonRpc is disposed in the middle of read/write.
// until we move to newly added cancellation support in JsonRpc, we will catch exception and translate to
// cancellation exception here. if any exception is thrown unrelated to cancellation, then we will rethrow
// the exception
_cancellationToken.ThrowIfCancellationRequested();
throw;
}
......
......@@ -86,13 +86,17 @@ internal class JsonRpcSession : RemoteHostClient.Session
private async Task InitializeAsync()
{
// all roslyn remote service must based on ServiceHubServiceBase which implements Initialize method
// All roslyn remote service must based on ServiceHubServiceBase which implements Initialize method
// This will set this session's solution and whether that solution is for primary branch or not
var primaryBranch = PinnedScopeOpt?.ForPrimaryBranch ?? false;
var solutionChecksum = PinnedScopeOpt?.SolutionChecksum;
if (_snapshotClientOpt != null)
{
await _snapshotClientOpt.InvokeAsync(WellKnownServiceHubServices.ServiceHubServiceBase_Initialize, _currentSessionId, PinnedScopeOpt.SolutionChecksum).ConfigureAwait(false);
await _snapshotClientOpt.InvokeAsync(WellKnownServiceHubServices.ServiceHubServiceBase_Initialize, _currentSessionId, primaryBranch, solutionChecksum).ConfigureAwait(false);
}
await _serviceClient.InvokeAsync(WellKnownServiceHubServices.ServiceHubServiceBase_Initialize, _currentSessionId, PinnedScopeOpt?.SolutionChecksum).ConfigureAwait(false);
await _serviceClient.InvokeAsync(WellKnownServiceHubServices.ServiceHubServiceBase_Initialize, _currentSessionId, primaryBranch, solutionChecksum).ConfigureAwait(false);
}
public override Task InvokeAsync(string targetName, params object[] arguments)
......
......@@ -25,6 +25,18 @@ public void TestChecksum()
VerifyJsonSerialization(new Checksum(Guid.NewGuid().ToByteArray()));
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public void TestChecksum_Null()
{
VerifyJsonSerialization<Checksum>(null);
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public void TestChecksumNull()
{
VerifyJsonSerialization(Checksum.Null);
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public void TestSolutionId()
{
......
......@@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.DebugUtil;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Roslyn.VisualStudio.Next.UnitTests.Mocks;
......
......@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.DebugUtil;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.TodoComments;
using Roslyn.Test.Utilities;
......
......@@ -7,11 +7,10 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.DebugUtil;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.Remote;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Roslyn.VisualStudio.Next.UnitTests.Mocks;
......@@ -38,6 +37,36 @@ public async Task TestCreation()
}
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public async Task TestGetSolutionWithPrimaryFlag()
{
var code1 = @"class Test1 { void Method() { } }";
using (var workspace = TestWorkspace.CreateCSharp(code1))
{
var solution = workspace.CurrentSolution;
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var service = await GetSolutionServiceAsync(solution);
var synched = await service.GetSolutionAsync(solutionChecksum, CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
Assert.True(synched.Workspace is TemporaryWorkspace);
}
var code2 = @"class Test2 { void Method() { } }";
using (var workspace = TestWorkspace.CreateCSharp(code2))
{
var solution = workspace.CurrentSolution;
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var service = (ISolutionController)await GetSolutionServiceAsync(solution);
var synched = await service.GetSolutionAsync(solutionChecksum, fromPrimaryBranch: true, cancellationToken: CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
Assert.True(synched.Workspace is RemoteWorkspace);
}
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public async Task TestStrongNameProvider()
{
......@@ -249,7 +278,7 @@ public async Task TestRemoteWorkspaceSolutionCrawler()
// update primary workspace
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
await service.UpdatePrimaryWorkspaceAsync(solutionChecksum, CancellationToken.None);
await ((ISolutionController)service).UpdatePrimaryWorkspaceAsync(solutionChecksum, CancellationToken.None);
// get solution in remote host
var oopSolution = await service.GetSolutionAsync(solutionChecksum, CancellationToken.None);
......@@ -297,7 +326,7 @@ private static async Task VerifySolutionUpdate(TestWorkspace workspace, Func<Sol
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
// update primary workspace
await service.UpdatePrimaryWorkspaceAsync(solutionChecksum, CancellationToken.None);
await ((ISolutionController)service).UpdatePrimaryWorkspaceAsync(solutionChecksum, CancellationToken.None);
var first = await service.GetSolutionAsync(solutionChecksum, CancellationToken.None);
Assert.Equal(solutionChecksum, await first.State.GetChecksumAsync(CancellationToken.None));
......@@ -315,7 +344,7 @@ private static async Task VerifySolutionUpdate(TestWorkspace workspace, Func<Sol
Assert.False(object.ReferenceEquals(PrimaryWorkspace.Workspace.PrimaryBranchId, second.BranchId));
// do same once updating primary workspace
await service.UpdatePrimaryWorkspaceAsync(newSolutionChecksum, CancellationToken.None);
await ((ISolutionController)service).UpdatePrimaryWorkspaceAsync(newSolutionChecksum, CancellationToken.None);
var third = await service.GetSolutionAsync(newSolutionChecksum, CancellationToken.None);
Assert.Equal(newSolutionChecksum, await third.State.GetChecksumAsync(CancellationToken.None));
......
......@@ -263,7 +263,6 @@
<Compile Include="Mocks\TestOptionSet.cs" />
<Compile Include="Remote\JsonConverterTests.cs" />
<Compile Include="Remote\RemoteHostClientServiceFactoryTests.cs" />
<Compile Include="TestUtils.cs" />
<Compile Include="Services\SolutionServiceTests.cs" />
<Compile Include="Services\AssetStorageTests.cs" />
<Compile Include="Services\AssetServiceTests.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
......@@ -156,13 +157,21 @@ public RemotableData GetRemotableData(PinnedRemotableDataScope scope, Checksum c
public void RegisterSnapshot(PinnedRemotableDataScope snapshot, AssetStorages.Storage storage)
{
// duplicates are not allowed, there can be multiple snapshots to same solution, so no ref counting.
Contract.ThrowIfFalse(_storages.TryAdd(snapshot, storage));
if (!_storages.TryAdd(snapshot, storage))
{
// this should make failure more explicit
FailFast.OnFatalException(new Exception("who is adding same snapshot?"));
}
}
public void UnregisterSnapshot(PinnedRemotableDataScope snapshot)
{
// calling it multiple times for same snapshot is not allowed.
Contract.ThrowIfFalse(_storages.TryRemove(snapshot, out var dummy));
if (!_storages.TryRemove(snapshot, out var dummy))
{
// this should make failure more explicit
FailFast.OnFatalException(new Exception("who is removing same snapshot?"));
}
}
private IEnumerable<Storage> GetStorages(PinnedRemotableDataScope scope)
......
......@@ -25,6 +25,8 @@ internal sealed class PinnedRemotableDataScope : IDisposable
AssetStorages.Storage storage,
Checksum solutionChecksum)
{
Contract.ThrowIfNull(solutionChecksum);
_storages = storages;
_storage = storage;
......@@ -33,6 +35,14 @@ internal sealed class PinnedRemotableDataScope : IDisposable
_storages.RegisterSnapshot(this, storage);
}
/// <summary>
/// This indicates whether this scope is for primary branch or not (not forked solution)
///
/// Features like OOP will use this flag to see whether caching information related to this solution
/// can benefit other requests or not
/// </summary>
public bool ForPrimaryBranch => _storage.SolutionState.BranchId == Workspace.PrimaryBranchId;
public Workspace Workspace => _storage.SolutionState.Workspace;
/// <summary>
......
......@@ -327,51 +327,70 @@ internal enum FunctionId
BKTree_ExceptionInCacheRead,
IntellisenseBuild_Failed,
FileTextLoader_FileLengthThresholdExceeded,
// Generic performance measurement action IDs
MeasurePerformance_StartAction,
MeasurePerformance_StopAction,
RemoteHostClientService_AddGlobalAssetsAsync,
RemoteHostClientService_RemoveGlobalAssets,
RemoteHostClientService_Enabled,
ServiceHubRemoteHostClient_CreateAsync,
SolutionSynchronizationServiceFactory_CreatePinnedRemotableDataScopeAsync,
PinnedRemotableDataScope_GetRemotableData,
Serializer_CreateChecksum,
Serializer_Serialize,
Serializer_Deserialize,
CodeAnalysisService_CalculateDiagnosticsAsync,
CodeAnalysisService_SerializeDiagnosticResultAsync,
AssetStorage_CleanAssets,
AssetService_GetAssetAsync,
SnapshotService_RequestAssetAsync,
CompilationService_GetCompilationAsync,
RemoteHostService_SynchronizePrimaryWorkspaceAsync,
RemoteHostService_SynchronizeGlobalAssetsAsync,
AssetStorage_TryGetAsset,
AssetService_SynchronizeAssetsAsync,
AssetService_SynchronizeSolutionAssetsAsync,
CodeAnalysisService_GetReferenceCountAsync,
CodeAnalysisService_FindReferenceLocationsAsync,
CodeAnalysisService_FindReferenceMethodsAsync,
CodeAnalysisService_GetFullyQualifiedName,
SolutionChecksumUpdater_SynchronizePrimaryWorkspace,
SolutionState_ComputeChecksumsAsync,
ProjectState_ComputeChecksumsAsync,
DocumentState_ComputeChecksumsAsync,
JsonRpcSession_RequestAssetAsync,
SolutionSynchronizationService_GetRemotableData,
AssetService_SynchronizeProjectAssetsAsync,
FileTextLoader_FileLengthThresholdExceeded,
CodeAnalysisService_GetTodoCommentsAsync,
CodeAnalysisService_GetDesignerAttributesAsync,
ServiceHubRemoteHostClient_CreateAsync,
PinnedRemotableDataScope_GetRemotableData,
RemoteHost_Connect,
RemoteHost_Disconnect,
CodeAnalysisService_GetTodoCommentsAsync,
RemoteHostClientService_AddGlobalAssetsAsync,
RemoteHostClientService_RemoveGlobalAssets,
RemoteHostClientService_Enabled,
RemoteHostClientService_Restarted,
RemoteHostService_SynchronizePrimaryWorkspaceAsync,
RemoteHostService_SynchronizeGlobalAssetsAsync,
AssetStorage_CleanAssets,
AssetStorage_TryGetAsset,
AssetService_GetAssetAsync,
AssetService_SynchronizeAssetsAsync,
AssetService_SynchronizeSolutionAssetsAsync,
AssetService_SynchronizeProjectAssetsAsync,
CodeLens_GetReferenceCountAsync,
CodeLens_FindReferenceLocationsAsync,
CodeLens_FindReferenceMethodsAsync,
CodeLens_GetFullyQualifiedName,
RemoteHostClientService_Restarted,
CodeAnalysisService_GetDesignerAttributesAsync,
Extension_InfoBar
SolutionState_ComputeChecksumsAsync,
ProjectState_ComputeChecksumsAsync,
DocumentState_ComputeChecksumsAsync,
SolutionSynchronizationService_GetRemotableData,
SolutionSynchronizationServiceFactory_CreatePinnedRemotableDataScopeAsync,
SolutionChecksumUpdater_SynchronizePrimaryWorkspace,
JsonRpcSession_RequestAssetAsync,
SolutionService_GetSolutionAsync,
SolutionService_UpdatePrimaryWorkspaceAsync,
SnapshotService_RequestAssetAsync,
CompilationService_GetCompilationAsync,
SolutionCreator_AssetDifferences,
Extension_InfoBar,
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Versions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
internal partial class TextDocumentState
{
private const string TextChecksum = nameof(TextChecksum);
private const string SerializationFormat = "1";
public bool TryGetStateChecksums(out DocumentStateChecksums stateChecksums)
{
return _lazyChecksums.TryGetValue(out stateChecksums);
......@@ -42,85 +34,10 @@ private async Task<DocumentStateChecksums> ComputeChecksumsAsync(CancellationTok
var serializer = new Serializer(solutionServices.Workspace);
var infoChecksum = serializer.CreateChecksum(Info.Attributes, cancellationToken);
var textChecksum = await GetTextChecksumAsync(serializer, await textAndVersionTask.ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
var textChecksum = serializer.CreateChecksum((await textAndVersionTask.ConfigureAwait(false)).Text, cancellationToken);
return new DocumentStateChecksums(infoChecksum, textChecksum);
}
}
private async Task<Checksum> GetTextChecksumAsync(Serializer serializer, TextAndVersion textAndVersion, CancellationToken cancellationToken)
{
// calculating checksum for source text is one of most expansive checksum calculation we need to do.
// this should let us get around it if possible
var solution = solutionServices.Workspace.CurrentSolution;
var document = solution.GetDocument(Id);
if (document == null)
{
// can't use persistent service since it is based on solution objects
return serializer.CreateChecksum(textAndVersion.Text, cancellationToken);
}
var storage = solution.Workspace.Services.GetService<IPersistentStorageService>()?.GetStorage(solution);
if (storage == null)
{
// persistent service not available
return serializer.CreateChecksum(textAndVersion.Text, cancellationToken);
}
try
{
using (var stream = await storage.ReadStreamAsync(document, TextChecksum, cancellationToken).ConfigureAwait(false))
using (var reader = ObjectReader.TryGetReader(stream))
{
if (TryReadVersion(reader, out var persistedVersion) &&
document.CanReusePersistedTextVersion(textAndVersion.Version, persistedVersion))
{
return Checksum.ReadFrom(reader);
}
}
// either checksum doesn't exist or can't reuse. re-calculate the checksum
var checksum = serializer.CreateChecksum(textAndVersion.Text, cancellationToken);
// save newly calculated checksum
using (var stream = SerializableBytes.CreateWritableStream())
using (var writer = new ObjectWriter(stream, cancellationToken))
{
WriteVersionsTo(writer, textAndVersion.Version);
checksum.WriteTo(writer);
stream.Position = 0;
await storage.WriteStreamAsync(document, TextChecksum, stream, cancellationToken).ConfigureAwait(false);
}
return checksum;
}
catch (Exception e) when (IOUtilities.IsNormalIOException(e))
{
// Storage APIs can throw arbitrary exceptions.
}
// go simple route if persistent thing didn't work out
return serializer.CreateChecksum(textAndVersion.Text, cancellationToken);
}
private void WriteVersionsTo(ObjectWriter writer, VersionStamp version)
{
writer.WriteString(SerializationFormat);
version.WriteTo(writer); ;
}
private static bool TryReadVersion(ObjectReader reader, out VersionStamp persistedVersion)
{
persistedVersion = VersionStamp.Default;
if (reader?.ReadString() != SerializationFormat)
{
return false;
}
persistedVersion = VersionStamp.ReadFrom(reader);
return true;
}
}
}
......@@ -70,6 +70,7 @@
<Compile Include="Services\AssetStorage.cs" />
<Compile Include="Services\ChecksumSynchronizer.cs" />
<Compile Include="Services\CompilationService.cs" />
<Compile Include="Services\ISolutionController.cs" />
<Compile Include="Services\ProjectCacheHostServiceFactory.cs" />
<Compile Include="Services\RoslynServices.cs" />
<Compile Include="Services\SolutionCreator.cs" />
......@@ -78,6 +79,7 @@
<Compile Include="Services\TemporaryWorkspace.cs" />
<Compile Include="Services\TemporaryWorkspaceOptionsServiceFactory.cs" />
<Compile Include="Storage\RemotePersistentStorageLocationService.cs" />
<Compile Include="TestUtils.cs" />
</ItemGroup>
<Import Project="..\..\..\..\build\Targets\Imports.targets" />
</Project>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// Engine use this to set or update solution for given session (connection)
/// </summary>
internal interface ISolutionController
{
Task<Solution> GetSolutionAsync(Checksum solutionChecksum, bool fromPrimaryBranch, CancellationToken cancellationToken);
Task UpdatePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Remote.DebugUtil;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
......@@ -79,10 +80,8 @@ public async Task<Solution> CreateSolutionAsync(Checksum newSolutionChecksum)
solution = await UpdateProjectsAsync(solution, oldSolutionChecksums.Projects, newSolutionChecksums.Projects).ConfigureAwait(false);
}
#if DEBUG
// make sure created solution has same checksum as given one
Contract.Requires(newSolutionChecksum == await solution.State.GetChecksumAsync(_cancellationToken).ConfigureAwait(false));
#endif
await ValidateChecksumAsync(newSolutionChecksum, solution).ConfigureAwait(false);
return solution;
}
......@@ -613,5 +612,64 @@ private ImmutableArray<string> GetStrongNameKeyPaths(ProjectInfo.ProjectAttribut
return builder.ToImmutableAndFree();
}
private async Task ValidateChecksumAsync(Checksum givenSolutionChecksum, Solution solution)
{
// have this to avoid error on async
await SpecializedTasks.EmptyTask.ConfigureAwait(false);
#if DEBUG
var currentSolutionChecksum = await solution.State.GetChecksumAsync(_cancellationToken).ConfigureAwait(false);
if (givenSolutionChecksum == currentSolutionChecksum)
{
return;
}
Contract.Requires(false, "checksum not same");
var map = solution.GetAssetMap();
await RemoveDuplicateChecksumsAsync(givenSolutionChecksum, map).ConfigureAwait(false);
foreach (var kv in map.Where(kv => kv.Value is ChecksumWithChildren).ToList())
{
map.Remove(kv.Key);
}
var sb = new StringBuilder();
foreach (var kv in map)
{
sb.AppendLine($"{kv.Key.ToString()}, {kv.Value.ToString()}");
}
Logger.Log(FunctionId.SolutionCreator_AssetDifferences, sb.ToString());
#endif
return;
}
private async Task RemoveDuplicateChecksumsAsync(Checksum givenSolutionChecksum, Dictionary<Checksum, object> map)
{
var solutionChecksums = await _assetService.GetAssetAsync<SolutionStateChecksums>(givenSolutionChecksum, _cancellationToken).ConfigureAwait(false);
map.RemoveChecksums(solutionChecksums);
foreach (var projectChecksum in solutionChecksums.Projects)
{
var projectChecksums = await _assetService.GetAssetAsync<ProjectStateChecksums>(projectChecksum, _cancellationToken).ConfigureAwait(false);
map.RemoveChecksums(projectChecksums);
foreach (var documentChecksum in projectChecksums.Documents)
{
var documentChecksums = await _assetService.GetAssetAsync<DocumentStateChecksums>(documentChecksum, _cancellationToken).ConfigureAwait(false);
map.RemoveChecksums(documentChecksums);
}
foreach (var documentChecksum in projectChecksums.AdditionalDocuments)
{
var documentChecksums = await _assetService.GetAssetAsync<DocumentStateChecksums>(documentChecksum, _cancellationToken).ConfigureAwait(false);
map.RemoveChecksums(documentChecksums);
}
}
}
}
}
......@@ -3,7 +3,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
......@@ -13,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Remote
///
/// TODO: change this to workspace service
/// </summary>
internal class SolutionService
internal class SolutionService : ISolutionController
{
private static readonly SemaphoreSlim s_gate = new SemaphoreSlim(initialCount: 1);
private static readonly RemoteWorkspace s_primaryWorkspace = new RemoteWorkspace();
......@@ -29,7 +28,14 @@ public SolutionService(AssetService assetService)
_assetService = assetService;
}
public async Task<Solution> GetSolutionAsync(Checksum solutionChecksum, CancellationToken cancellationToken)
public Task<Solution> GetSolutionAsync(Checksum solutionChecksum, CancellationToken cancellationToken)
{
// this method is called by users which means we don't know whether the solution is from primary branch or not.
// so we will be conservative and assume it is not. meaning it won't update any internal caches but only consume cache if possible.
return GetSolutionInternalAsync(solutionChecksum, fromPrimaryBranch: false, cancellationToken: cancellationToken);
}
private async Task<Solution> GetSolutionInternalAsync(Checksum solutionChecksum, bool fromPrimaryBranch, CancellationToken cancellationToken)
{
var currentSolution = GetAvailableSolution(solutionChecksum);
if (currentSolution != null)
......@@ -46,67 +52,97 @@ public async Task<Solution> GetSolutionAsync(Checksum solutionChecksum, Cancella
return currentSolution;
}
var solution = await CreateSolution_NoLockAsync(solutionChecksum, s_primaryWorkspace.CurrentSolution, cancellationToken).ConfigureAwait(false);
var solution = await CreateSolution_NoLockAsync(solutionChecksum, fromPrimaryBranch, s_primaryWorkspace.CurrentSolution, cancellationToken).ConfigureAwait(false);
s_lastSolution = Tuple.Create(solutionChecksum, solution);
return solution;
}
}
public async Task UpdatePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken)
/// <summary>
/// SolutionService is designed to be stateless. if someone asks a solution (through solution checksum),
/// it will create one and return the solution. the engine takes care of synching required data and creating a solution
/// correspoing to the given checksum.
///
/// but doing that from scratch all the time wil be expansive in terms of synching data, compilation being cached, file being parsed
/// and etc. so even if the service itself is stateless, internally it has several caches to improve perf of various parts.
///
/// first, it holds onto last solution got built. this will take care of common cases where multiple services running off same solution.
/// second, it uses assets cache to hold onto data just synched (within 3 min) so that if it requires to build new solution,
/// it can save some time to re-sync data which might just used by other solution.
/// third, it holds onto solution from primary branch from Host. and it will try to see whether it can build new solution off the
/// primary solution it is holding onto. this will make many solution level cache to be re-used.
///
/// the primary solution can be updated in 2 ways.
/// first, host will keep track of primary solution changes in host, and call OOP to synch to latest time to time.
/// second, engine keeps track of whether a certain request is for primary solution or not, and if it is,
/// it let that request to update primary solution cache to latest.
///
/// these 2 are complimentary to each other. #1 makes OOP's primary solution to be ready for next call (push), #2 makes OOP's primary
/// solution be not stale as much as possible. (pull)
/// </summary>
private async Task<Solution> CreateSolution_NoLockAsync(Checksum solutionChecksum, bool fromPrimaryBranch, Solution baseSolution, CancellationToken cancellationToken)
{
var currentSolution = s_primaryWorkspace.CurrentSolution;
var updater = new SolutionCreator(_assetService, baseSolution, cancellationToken);
var primarySolutionChecksum = await currentSolution.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false);
if (primarySolutionChecksum == solutionChecksum)
// check whether solution is update to the given base solution
if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false))
{
// nothing changed
return;
}
// create updated solution off the baseSolution
var solution = await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false);
using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
var solution = await UpdatePrimaryWorkspace_NoLockAsync(solutionChecksum, currentSolution, cancellationToken).ConfigureAwait(false);
s_primarySolution = Tuple.Create(solutionChecksum, solution);
if (fromPrimaryBranch)
{
// if the solutionChecksum is for primary branch, update primary workspace cache with the solution
s_primaryWorkspace.UpdateSolution(solution);
return s_primaryWorkspace.CurrentSolution;
}
// otherwise, just return the solution
return solution;
}
}
private async Task<Solution> CreateSolution_NoLockAsync(Checksum solutionChecksum, Solution baseSolution, CancellationToken cancellationToken)
{
var updater = new SolutionCreator(_assetService, baseSolution, cancellationToken);
// we need new solution. bulk sync all asset for the solution first.
await _assetService.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false))
// get new solution info
var solutionInfo = await updater.CreateSolutionInfoAsync(solutionChecksum).ConfigureAwait(false);
if (fromPrimaryBranch)
{
// solution has updated
return await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false);
}
// if the solutionChecksum is for primary branch, update primary workspace cache with new solution
s_primaryWorkspace.ClearSolution();
s_primaryWorkspace.AddSolution(solutionInfo);
// new solution. bulk sync all asset for the solution
await _assetService.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
return s_primaryWorkspace.CurrentSolution;
}
// otherwise, just return new solution
var workspace = new TemporaryWorkspace(await updater.CreateSolutionInfoAsync(solutionChecksum).ConfigureAwait(false));
return workspace.CurrentSolution;
}
private async Task<Solution> UpdatePrimaryWorkspace_NoLockAsync(Checksum solutionChecksum, Solution baseSolution, CancellationToken cancellationToken)
async Task<Solution> ISolutionController.GetSolutionAsync(Checksum solutionChecksum, bool primary, CancellationToken cancellationToken)
{
var updater = new SolutionCreator(_assetService, baseSolution, cancellationToken);
return await GetSolutionInternalAsync(solutionChecksum, primary, cancellationToken).ConfigureAwait(false);
}
if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false))
{
// solution has updated
s_primaryWorkspace.UpdateSolution(await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false));
async Task ISolutionController.UpdatePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken)
{
var currentSolution = s_primaryWorkspace.CurrentSolution;
return s_primaryWorkspace.CurrentSolution;
var primarySolutionChecksum = await currentSolution.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false);
if (primarySolutionChecksum == solutionChecksum)
{
return;
}
// new solution. bulk sync all asset for the solution
await _assetService.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
s_primaryWorkspace.ClearSolution();
s_primaryWorkspace.AddSolution(await updater.CreateSolutionInfoAsync(solutionChecksum).ConfigureAwait(false));
return s_primaryWorkspace.CurrentSolution;
using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
var primary = true;
var solution = await CreateSolution_NoLockAsync(solutionChecksum, primary, currentSolution, cancellationToken).ConfigureAwait(false);
s_primarySolution = Tuple.Create(solutionChecksum, solution);
}
}
private static Solution GetAvailableSolution(Checksum solutionChecksum)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Threading;
using Microsoft.CodeAnalysis.Serialization;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote.DebugUtil
{
internal static class TestUtils
{
public static void RemoveChecksums(this Dictionary<Checksum, object> map, ChecksumWithChildren checksums)
{
map.Remove(checksums.Checksum);
foreach (var child in checksums.Children)
{
var checksum = child as Checksum;
if (checksum != null)
{
map.Remove(checksum);
}
var collection = child as ChecksumCollection;
if (collection != null)
{
foreach (var item in collection)
{
map.Remove(item);
}
}
}
}
public static Dictionary<Checksum, object> GetAssetMap(this Solution solution)
{
var map = new Dictionary<Checksum, object>();
AppendAssetMap(solution, map);
return map;
}
public static Dictionary<Checksum, object> GetAssetMap(this Project project)
{
var map = new Dictionary<Checksum, object>();
AppendAssetMap(project, map);
return map;
}
public static void AppendAssetMap(this Solution solution, Dictionary<Checksum, object> map)
{
SolutionStateChecksums solutionChecksums;
Contract.ThrowIfFalse(solution.State.TryGetStateChecksums(out solutionChecksums));
solutionChecksums.Find(solution.State, Flatten(solutionChecksums), map, CancellationToken.None);
foreach (var project in solution.Projects)
{
AppendAssetMap(project, map);
}
}
private static void AppendAssetMap(Project project, Dictionary<Checksum, object> map)
{
ProjectStateChecksums projectChecksums;
if (!project.State.TryGetStateChecksums(out projectChecksums))
{
Contract.Requires(!RemoteSupportedLanguages.IsSupported(project.Language));
return;
}
projectChecksums.Find(project.State, Flatten(projectChecksums), map, CancellationToken.None);
foreach (var document in project.Documents)
{
AppendAssetMap(document, map);
}
foreach (var document in project.AdditionalDocuments)
{
AppendAssetMap(document, map);
}
}
private static void AppendAssetMap(TextDocument document, Dictionary<Checksum, object> map)
{
DocumentStateChecksums documentChecksums;
Contract.ThrowIfFalse(document.State.TryGetStateChecksums(out documentChecksums));
documentChecksums.Find(document.State, Flatten(documentChecksums), map, CancellationToken.None);
// fix up due to source text can't be obtained synchronously in product code
map[documentChecksums.Text] = document.State.GetTextSynchronously(CancellationToken.None);
}
private static HashSet<Checksum> Flatten(ChecksumWithChildren checksums)
{
var set = new HashSet<Checksum>();
set.Add(checksums.Checksum);
foreach (var child in checksums.Children)
{
var checksum = child as Checksum;
if (checksum != null)
{
set.Add(checksum);
}
var collection = child as ChecksumCollection;
if (collection != null)
{
foreach (var item in collection)
{
set.Add(item);
}
}
}
return set;
}
}
}
......@@ -90,6 +90,7 @@
<Compile Include="Shared\ServerDirectStream.cs" />
<Compile Include="Shared\ClientDirectStream.cs" />
<Compile Include="Telemetry\WatsonReporter.cs" />
<Compile Include="UserOperationBooster.cs" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.LiveUnitTesting.BuildManager" />
......
......@@ -81,7 +81,8 @@ public async Task SynchronizePrimaryWorkspaceAsync(Checksum checksum)
{
try
{
await RoslynServices.SolutionService.UpdatePrimaryWorkspaceAsync(checksum, CancellationToken).ConfigureAwait(false);
var solutionController = (ISolutionController)RoslynServices.SolutionService;
await solutionController.UpdatePrimaryWorkspaceAsync(checksum, CancellationToken).ConfigureAwait(false);
}
catch (IOException)
{
......
......@@ -27,9 +27,9 @@ internal partial class SnapshotService : ServiceHubServiceBase
Rpc.StartListening();
}
public override void Initialize(int sessionId, byte[] solutionChecksum)
public override void Initialize(int sessionId, bool primary, Checksum solutionChecksum)
{
base.Initialize(sessionId, solutionChecksum);
base.Initialize(sessionId, primary, solutionChecksum);
lock (_gate)
{
......
......@@ -127,11 +127,16 @@ private class ChecksumJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => typeof(Checksum) == objectType;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
new Checksum(Convert.FromBase64String((string)reader.Value));
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = (string)reader.Value;
return value == null ? null : new Checksum(Convert.FromBase64String(value));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
writer.WriteValue(value.ToString());
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value?.ToString());
}
}
}
}
......@@ -5,7 +5,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.LanguageServices.Remote;
using Roslyn.Utilities;
using StreamJsonRpc;
......@@ -27,8 +26,25 @@ internal abstract class ServiceHubServiceBase : IDisposable
protected readonly AssetStorage AssetStorage;
protected readonly CancellationToken CancellationToken;
/// <summary>
/// Session Id of this service. caller and callee share this id which one
/// can use to find matching caller and callee when debugging or logging
/// </summary>
private int _sessionId;
/// <summary>
/// Mark whether the solution checksum it got is for primary branch or not
///
/// this flag will be passed down to solution controller to help
/// solution service's cache policy. for more detail, see <see cref="SolutionService"/>
/// </summary>
private bool _fromPrimaryBranch;
/// <summary>
/// solution this connection belong to
/// </summary>
private Checksum _solutionChecksumOpt;
private RoslynServices _lazyRoslynServices;
[Obsolete("For backward compatibility. this will be removed once all callers moved to new ctor")]
......@@ -90,7 +106,9 @@ protected RoslynServices RoslynServices
protected Task<Solution> GetSolutionAsync()
{
Contract.ThrowIfNull(_solutionChecksumOpt);
return RoslynServices.SolutionService.GetSolutionAsync(_solutionChecksumOpt, CancellationToken);
var solutionController = (ISolutionController)RoslynServices.SolutionService;
return solutionController.GetSolutionAsync(_solutionChecksumOpt, _fromPrimaryBranch, CancellationToken);
}
protected virtual void Dispose(bool disposing)
......@@ -103,14 +121,15 @@ protected void LogError(string message)
Log(TraceEventType.Error, message);
}
public virtual void Initialize(int sessionId, byte[] solutionChecksum)
public virtual void Initialize(int sessionId, bool fromPrimaryBranch, Checksum solutionChecksum)
{
// set session related information
_sessionId = sessionId;
_fromPrimaryBranch = fromPrimaryBranch;
if (solutionChecksum != null)
{
_solutionChecksumOpt = new Checksum(solutionChecksum);
_solutionChecksumOpt = solutionChecksum;
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Threading;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// Boost performance of any servicehub service which is invoked by user explicit actions
/// </summary>
internal static class UserOperationBooster
{
private static int s_count = 0;
public static IDisposable Boost()
{
return new Booster();
}
private static void Start()
{
var value = Interlocked.Increment(ref s_count);
Contract.Requires(value >= 0);
if (value == 1)
{
// boost to normal priority
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.Normal;
}
}
private static void Done()
{
var value = Interlocked.Decrement(ref s_count);
Contract.Requires(value >= 0);
if (value == 0)
{
// when boost is done, set process back to below normal priority
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.BelowNormal;
}
}
private class Booster : IDisposable
{
public Booster()
{
Start();
}
public void Dispose()
{
Done();
GC.SuppressFinalize(this);
}
~Booster()
{
if (!Environment.HasShutdownStarted)
{
Contract.Fail($@"Should have been disposed!");
}
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册