提交 2915481c 编写于 作者: H Heejae Chang 提交者: GitHub

Merge pull request #19516 from heejaechang/KeepSession2

added an ability to keep session alive for OOP
......@@ -6,7 +6,6 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Remote;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.SymbolSearch
{
......@@ -24,7 +23,11 @@ internal static class SymbolSearchUpdateEngineFactory
RemoteFeatureOptions.SymbolSearchEnabled, cancellationToken).ConfigureAwait(false);
if (client != null)
{
return new RemoteUpdateEngine(workspace, client, logService, cancellationToken);
var session = await client.TryCreateKeepAliveSessionAsync(WellKnownServiceHubServices.RemoteSymbolSearchUpdateEngine, logService, cancellationToken).ConfigureAwait(false);
if (session != null)
{
return new RemoteUpdateEngine(workspace, session);
}
}
// Couldn't go out of proc. Just do everything inside the current process.
......@@ -36,144 +39,48 @@ private class RemoteUpdateEngine : ISymbolSearchUpdateEngine
private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1);
private readonly Workspace _workspace;
private readonly ISymbolSearchLogService _logService;
private readonly CancellationToken _cancellationToken;
private RemoteHostClient _client;
private RemoteHostClient.Session _sessionDoNotAccessDirectly;
private readonly KeepAliveSession _session;
public RemoteUpdateEngine(
Workspace workspace, RemoteHostClient client,
ISymbolSearchLogService logService, CancellationToken cancellationToken)
public RemoteUpdateEngine(Workspace workspace, KeepAliveSession session)
{
_workspace = workspace;
_logService = logService;
_cancellationToken = cancellationToken;
// this engine is stateful service which maintaining a connection to remote host. so
// this feature is required to handle remote host recycle situation.
_client = client;
_client.ConnectionChanged += OnConnectionChanged;
}
private async void OnConnectionChanged(object sender, bool connected)
{
if (connected)
{
return;
}
// to make things simpler, this is not cancellable. I believe this
// is okay since this handle rare cases where remote host is recycled or
// removed
using (await _gate.DisposableWaitAsync(CancellationToken.None).ConfigureAwait(false))
{
_client.ConnectionChanged -= OnConnectionChanged;
_sessionDoNotAccessDirectly?.Dispose();
_sessionDoNotAccessDirectly = null;
_client = await _workspace.TryGetRemoteHostClientAsync(CancellationToken.None).ConfigureAwait(false);
if (_client != null)
{
// client can be null if host is shutting down
_client.ConnectionChanged += OnConnectionChanged;
}
}
}
private async Task<RemoteHostClient.Session> TryGetSessionAsync()
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
{
if (_sessionDoNotAccessDirectly != null)
{
return _sessionDoNotAccessDirectly;
}
if (_client == null)
{
// client can be null if host is shutting down
return null;
}
// We create a single session and use it for the entire lifetime of this process.
// That single session will be used to do all communication with the remote process.
// This is because each session will cause a new instance of the RemoteSymbolSearchUpdateEngine
// to be created on the remote side. We only want one instance of that type. The
// alternative is to make that type static variable on the remote side. But that's
// much less clean and would make some of the state management much more complex.
_sessionDoNotAccessDirectly = await _client.TryCreateServiceSessionAsync(
WellKnownServiceHubServices.RemoteSymbolSearchUpdateEngine,
_logService,
_cancellationToken).ConfigureAwait(false);
return _sessionDoNotAccessDirectly;
}
_session = session;
}
public async Task<ImmutableArray<PackageWithTypeResult>> FindPackagesWithTypeAsync(
string source, string name, int arity)
{
var session = await TryGetSessionAsync().ConfigureAwait(false);
if (session == null)
{
// we couldn't get session. most likely remote host is gone
return ImmutableArray<PackageWithTypeResult>.Empty;
}
var results = await session.InvokeAsync<ImmutableArray<PackageWithTypeResult>>(
var results = await _session.TryInvokeAsync<ImmutableArray<PackageWithTypeResult>>(
nameof(IRemoteSymbolSearchUpdateEngine.FindPackagesWithTypeAsync),
source, name, arity).ConfigureAwait(false);
return results;
return results.NullToEmpty();
}
public async Task<ImmutableArray<PackageWithAssemblyResult>> FindPackagesWithAssemblyAsync(
string source, string assemblyName)
{
var session = await TryGetSessionAsync().ConfigureAwait(false);
if (session == null)
{
// we couldn't get session. most likely remote host is gone
return ImmutableArray<PackageWithAssemblyResult>.Empty;
}
var results = await session.InvokeAsync<ImmutableArray<PackageWithAssemblyResult>>(
var results = await _session.TryInvokeAsync<ImmutableArray<PackageWithAssemblyResult>>(
nameof(IRemoteSymbolSearchUpdateEngine.FindPackagesWithAssemblyAsync),
source, assemblyName).ConfigureAwait(false);
return results;
return results.NullToEmpty();
}
public async Task<ImmutableArray<ReferenceAssemblyWithTypeResult>> FindReferenceAssembliesWithTypeAsync(
string name, int arity)
{
var session = await TryGetSessionAsync().ConfigureAwait(false);
if (session == null)
{
// we couldn't get session. most likely remote host is gone
return ImmutableArray<ReferenceAssemblyWithTypeResult>.Empty;
}
var results = await session.InvokeAsync<ImmutableArray<ReferenceAssemblyWithTypeResult>>(
var results = await _session.TryInvokeAsync<ImmutableArray<ReferenceAssemblyWithTypeResult>>(
nameof(IRemoteSymbolSearchUpdateEngine.FindReferenceAssembliesWithTypeAsync),
name, arity).ConfigureAwait(false);
return results;
return results.NullToEmpty();
}
public async Task UpdateContinuouslyAsync(
string sourceName, string localSettingsDirectory)
{
var session = await TryGetSessionAsync().ConfigureAwait(false);
if (session == null)
{
// we couldn't get session. most likely remote host is gone
return;
}
await session.InvokeAsync(
await _session.TryInvokeAsync(
nameof(IRemoteSymbolSearchUpdateEngine.UpdateContinuouslyAsync),
sourceName, localSettingsDirectory).ConfigureAwait(false);
}
......
......@@ -7,7 +7,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.VisualStudio.LanguageServices.Remote;
using Nerdbank;
......@@ -37,7 +36,7 @@ public static async Task<RemoteHostClient> CreateAsync(Workspace workspace, bool
// TODO: change this to non fatal watson and make VS to use inproc implementation
Contract.ThrowIfFalse(host == current.ToString());
instance.Connected();
instance.Started();
// return instance
return instance;
......@@ -59,24 +58,26 @@ public static async Task<RemoteHostClient> CreateAsync(Workspace workspace, bool
public AssetStorage AssetStorage => _inprocServices.AssetStorage;
protected override async Task<Session> TryCreateServiceSessionAsync(string serviceName, Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync, object callbackTarget, CancellationToken cancellationToken)
public override async Task<Connection> TryCreateConnectionAsync(
string serviceName, object callbackTarget, CancellationToken cancellationToken)
{
// get stream from service hub to communicate snapshot/asset related information
// 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);
var snapshotStream = await _inprocServices.RequestServiceAsync(
WellKnownServiceHubServices.SnapshotService, cancellationToken).ConfigureAwait(false);
// 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);
return await JsonRpcSession.CreateAsync(getSnapshotAsync, callbackTarget, serviceStream, snapshotStream, cancellationToken).ConfigureAwait(false);
return new ServiceHubJsonRpcConnection(callbackTarget, serviceStream, snapshotStream, cancellationToken);
}
protected override void OnConnected()
protected override void OnStarted()
{
}
protected override void OnDisconnected()
protected override void OnStopped()
{
// we are asked to disconnect. unsubscribe and dispose to disconnect
_rpc.Disconnected -= OnRpcDisconnected;
......@@ -85,7 +86,7 @@ protected override void OnDisconnected()
private void OnRpcDisconnected(object sender, JsonRpcDisconnectedEventArgs e)
{
Disconnected();
Stopped();
}
public class ServiceProvider : IServiceProvider
......
......@@ -173,8 +173,8 @@
<Compile Include="..\..\visualstudio\core\next\remote\JsonRpcClient.cs">
<Link>Remote\JsonRpcClient.cs</Link>
</Compile>
<Compile Include="..\..\visualstudio\core\next\remote\JsonRpcSession.cs">
<Link>Remote\JsonRpcSession.cs</Link>
<Compile Include="..\..\VisualStudio\Core\Next\Remote\JsonRpcConnection.cs">
<Link>Remote\JsonRpcConnection.cs</Link>
</Compile>
<Compile Include="AbstractCommandHandlerTestState.cs" />
<Compile Include="Async\AsynchronousOperationBlocker.cs" />
......
......@@ -5,18 +5,29 @@
using System.Collections.Immutable;
using System.Composition;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.TodoComments;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.TodoComments
{
[ExportLanguageService(typeof(ITodoCommentService), LanguageNames.CSharp), Shared]
[ExportLanguageServiceFactory(typeof(ITodoCommentService), LanguageNames.CSharp), Shared]
internal class CSharpTodoCommentServiceFactory : ILanguageServiceFactory
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
=> new CSharpTodoCommentService(languageServices.WorkspaceServices.Workspace);
}
internal class CSharpTodoCommentService : AbstractTodoCommentService
{
private static readonly int s_multilineCommentPostfixLength = "*/".Length;
private const string SingleLineCommentPrefix = "//";
public CSharpTodoCommentService(Workspace workspace) : base(workspace)
{
}
protected override void AppendTodoComments(ImmutableArray<TodoCommentDescriptor> commentDescriptors, SyntacticDocument document, SyntaxTrivia trivia, List<TodoComment> todoList)
{
if (PreprocessorHasComment(trivia))
......
......@@ -7,8 +7,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
......@@ -21,7 +19,7 @@
namespace Microsoft.CodeAnalysis.AddImport
{
internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSyntax>
internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSyntax>
: IAddImportFeatureService, IEqualityComparer<PortableExecutableReference>
where TSimpleNameSyntax : SyntaxNode
{
......@@ -56,31 +54,36 @@ protected AbstractAddImportFeatureService()
public async Task<ImmutableArray<AddImportFixData>> GetFixesAsync(
Document document, TextSpan span, string diagnosticId, bool placeSystemNamespaceFirst,
ISymbolSearchService symbolSearchService, bool searchReferenceAssemblies,
ISymbolSearchService symbolSearchService, bool searchReferenceAssemblies,
ImmutableArray<PackageSource> packageSources, CancellationToken cancellationToken)
{
var callbackTarget = new RemoteSymbolSearchService(symbolSearchService, cancellationToken);
var session = await document.Project.Solution.TryCreateCodeAnalysisServiceSessionAsync(
RemoteFeatureOptions.AddImportEnabled, callbackTarget, cancellationToken).ConfigureAwait(false);
var result = await document.Project.Solution.TryRunCodeAnalysisRemoteAsync<ImmutableArray<AddImportFixData>>(
RemoteFeatureOptions.AddImportEnabled,
callbackTarget,
nameof(IRemoteAddImportFeatureService.GetFixesAsync),
new object[]
{
document.Id,
span,
diagnosticId,
placeSystemNamespaceFirst,
searchReferenceAssemblies,
packageSources
},
cancellationToken).ConfigureAwait(false);
var documentOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
using (session)
if (!result.IsDefault)
{
if (session == null)
{
return await GetFixesInCurrentProcessAsync(
document, span, diagnosticId, placeSystemNamespaceFirst,
symbolSearchService, searchReferenceAssemblies,
packageSources, cancellationToken).ConfigureAwait(false);
}
else
{
return await GetFixesInRemoteProcessAsync(
session, document, span, diagnosticId, placeSystemNamespaceFirst,
searchReferenceAssemblies, packageSources).ConfigureAwait(false);
}
return result;
}
return await GetFixesInCurrentProcessAsync(
document, span, diagnosticId, placeSystemNamespaceFirst,
symbolSearchService, searchReferenceAssemblies,
packageSources, cancellationToken).ConfigureAwait(false);
}
private async Task<ImmutableArray<AddImportFixData>> GetFixesInCurrentProcessAsync(
......@@ -104,7 +107,7 @@ protected AbstractAddImportFeatureService()
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var allSymbolReferences = await FindResultsAsync(
document, semanticModel, diagnosticId, node, symbolSearchService,
document, semanticModel, diagnosticId, node, symbolSearchService,
searchReferenceAssemblies, packageSources, cancellationToken).ConfigureAwait(false);
// Nothing found at all. No need to proceed.
......@@ -124,7 +127,7 @@ protected AbstractAddImportFeatureService()
}
private async Task<ImmutableArray<Reference>> FindResultsAsync(
Document document, SemanticModel semanticModel, string diagnosticId, SyntaxNode node, ISymbolSearchService symbolSearchService,
Document document, SemanticModel semanticModel, string diagnosticId, SyntaxNode node, ISymbolSearchService symbolSearchService,
bool searchReferenceAssemblies, ImmutableArray<PackageSource> packageSources, CancellationToken cancellationToken)
{
// Caches so we don't produce the same data multiple times while searching
......@@ -201,7 +204,7 @@ private static bool IsHostOrTestOrRemoteWorkspace(Project project)
}
private async Task FindResultsInAllSymbolsInStartingProjectAsync(
ArrayBuilder<Reference> allSymbolReferences, SymbolReferenceFinder finder,
ArrayBuilder<Reference> allSymbolReferences, SymbolReferenceFinder finder,
bool exact, CancellationToken cancellationToken)
{
var references = await finder.FindInAllSymbolsInStartingProjectAsync(exact, cancellationToken).ConfigureAwait(false);
......
......@@ -2,31 +2,14 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Packaging;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.SymbolSearch;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.AddImport
{
internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSyntax>
{
private async Task<ImmutableArray<AddImportFixData>> GetFixesInRemoteProcessAsync(
RemoteHostClient.Session session, Document document, TextSpan span,
string diagnosticId, bool placeSystemNamespaceFirst,
bool searchReferenceAssemblies, ImmutableArray<PackageSource> packageSources)
{
var result = await session.InvokeAsync<ImmutableArray<AddImportFixData>>(
nameof(IRemoteAddImportFeatureService.GetFixesAsync),
document.Id, span, diagnosticId, placeSystemNamespaceFirst,
searchReferenceAssemblies, packageSources).ConfigureAwait(false);
return result;
}
/// <summary>
/// Used to supply the OOP server a callback that it can use to search for ReferenceAssemblies or
/// nuget packages. We can't necessarily do that search directly in the OOP server as our
......@@ -56,31 +39,25 @@ public Task UpdateContinuouslyAsync(string sourceName, string localSettingsDirec
throw new NotImplementedException();
}
public async Task<ImmutableArray<PackageWithTypeResult>> FindPackagesWithTypeAsync(
public Task<ImmutableArray<PackageWithTypeResult>> FindPackagesWithTypeAsync(
string source, string name, int arity)
{
var result = await _symbolSearchService.FindPackagesWithTypeAsync(
source, name, arity, _cancellationToken).ConfigureAwait(false);
return result;
return _symbolSearchService.FindPackagesWithTypeAsync(
source, name, arity, _cancellationToken);
}
public async Task<ImmutableArray<PackageWithAssemblyResult>> FindPackagesWithAssemblyAsync(
public Task<ImmutableArray<PackageWithAssemblyResult>> FindPackagesWithAssemblyAsync(
string source, string name)
{
var result = await _symbolSearchService.FindPackagesWithAssemblyAsync(
source, name, _cancellationToken).ConfigureAwait(false);
return result;
return _symbolSearchService.FindPackagesWithAssemblyAsync(
source, name, _cancellationToken);
}
public async Task<ImmutableArray<ReferenceAssemblyWithTypeResult>> FindReferenceAssembliesWithTypeAsync(
public Task<ImmutableArray<ReferenceAssemblyWithTypeResult>> FindReferenceAssembliesWithTypeAsync(
string name, int arity)
{
var result = await _symbolSearchService.FindReferenceAssembliesWithTypeAsync(
name, arity, _cancellationToken).ConfigureAwait(false);
return result;
return _symbolSearchService.FindReferenceAssembliesWithTypeAsync(
name, arity, _cancellationToken);
}
}
}
......
......@@ -135,7 +135,7 @@ public AnalyzerExecutor(AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateS
forcedAnalysis, analyzerDriver.AnalysisOptions.ReportSuppressedDiagnostics, analyzerDriver.AnalysisOptions.LogAnalyzerExecutionTime,
project.Id, optionAsset.Checksum, analyzerMap.Keys.ToArray());
using (var session = await client.TryCreateCodeAnalysisServiceSessionAsync(solution, cancellationToken).ConfigureAwait(false))
using (var session = await client.TryCreateCodeAnalysisSessionAsync(solution, cancellationToken).ConfigureAwait(false))
{
if (session == null)
{
......
......@@ -38,23 +38,23 @@ internal abstract partial class AbstractDocumentHighlightsService : IDocumentHig
private async Task<(bool succeeded, ImmutableArray<DocumentHighlights> highlights)> GetDocumentHighlightsInRemoteProcessAsync(
Document document, int position, IImmutableSet<Document> documentsToSearch, CancellationToken cancellationToken)
{
var session = await document.Project.Solution.TryCreateCodeAnalysisServiceSessionAsync(
RemoteFeatureOptions.DocumentHighlightingEnabled, cancellationToken).ConfigureAwait(false);
using (session)
{
if (session == null)
var result = await document.Project.Solution.TryRunCodeAnalysisRemoteAsync<ImmutableArray<SerializableDocumentHighlights>>(
RemoteFeatureOptions.DocumentHighlightingEnabled,
nameof(IRemoteDocumentHighlights.GetDocumentHighlightsAsync),
new object[]
{
return (succeeded: false, ImmutableArray<DocumentHighlights>.Empty);
}
var result = await session.InvokeAsync<ImmutableArray<SerializableDocumentHighlights>>(
nameof(IRemoteDocumentHighlights.GetDocumentHighlightsAsync),
document.Id,
position,
documentsToSearch.Select(d => d.Id).ToArray()).ConfigureAwait(false);
return (true, result.SelectAsArray(h => h.Rehydrate(document.Project.Solution)));
documentsToSearch.Select(d => d.Id).ToArray()
},
cancellationToken).ConfigureAwait(false);
if (result.IsDefault)
{
return (succeeded: false, ImmutableArray<DocumentHighlights>.Empty);
}
return (true, result.SelectAsArray(h => h.Rehydrate(document.Project.Solution)));
}
private async Task<ImmutableArray<DocumentHighlights>> GetDocumentHighlightsInCurrentProcessAsync(
......
// 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.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Remote;
namespace Microsoft.CodeAnalysis.NavigateTo
......@@ -10,29 +13,32 @@ namespace Microsoft.CodeAnalysis.NavigateTo
internal abstract partial class AbstractNavigateToSearchService
{
private async Task<ImmutableArray<INavigateToSearchResult>> SearchDocumentInRemoteProcessAsync(
RemoteHostClient.Session session, Document document, string searchPattern, CancellationToken cancellationToken)
RemoteHostClient client, Document document, string searchPattern, CancellationToken cancellationToken)
{
var serializableResults = await session.InvokeAsync<ImmutableArray<SerializableNavigateToSearchResult>>(
nameof(IRemoteNavigateToSearchService.SearchDocumentAsync),
document.Id, searchPattern).ConfigureAwait(false);
var solution = document.Project.Solution;
return serializableResults.SelectAsArray(r => r.Rehydrate(document.Project.Solution));
var serializableResults = await client.TryRunCodeAnalysisRemoteAsync<ImmutableArray<SerializableNavigateToSearchResult>>(
solution, nameof(IRemoteNavigateToSearchService.SearchDocumentAsync),
new object[] { document.Id, searchPattern }, cancellationToken).ConfigureAwait(false);
return serializableResults.NullToEmpty().SelectAsArray(r => r.Rehydrate(solution));
}
private async Task<ImmutableArray<INavigateToSearchResult>> SearchProjectInRemoteProcessAsync(
RemoteHostClient.Session session, Project project, string searchPattern, CancellationToken cancellationToken)
RemoteHostClient client, Project project, string searchPattern, CancellationToken cancellationToken)
{
var serializableResults = await session.InvokeAsync<ImmutableArray<SerializableNavigateToSearchResult>>(
nameof(IRemoteNavigateToSearchService.SearchProjectAsync),
project.Id, searchPattern).ConfigureAwait(false);
var solution = project.Solution;
var serializableResults = await client.TryRunCodeAnalysisRemoteAsync<ImmutableArray<SerializableNavigateToSearchResult>>(
solution, nameof(IRemoteNavigateToSearchService.SearchProjectAsync),
new object[] { project.Id, searchPattern }, cancellationToken).ConfigureAwait(false);
return serializableResults.SelectAsArray(r => r.Rehydrate(project.Solution));
return serializableResults.NullToEmpty().SelectAsArray(r => r.Rehydrate(solution));
}
private static Task<RemoteHostClient.Session> GetRemoteHostSessionAsync(Project project, CancellationToken cancellationToken)
private static async Task<RemoteHostClient> TryGetRemoteHostClientAsync(Project project, CancellationToken cancellationToken)
{
return project.Solution.TryCreateCodeAnalysisServiceSessionAsync(
RemoteFeatureOptions.NavigateToEnabled, cancellationToken);
return await project.Solution.Workspace.TryGetRemoteHostClientAsync(RemoteFeatureOptions.NavigateToEnabled, cancellationToken).ConfigureAwait(false);
}
}
}
\ No newline at end of file
......@@ -11,38 +11,32 @@ internal abstract partial class AbstractNavigateToSearchService : INavigateToSea
public async Task<ImmutableArray<INavigateToSearchResult>> SearchDocumentAsync(
Document document, string searchPattern, CancellationToken cancellationToken)
{
var session = await GetRemoteHostSessionAsync(document.Project, cancellationToken).ConfigureAwait(false);
using (session)
var client = await TryGetRemoteHostClientAsync(document.Project, cancellationToken).ConfigureAwait(false);
if (client == null)
{
if (session == null)
{
return await SearchDocumentInCurrentProcessAsync(
document, searchPattern, cancellationToken).ConfigureAwait(false);
}
else
{
return await SearchDocumentInRemoteProcessAsync(
session, document, searchPattern, cancellationToken).ConfigureAwait(false);
}
return await SearchDocumentInCurrentProcessAsync(
document, searchPattern, cancellationToken).ConfigureAwait(false);
}
else
{
return await SearchDocumentInRemoteProcessAsync(
client, document, searchPattern, cancellationToken).ConfigureAwait(false);
}
}
public async Task<ImmutableArray<INavigateToSearchResult>> SearchProjectAsync(
Project project, string searchPattern, CancellationToken cancellationToken)
{
var session = await GetRemoteHostSessionAsync(project, cancellationToken).ConfigureAwait(false);
using (session)
var client = await TryGetRemoteHostClientAsync(project, cancellationToken).ConfigureAwait(false);
if (client == null)
{
return await SearchProjectInCurrentProcessAsync(
project, searchPattern, cancellationToken).ConfigureAwait(false);
}
else
{
if (session == null)
{
return await SearchProjectInCurrentProcessAsync(
project, searchPattern, cancellationToken).ConfigureAwait(false);
}
else
{
return await SearchProjectInRemoteProcessAsync(
session, project, searchPattern, cancellationToken).ConfigureAwait(false);
}
return await SearchProjectInRemoteProcessAsync(
client, project, searchPattern, cancellationToken).ConfigureAwait(false);
}
}
}
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Versions;
......@@ -28,7 +29,6 @@ private sealed class NormalPriorityProcessor : AbstractPriorityProcessor
private readonly AsyncDocumentWorkItemQueue _workItemQueue;
private readonly ConcurrentDictionary<DocumentId, IDisposable> _higherPriorityDocumentsNotProcessed;
private readonly HashSet<ProjectId> _currentSnapshotVersionTrackingSet;
private ProjectId _currentProjectProcessing;
private Solution _processingSolution;
......@@ -53,8 +53,6 @@ private sealed class NormalPriorityProcessor : AbstractPriorityProcessor
_currentProjectProcessing = default(ProjectId);
_processingSolution = null;
_currentSnapshotVersionTrackingSet = new HashSet<ProjectId>();
Start();
}
......@@ -321,8 +319,6 @@ private async Task ProcessDocumentAsync(ImmutableArray<IIncrementalAnalyzer> ana
if (document != null)
{
await TrackSemanticVersionsAsync(document, workItem, cancellationToken).ConfigureAwait(false);
// if we are called because a document is opened, we invalidate the document so that
// it can be re-analyzed. otherwise, since newly opened document has same version as before
// analyzer will simply return same data back
......@@ -372,33 +368,6 @@ private async Task ProcessDocumentAsync(ImmutableArray<IIncrementalAnalyzer> ana
}
}
private async Task TrackSemanticVersionsAsync(Document document, WorkItem workItem, CancellationToken cancellationToken)
{
if (workItem.IsRetry ||
workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentAdded) ||
!workItem.InvocationReasons.Contains(PredefinedInvocationReasons.SyntaxChanged))
{
return;
}
var service = document.Project.Solution.Workspace.Services.GetService<ISemanticVersionTrackingService>();
if (service == null)
{
return;
}
// we already reported about this project for same snapshot, don't need to do it again
if (_currentSnapshotVersionTrackingSet.Contains(document.Project.Id))
{
return;
}
await service.RecordSemanticVersionsAsync(document.Project, cancellationToken).ConfigureAwait(false);
// mark this project as already processed.
_currentSnapshotVersionTrackingSet.Add(document.Project.Id);
}
private async Task ProcessOpenDocumentIfNeeded(ImmutableArray<IIncrementalAnalyzer> analyzers, WorkItem workItem, Document document, bool isOpen, CancellationToken cancellationToken)
{
if (!isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentOpened))
......@@ -493,24 +462,27 @@ private async Task ResetStatesAsync()
{
var currentSolution = this.Processor.CurrentSolution;
if (currentSolution != _processingSolution)
if (currentSolution == _processingSolution)
{
ResetLogAggregatorIfNeeded(currentSolution);
return;
}
// clear version tracking set we already reported.
_currentSnapshotVersionTrackingSet.Clear();
// solution has changed
ResetLogAggregatorIfNeeded(currentSolution);
_processingSolution = currentSolution;
_processingSolution = currentSolution;
await RunAnalyzersAsync(this.Analyzers, currentSolution, (a, s, c) => a.NewSolutionSnapshotAsync(s, c), this.CancellationToken).ConfigureAwait(false);
// synchronize new solution to OOP
await currentSolution.Workspace.SynchronizePrimaryWorkspaceAsync(currentSolution, this.CancellationToken).ConfigureAwait(false);
foreach (var id in this.Processor.GetOpenDocumentIds())
{
AddHigherPriorityDocument(id);
}
await RunAnalyzersAsync(this.Analyzers, currentSolution, (a, s, c) => a.NewSolutionSnapshotAsync(s, c), this.CancellationToken).ConfigureAwait(false);
SolutionCrawlerLogger.LogResetStates(this.Processor._logAggregator);
foreach (var id in this.Processor.GetOpenDocumentIds())
{
AddHigherPriorityDocument(id);
}
SolutionCrawlerLogger.LogResetStates(this.Processor._logAggregator);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
......
......@@ -296,7 +296,6 @@ private void ProcessProjectEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncTo
switch (e.Kind)
{
case WorkspaceChangeKind.ProjectAdded:
OnProjectAdded(e.NewSolution.GetProject(e.ProjectId));
EnqueueEvent(e.NewSolution, e.ProjectId, InvocationReasons.DocumentAdded, asyncToken);
break;
case WorkspaceChangeKind.ProjectRemoved:
......@@ -316,7 +315,6 @@ private void ProcessSolutionEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncT
switch (e.Kind)
{
case WorkspaceChangeKind.SolutionAdded:
OnSolutionAdded(e.NewSolution);
EnqueueEvent(e.NewSolution, InvocationReasons.DocumentAdded, asyncToken);
break;
case WorkspaceChangeKind.SolutionRemoved:
......@@ -334,32 +332,6 @@ private void ProcessSolutionEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncT
}
}
private void OnSolutionAdded(Solution solution)
{
var asyncToken = _listener.BeginAsyncOperation("OnSolutionAdded");
_eventProcessingQueue.ScheduleTask(() =>
{
var semanticVersionTrackingService = solution.Workspace.Services.GetService<ISemanticVersionTrackingService>();
if (semanticVersionTrackingService != null)
{
semanticVersionTrackingService.LoadInitialSemanticVersions(solution);
}
}, _shutdownToken).CompletesAsyncOperation(asyncToken);
}
private void OnProjectAdded(Project project)
{
var asyncToken = _listener.BeginAsyncOperation("OnProjectAdded");
_eventProcessingQueue.ScheduleTask(() =>
{
var semanticVersionTrackingService = project.Solution.Workspace.Services.GetService<ISemanticVersionTrackingService>();
if (semanticVersionTrackingService != null)
{
semanticVersionTrackingService.LoadInitialSemanticVersions(project);
}
}, _shutdownToken).CompletesAsyncOperation(asyncToken);
}
private void EnqueueEvent(Solution oldSolution, Solution newSolution, IAsyncToken asyncToken)
{
_eventProcessingQueue.ScheduleTask(
......
......@@ -8,11 +8,27 @@
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.TodoComments
{
internal abstract class AbstractTodoCommentService : ITodoCommentService
{
// we hold onto workspace to make sure given input (Document) belong to right workspace.
// since remote host is from workspace service, different workspace can have different expectation
// on remote host, so we need to make sure given input always belong to right workspace where
// the session belong to.
private readonly Workspace _workspace;
private readonly SemaphoreSlim _gate;
private KeepAliveSession _sessionDoNotAccessDirectly;
protected AbstractTodoCommentService(Workspace workspace)
{
_gate = new SemaphoreSlim(initialCount: 1);
_workspace = workspace;
}
protected abstract bool PreprocessorHasComment(SyntaxTrivia trivia);
protected abstract bool IsSingleLineComment(SyntaxTrivia trivia);
protected abstract bool IsMultilineComment(SyntaxTrivia trivia);
......@@ -24,6 +40,9 @@ internal abstract class AbstractTodoCommentService : ITodoCommentService
public async Task<IList<TodoComment>> GetTodoCommentsAsync(Document document, ImmutableArray<TodoCommentDescriptor> commentDescriptors, CancellationToken cancellationToken)
{
// make sure given input is right one
Contract.ThrowIfFalse(_workspace == document.Project.Solution.Workspace);
// same service run in both inproc and remote host, but remote host will not have RemoteHostClient service,
// so inproc one will always run
var client = await document.Project.Solution.Workspace.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
......@@ -42,9 +61,29 @@ public async Task<IList<TodoComment>> GetTodoCommentsAsync(Document document, Im
private async Task<IList<TodoComment>> GetTodoCommentsInRemoteHostAsync(
RemoteHostClient client, Document document, ImmutableArray<TodoCommentDescriptor> commentDescriptors, CancellationToken cancellationToken)
{
return await client.RunCodeAnalysisServiceOnRemoteHostAsync<IList<TodoComment>>(
document.Project.Solution, nameof(IRemoteTodoCommentService.GetTodoCommentsAsync),
new object[] { document.Id, commentDescriptors }, cancellationToken).ConfigureAwait(false);
var keepAliveSession = await TryGetKeepAliveSessionAsync(client, cancellationToken).ConfigureAwait(false);
var result = await keepAliveSession.TryInvokeAsync<IList<TodoComment>>(
nameof(IRemoteTodoCommentService.GetTodoCommentsAsync),
document.Project.Solution,
new object[] { document.Id, commentDescriptors }).ConfigureAwait(false);
return result ?? SpecializedCollections.EmptyList<TodoComment>();
}
private async Task<KeepAliveSession> TryGetKeepAliveSessionAsync(RemoteHostClient client, CancellationToken cancellation)
{
using (await _gate.DisposableWaitAsync(cancellation).ConfigureAwait(false))
{
if (_sessionDoNotAccessDirectly == null)
{
// we give cancellation none here since the cancellation will cause KeepAlive session to be shutdown
// when raised
_sessionDoNotAccessDirectly = await client.TryCreateCodeAnalysisKeepAliveSessionAsync(CancellationToken.None).ConfigureAwait(false);
}
return _sessionDoNotAccessDirectly;
}
}
private async Task<IList<TodoComment>> GetTodoCommentsInCurrentProcessAsync(
......
......@@ -3,14 +3,28 @@
Imports System.Collections.Immutable
Imports System.Composition
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.TodoComments
Namespace Microsoft.CodeAnalysis.VisualBasic.TodoComments
<ExportLanguageService(GetType(ITodoCommentService), LanguageNames.VisualBasic), [Shared]>
<ExportLanguageServiceFactory(GetType(ITodoCommentService), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicTodoCommentServiceFactory
Implements ILanguageServiceFactory
Public Function CreateLanguageService(languageServices As HostLanguageServices) As ILanguageService Implements ILanguageServiceFactory.CreateLanguageService
Return New VisualBasicTodoCommentService(languageServices.WorkspaceServices.Workspace)
End Function
End Class
Friend Class VisualBasicTodoCommentService
Inherits AbstractTodoCommentService
Public Sub New(workspace As Workspace)
MyBase.New(workspace)
End Sub
Protected Overrides Sub AppendTodoComments(commentDescriptors As ImmutableArray(Of TodoCommentDescriptor), document As SyntacticDocument, trivia As SyntaxTrivia, todoList As List(Of TodoComment))
If PreprocessorHasComment(trivia) Then
Dim commentTrivia = trivia.GetStructure().DescendantTrivia().First(Function(t) t.RawKind = SyntaxKind.CommentTrivia)
......@@ -39,7 +53,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.TodoComments
' 3 for REM
Dim index = GetFirstCharacterIndex(message)
If index >= message.Length OrElse
index > message.Length - 3 Then
index > message.Length - 3 Then
Return index
End If
......@@ -91,7 +105,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.TodoComments
Protected Overrides Function PreprocessorHasComment(trivia As SyntaxTrivia) As Boolean
Return SyntaxFacts.IsPreprocessorDirective(CType(trivia.RawKind, SyntaxKind)) AndAlso
trivia.GetStructure().DescendantTrivia().Any(Function(t) t.RawKind = SyntaxKind.CommentTrivia)
trivia.GetStructure().DescendantTrivia().Any(Function(t) t.RawKind = SyntaxKind.CommentTrivia)
End Function
' TODO: remove this if SyntaxFacts.IsSingleQuote become public
......
......@@ -129,7 +129,7 @@ public async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, In
}
}
private static async Task<RemoteHostClient.Session> TryGetRemoteSessionAsync(
private static async Task<SessionWithSolution> TryGetRemoteSessionAsync(
Solution solution, CancellationToken cancellationToken)
{
var client = await solution.Workspace.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
......@@ -138,7 +138,7 @@ public async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, In
return null;
}
return await client.TryCreateCodeAnalysisServiceSessionAsync(
return await client.TryCreateCodeAnalysisSessionAsync(
solution, cancellationToken).ConfigureAwait(false);
}
......
......@@ -137,11 +137,6 @@ public void Disable()
client?.Shutdown();
}
public Task<RemoteHostClient> GetRemoteHostClientAsync(CancellationToken cancellationToken)
{
return TryGetRemoteHostClientAsync(cancellationToken);
}
public Task<RemoteHostClient> TryGetRemoteHostClientAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
......@@ -171,13 +166,13 @@ private async Task<RemoteHostClient> EnableAsync(CancellationToken cancellationT
return null;
}
client.ConnectionChanged += OnConnectionChanged;
client.StatusChanged += OnStatusChanged;
// set global assets on remote host
var checksums = AddGlobalAssets(cancellationToken);
// send over global asset
await client.RunOnRemoteHostAsync(
await client.TryRunRemoteAsync(
WellKnownRemoteHostServices.RemoteHostService, _workspace.CurrentSolution,
nameof(IRemoteHostService.SynchronizeGlobalAssetsAsync),
(object)checksums, cancellationToken).ConfigureAwait(false);
......@@ -219,9 +214,9 @@ private void RemoveGlobalAssets()
}
}
private void OnConnectionChanged(object sender, bool connected)
private void OnStatusChanged(object sender, bool started)
{
if (connected)
if (started)
{
return;
}
......@@ -288,7 +283,7 @@ public async Task RequestNewRemoteHostAsync(CancellationToken cancellationToken)
Logger.Log(FunctionId.RemoteHostClientService_Restarted, KeyValueLogMessage.NoProperty);
// we are going to kill the existing remote host, connection change is expected
existingClient.ConnectionChanged -= OnConnectionChanged;
existingClient.StatusChanged -= OnStatusChanged;
lock (_gate)
{
......
......@@ -109,23 +109,9 @@ private void EnqueueChecksumUpdate()
_event.Release();
}
private async Task SynchronizePrimaryWorkspaceAsync(CancellationToken cancellationToken)
private Task SynchronizePrimaryWorkspaceAsync(CancellationToken cancellationToken)
{
var remoteHostClient = await _service.GetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
if (remoteHostClient == null)
{
return;
}
using (Logger.LogBlock(FunctionId.SolutionChecksumUpdater_SynchronizePrimaryWorkspace, cancellationToken))
{
var solution = _service.Workspace.CurrentSolution;
var checksum = await solution.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false);
await remoteHostClient.RunOnRemoteHostAsync(
WellKnownRemoteHostServices.RemoteHostService, solution,
nameof(IRemoteHostService.SynchronizePrimaryWorkspaceAsync), checksum, cancellationToken).ConfigureAwait(false);
}
return _service.Workspace.SynchronizePrimaryWorkspaceAsync(_service.Workspace.CurrentSolution, cancellationToken);
}
private static void CancelAndDispose(CancellationTokenSource cancellationSource)
......
// 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.Composition;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Versions;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Versions
{
/// <summary>
/// this service tracks semantic version changes as solution changes and provide a way to get back to initial project/semantic version
/// pairs at the solution load
/// </summary>
[ExportWorkspaceService(typeof(ISemanticVersionTrackingService), ServiceLayer.Host), Shared]
internal class SemanticVersionTrackingService : ISemanticVersionTrackingService
{
private const int SerializationFormat = 1;
private const string SemanticVersion = nameof(SemanticVersion);
private const string DependentSemanticVersion = nameof(DependentSemanticVersion);
private static readonly ConditionalWeakTable<ProjectId, Versions> s_initialSemanticVersions = new ConditionalWeakTable<ProjectId, Versions>();
private static readonly ConditionalWeakTable<ProjectId, Versions> s_initialDependentSemanticVersions = new ConditionalWeakTable<ProjectId, Versions>();
public VersionStamp GetInitialProjectVersionFromSemanticVersion(Project project, VersionStamp semanticVersion)
{
if (!TryGetInitialVersions(s_initialSemanticVersions, project, SemanticVersion, out var versions))
{
return VersionStamp.Default;
}
if (!VersionStamp.CanReusePersistedVersion(semanticVersion, versions.SemanticVersion))
{
return VersionStamp.Default;
}
return versions.ProjectVersion;
}
public VersionStamp GetInitialDependentProjectVersionFromDependentSemanticVersion(Project project, VersionStamp dependentSemanticVersion)
{
if (!TryGetInitialVersions(s_initialDependentSemanticVersions, project, DependentSemanticVersion, out var versions))
{
return VersionStamp.Default;
}
if (!VersionStamp.CanReusePersistedVersion(dependentSemanticVersion, versions.SemanticVersion))
{
return VersionStamp.Default;
}
return versions.ProjectVersion;
}
private bool TryGetInitialVersions(ConditionalWeakTable<ProjectId, Versions> initialVersionMap, Project project, string keyName, out Versions versions)
{
// if we already loaded this, return it.
if (initialVersionMap.TryGetValue(project.Id, out versions))
{
return true;
}
// otherwise, load it
return TryLoadInitialVersions(initialVersionMap, project, keyName, out versions);
}
public void LoadInitialSemanticVersions(Solution solution)
{
foreach (var project in solution.Projects)
{
LoadInitialSemanticVersions(project);
}
}
public void LoadInitialSemanticVersions(Project project)
{
if (!s_initialSemanticVersions.TryGetValue(project.Id, out var unused))
{
PersistedVersionStampLogger.LogProject();
if (TryLoadInitialVersions(s_initialSemanticVersions, project, SemanticVersion, out unused))
{
PersistedVersionStampLogger.LogInitialSemanticVersion();
}
}
if (!s_initialDependentSemanticVersions.TryGetValue(project.Id, out unused) &&
TryLoadInitialVersions(s_initialDependentSemanticVersions, project, DependentSemanticVersion, out unused))
{
PersistedVersionStampLogger.LogInitialDependentSemanticVersion();
}
}
private bool TryLoadInitialVersions(ConditionalWeakTable<ProjectId, Versions> initialVersionMap, Project project, string keyName, out Versions versions)
{
var result = TryReadFrom(project, keyName, out versions);
if (result)
{
Versions save = versions;
initialVersionMap.GetValue(project.Id, _ => save);
return true;
}
initialVersionMap.GetValue(project.Id, _ => Versions.Default);
return false;
}
private static bool TryReadFrom(Project project, string keyName, out Versions versions)
{
versions = default(Versions);
var service = project.Solution.Workspace.Services.GetService<IPersistentStorageService>();
if (service == null)
{
return false;
}
try
{
using (var storage = service.GetStorage(project.Solution))
using (var stream = storage.ReadStreamAsync(keyName, CancellationToken.None).WaitAndGetResult(CancellationToken.None))
using (var reader = ObjectReader.TryGetReader(stream))
{
if (reader != null)
{
var formatVersion = reader.ReadInt32();
if (formatVersion == SerializationFormat)
{
var persistedProjectVersion = VersionStamp.ReadFrom(reader);
var persistedSemanticVersion = VersionStamp.ReadFrom(reader);
versions = new Versions(persistedProjectVersion, persistedSemanticVersion);
return true;
}
}
}
}
catch (Exception e) when (IOUtilities.IsNormalIOException(e))
{
}
return false;
}
public async Task RecordSemanticVersionsAsync(Project project, CancellationToken cancellationToken)
{
var service = project.Solution.Workspace.Services.GetService<IPersistentStorageService>();
if (service == null)
{
return;
}
using (var storage = service.GetStorage(project.Solution))
{
// first update semantic and dependent semantic version of this project
await WriteToSemanticVersionAsync(storage, project, cancellationToken).ConfigureAwait(false);
await WriteToDependentSemanticVersionAsync(storage, project, cancellationToken).ConfigureAwait(false);
// next update dependent semantic version fo all projects that depend on this project.
var projectIds = project.Solution.GetProjectDependencyGraph().GetProjectsThatTransitivelyDependOnThisProject(project.Id);
foreach (var projectId in projectIds)
{
cancellationToken.ThrowIfCancellationRequested();
var currentProject = project.Solution.GetProject(projectId);
if (currentProject == null)
{
continue;
}
await WriteToDependentSemanticVersionAsync(storage, currentProject, cancellationToken).ConfigureAwait(false);
}
}
}
private static async Task WriteToSemanticVersionAsync(IPersistentStorage storage, Project project, CancellationToken cancellationToken)
{
var projectVersion = await project.GetVersionAsync(cancellationToken).ConfigureAwait(false);
var semanticVersion = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
await WriteToVersionAsync(storage, SemanticVersion, projectVersion, semanticVersion, cancellationToken).ConfigureAwait(false);
}
private static async Task WriteToDependentSemanticVersionAsync(IPersistentStorage storage, Project project, CancellationToken cancellationToken)
{
var projectVersion = await project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false);
var semanticVersion = await project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
await WriteToVersionAsync(storage, DependentSemanticVersion, projectVersion, semanticVersion, cancellationToken).ConfigureAwait(false);
}
private static async Task WriteToVersionAsync(
IPersistentStorage storage, string keyName, VersionStamp projectVersion, VersionStamp semanticVersion, CancellationToken cancellationToken)
{
using (var stream = SerializableBytes.CreateWritableStream())
using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken))
{
writer.WriteInt32(SerializationFormat);
projectVersion.WriteTo(writer);
semanticVersion.WriteTo(writer);
stream.Position = 0;
await storage.WriteStreamAsync(keyName, stream, cancellationToken).ConfigureAwait(false);
}
}
private class Versions
{
public static readonly Versions Default = new Versions(VersionStamp.Default, VersionStamp.Default);
public readonly VersionStamp ProjectVersion;
public readonly VersionStamp SemanticVersion;
public Versions(VersionStamp projectVersion, VersionStamp semanticVersion)
{
this.ProjectVersion = projectVersion;
this.SemanticVersion = semanticVersion;
}
}
}
}
......@@ -159,7 +159,6 @@
<Compile Include="Implementation\Utilities\AutomationDelegatingListView.cs" />
<Compile Include="Implementation\Utilities\HyperlinkStyleHelper.cs" />
<Compile Include="Implementation\Venus\VenusTaskExtensions.cs" />
<Compile Include="Implementation\Versions\SemanticVersionTrackingService.cs" />
<Compile Include="Implementation\ContainedLanguageRefactorNotifyService.cs" />
<Compile Include="Implementation\VirtualMemoryNotificationListener.cs" />
<Compile Include="Implementation\Watson\WatsonReporter.cs" />
......
......@@ -34,7 +34,7 @@ internal sealed class RemoteCodeLensReferencesService : ICodeLensReferencesServi
}
// TODO: send telemetry on session
return await remoteHostClient.RunCodeAnalysisServiceOnRemoteHostAsync<ReferenceCount>(
return await remoteHostClient.TryRunCodeAnalysisRemoteAsync<ReferenceCount>(
solution, WellKnownServiceHubServices.CodeAnalysisService_GetReferenceCountAsync,
new object[] { documentId, syntaxNode.Span, maxSearchResults }, cancellationToken).ConfigureAwait(false);
}
......@@ -58,7 +58,7 @@ internal sealed class RemoteCodeLensReferencesService : ICodeLensReferencesServi
}
// TODO: send telemetry on session
return await remoteHostClient.RunCodeAnalysisServiceOnRemoteHostAsync<IEnumerable<ReferenceLocationDescriptor>>(
return await remoteHostClient.TryRunCodeAnalysisRemoteAsync<IEnumerable<ReferenceLocationDescriptor>>(
solution, WellKnownServiceHubServices.CodeAnalysisService_FindReferenceLocationsAsync,
new object[] { documentId, syntaxNode.Span }, cancellationToken).ConfigureAwait(false);
}
......@@ -82,7 +82,7 @@ internal sealed class RemoteCodeLensReferencesService : ICodeLensReferencesServi
}
// TODO: send telemetry on session
return await remoteHostClient.RunCodeAnalysisServiceOnRemoteHostAsync<IEnumerable<ReferenceMethodDescriptor>>(
return await remoteHostClient.TryRunCodeAnalysisRemoteAsync<IEnumerable<ReferenceMethodDescriptor>>(
solution, WellKnownServiceHubServices.CodeAnalysisService_FindReferenceMethodsAsync,
new object[] { documentId, syntaxNode.Span }, cancellationToken).ConfigureAwait(false);
}
......@@ -106,7 +106,7 @@ internal sealed class RemoteCodeLensReferencesService : ICodeLensReferencesServi
}
// TODO: send telemetry on session
return await remoteHostClient.RunCodeAnalysisServiceOnRemoteHostAsync<string>(
return await remoteHostClient.TryRunCodeAnalysisRemoteAsync<string>(
solution, WellKnownServiceHubServices.CodeAnalysisService_GetFullyQualifiedName,
new object[] { documentId, syntaxNode.Span }, cancellationToken).ConfigureAwait(false);
}
......
......@@ -14,89 +14,35 @@
namespace Microsoft.VisualStudio.LanguageServices.Remote
{
internal class JsonRpcSession : RemoteHostClient.Session
internal class ServiceHubJsonRpcConnection : RemoteHostClient.Connection
{
private static int s_sessionId = 1;
// current session id
private readonly int _currentSessionId;
// communication channel related to service information
private readonly ServiceJsonRpcClient _serviceClient;
// communication channel related to snapshot information
private readonly SnapshotJsonRpcClient _snapshotClientOpt;
private readonly SnapshotJsonRpcClient _snapshotClient;
// close connection when cancellation has raised
private readonly CancellationTokenRegistration _cancellationRegistration;
public static async Task<JsonRpcSession> CreateAsync(
Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync,
object callbackTarget,
Stream serviceStream,
Stream snapshotStreamOpt,
CancellationToken cancellationToken)
{
var snapshot = getSnapshotAsync.Value == null ? null : await getSnapshotAsync.Value(cancellationToken).ConfigureAwait(false);
JsonRpcSession session;
try
{
session = new JsonRpcSession(snapshot, callbackTarget, serviceStream, snapshotStreamOpt, cancellationToken);
}
catch
{
snapshot?.Dispose();
throw;
}
try
{
await session.InitializeAsync().ConfigureAwait(false);
}
catch when (!cancellationToken.IsCancellationRequested)
{
// The session disposes of itself when cancellation is requested.
session.Dispose();
throw;
}
return session;
}
private JsonRpcSession(
PinnedRemotableDataScope snapshot,
public ServiceHubJsonRpcConnection(
object callbackTarget,
Stream serviceStream,
Stream snapshotStreamOpt,
Stream snapshotStream,
CancellationToken cancellationToken) :
base(snapshot, cancellationToken)
base(cancellationToken)
{
Contract.Requires((snapshot == null) == (snapshotStreamOpt == null));
// get session id
_currentSessionId = Interlocked.Increment(ref s_sessionId);
_serviceClient = new ServiceJsonRpcClient(serviceStream, callbackTarget, cancellationToken);
_snapshotClientOpt = snapshot == null ? null : new SnapshotJsonRpcClient(this, snapshotStreamOpt, cancellationToken);
_snapshotClient = new SnapshotJsonRpcClient(this, snapshotStream, cancellationToken);
// dispose session when cancellation has raised
_cancellationRegistration = CancellationToken.Register(Dispose);
}
private async Task InitializeAsync()
protected override async Task OnRegisterPinnedRemotableDataScopeAsync(PinnedRemotableDataScope scope)
{
// 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, primaryBranch, solutionChecksum).ConfigureAwait(false);
}
await _serviceClient.InvokeAsync(WellKnownServiceHubServices.ServiceHubServiceBase_Initialize, _currentSessionId, primaryBranch, solutionChecksum).ConfigureAwait(false);
await _snapshotClient.InvokeAsync(WellKnownServiceHubServices.ServiceHubServiceBase_Initialize, scope.SolutionInfo).ConfigureAwait(false);
await _serviceClient.InvokeAsync(WellKnownServiceHubServices.ServiceHubServiceBase_Initialize, scope.SolutionInfo).ConfigureAwait(false);
}
public override Task InvokeAsync(string targetName, params object[] arguments)
......@@ -121,12 +67,14 @@ public override Task<T> InvokeAsync<T>(string targetName, IEnumerable<object> ar
protected override void OnDisposed()
{
base.OnDisposed();
// dispose cancellation registration
_cancellationRegistration.Dispose();
// dispose service and snapshot channels
_serviceClient.Dispose();
_snapshotClientOpt?.Dispose();
_snapshotClient.Dispose();
}
/// <summary>
......@@ -160,39 +108,34 @@ public ServiceJsonRpcClient(Stream stream, object callbackTarget, CancellationTo
/// </summary>
private class SnapshotJsonRpcClient : JsonRpcClient
{
private readonly JsonRpcSession _owner;
private readonly ServiceHubJsonRpcConnection _owner;
private readonly CancellationTokenSource _source;
public SnapshotJsonRpcClient(JsonRpcSession owner, Stream stream, CancellationToken cancellationToken)
public SnapshotJsonRpcClient(ServiceHubJsonRpcConnection owner, Stream stream, CancellationToken cancellationToken)
: base(stream, callbackTarget: null, useThisAsCallback: true, cancellationToken: cancellationToken)
{
Contract.ThrowIfNull(owner.PinnedScopeOpt);
_owner = owner;
_source = new CancellationTokenSource();
StartListening();
}
private PinnedRemotableDataScope PinnedScope => _owner.PinnedScopeOpt;
/// <summary>
/// this is callback from remote host side to get asset associated with checksum from VS.
/// </summary>
public async Task RequestAssetAsync(int sessionId, Checksum[] checksums, string streamName)
public async Task RequestAssetAsync(int scopeId, Checksum[] checksums, string streamName)
{
try
{
Contract.ThrowIfFalse(_owner._currentSessionId == sessionId);
using (Logger.LogBlock(FunctionId.JsonRpcSession_RequestAssetAsync, streamName, _source.Token))
using (var stream = await DirectStream.GetAsync(streamName, _source.Token).ConfigureAwait(false))
{
var scope = _owner.PinnedRemotableDataScope;
using (var writer = new ObjectWriter(stream))
{
writer.WriteInt32(sessionId);
writer.WriteInt32(scopeId);
await WriteAssetAsync(writer, checksums).ConfigureAwait(false);
await WriteAssetAsync(writer, scope, checksums).ConfigureAwait(false);
}
await stream.FlushAsync(_source.Token).ConfigureAwait(false);
......@@ -210,7 +153,7 @@ public async Task RequestAssetAsync(int sessionId, Checksum[] checksums, string
}
}
private async Task WriteAssetAsync(ObjectWriter writer, Checksum[] checksums)
private async Task WriteAssetAsync(ObjectWriter writer, PinnedRemotableDataScope scope, Checksum[] checksums)
{
// special case
if (checksums.Length == 0)
......@@ -221,11 +164,11 @@ private async Task WriteAssetAsync(ObjectWriter writer, Checksum[] checksums)
if (checksums.Length == 1)
{
await WriteOneAssetAsync(writer, checksums[0]).ConfigureAwait(false);
await WriteOneAssetAsync(writer, scope, checksums[0]).ConfigureAwait(false);
return;
}
await WriteMultipleAssetsAsync(writer, checksums).ConfigureAwait(false);
await WriteMultipleAssetsAsync(writer, scope, checksums).ConfigureAwait(false);
}
private Task WriteNoAssetAsync(ObjectWriter writer)
......@@ -234,9 +177,9 @@ private Task WriteNoAssetAsync(ObjectWriter writer)
return SpecializedTasks.EmptyTask;
}
private async Task WriteOneAssetAsync(ObjectWriter writer, Checksum checksum)
private async Task WriteOneAssetAsync(ObjectWriter writer, PinnedRemotableDataScope scope, Checksum checksum)
{
var remotableData = PinnedScope.GetRemotableData(checksum, _source.Token) ?? RemotableData.Null;
var remotableData = scope.GetRemotableData(checksum, _source.Token) ?? RemotableData.Null;
writer.WriteInt32(1);
checksum.WriteTo(writer);
......@@ -245,9 +188,9 @@ private async Task WriteOneAssetAsync(ObjectWriter writer, Checksum checksum)
await remotableData.WriteObjectToAsync(writer, _source.Token).ConfigureAwait(false);
}
private async Task WriteMultipleAssetsAsync(ObjectWriter writer, Checksum[] checksums)
private async Task WriteMultipleAssetsAsync(ObjectWriter writer, PinnedRemotableDataScope scope, Checksum[] checksums)
{
var remotableDataMap = PinnedScope.GetRemotableData(checksums, _source.Token);
var remotableDataMap = scope.GetRemotableData(checksums, _source.Token);
writer.WriteInt32(remotableDataMap.Count);
foreach (var kv in remotableDataMap)
......
......@@ -57,7 +57,7 @@ private async Task RegisterPrimarySolutionAsync()
_currentSolutionId = _workspace.CurrentSolution.Id;
var solutionId = _currentSolutionId;
using (var session = await _client.TryCreateServiceSessionAsync(WellKnownRemoteHostServices.RemoteHostService, _workspace.CurrentSolution, CancellationToken.None).ConfigureAwait(false))
using (var session = await _client.TryCreateSessionAsync(WellKnownRemoteHostServices.RemoteHostService, _workspace.CurrentSolution, CancellationToken.None).ConfigureAwait(false))
{
if (session == null)
{
......@@ -98,7 +98,7 @@ public void OnSolutionRemoved()
private async Task UnregisterPrimarySolutionAsync(
SolutionId solutionId, bool synchronousShutdown)
{
await _client.RunOnRemoteHostAsync(
await _client.TryRunRemoteAsync(
WellKnownRemoteHostServices.RemoteHostService, _workspace.CurrentSolution,
nameof(IRemoteHostService.UnregisterPrimarySolutionId), new object[] { solutionId, synchronousShutdown },
CancellationToken.None).ConfigureAwait(false);
......
......@@ -54,7 +54,7 @@ internal sealed partial class ServiceHubRemoteHostClient : RemoteHostClient
// TODO: change this to non fatal watson and make VS to use inproc implementation
Contract.ThrowIfFalse(host == current.ToString());
instance.Connected();
instance.Started();
// Create a workspace host to hear about workspace changes. We'll
// remote those changes over to the remote side when they happen.
......@@ -115,26 +115,26 @@ private static async Task RegisterWorkspaceHostAsync(Workspace workspace, Remote
_rpc.StartListening();
}
protected override async Task<Session> TryCreateServiceSessionAsync(string serviceName, Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync, object callbackTarget, CancellationToken cancellationToken)
public override async Task<Connection> TryCreateConnectionAsync(string serviceName, object callbackTarget, CancellationToken cancellationToken)
{
// get stream from service hub to communicate snapshot/asset related information
// this is the back channel the system uses to move data between VS and remote host for solution related information
var snapshotStream = getSnapshotAsync.Value == null ? null : await RequestServiceAsync(_hubClient, WellKnownServiceHubServices.SnapshotService, _hostGroup, _timeout, cancellationToken).ConfigureAwait(false);
var snapshotStream = await RequestServiceAsync(_hubClient, WellKnownServiceHubServices.SnapshotService, _hostGroup, _timeout, cancellationToken).ConfigureAwait(false);
// get stream from service hub to communicate service specific information
// this is what consumer actually use to communicate information
var serviceStream = await RequestServiceAsync(_hubClient, serviceName, _hostGroup, _timeout, cancellationToken).ConfigureAwait(false);
return await JsonRpcSession.CreateAsync(getSnapshotAsync, callbackTarget, serviceStream, snapshotStream, cancellationToken).ConfigureAwait(false);
return new ServiceHubJsonRpcConnection(callbackTarget, serviceStream, snapshotStream, cancellationToken);
}
protected override void OnConnected()
protected override void OnStarted()
{
}
protected override void OnDisconnected()
protected override void OnStopped()
{
// we are asked to disconnect. unsubscribe and dispose to disconnect.
// we are asked to stop. unsubscribe and dispose to disconnect.
// there are 2 ways to get disconnected. one is Roslyn decided to disconnect with RemoteHost (ex, cancellation or recycle OOP) and
// the other is external thing disconnecting remote host from us (ex, user killing OOP process).
// the Disconnected event we subscribe is to detect #2 case. and this method is for #1 case. so when we are willingly disconnecting
......@@ -145,7 +145,7 @@ protected override void OnDisconnected()
private void OnRpcDisconnected(object sender, JsonRpcDisconnectedEventArgs e)
{
Disconnected();
Stopped();
}
private static async Task<Stream> RequestServiceAsync(
......
......@@ -171,7 +171,7 @@
<Compile Include="ProjectSystem\DeferredProjectWorkspaceService.cs" />
<Compile Include="Remote\JsonRpcClient.cs" />
<Compile Include="Remote\JsonRpcMessageHandler.cs" />
<Compile Include="Remote\JsonRpcSession.cs" />
<Compile Include="Remote\JsonRpcConnection.cs" />
<Compile Include="Remote\RemoteHostClientFactory.cs" />
<Compile Include="Remote\ServiceHubRemoteHostClient.WorkspaceHost.cs" />
<Compile Include="Remote\ServiceHubRemoteHostClient.cs" />
......
......@@ -3,12 +3,12 @@
extern alias hub;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.TodoComments;
using Newtonsoft.Json;
......@@ -127,6 +127,15 @@ public void TestTodoCommentList()
});
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public void TestPinnedSolutionInfo()
{
VerifyJsonSerialization(new PinnedSolutionInfo(scopeId: 10, fromPrimaryBranch: false, solutionChecksum: new Checksum(new byte[] { 1, 2, 3, 4, 5, 6 })), (x, y) =>
{
return (x.ScopeId == y.ScopeId && x.FromPrimaryBranch == y.FromPrimaryBranch && x.SolutionChecksum == y.SolutionChecksum) ? 0 : 1;
});
}
private static void VerifyJsonSerialization<T>(T value, Comparison<T> equality = null)
{
var serializer = new JsonSerializer();
......
......@@ -41,12 +41,12 @@ public async Task Enable_Disable()
service.Enable();
var enabledClient = await service.GetRemoteHostClientAsync(CancellationToken.None);
var enabledClient = await service.TryGetRemoteHostClientAsync(CancellationToken.None);
Assert.NotNull(enabledClient);
service.Disable();
var disabledClient = await service.GetRemoteHostClientAsync(CancellationToken.None);
var disabledClient = await service.TryGetRemoteHostClientAsync(CancellationToken.None);
Assert.Null(disabledClient);
}
......@@ -61,7 +61,7 @@ public async Task GlobalAssets()
service.Enable();
// make sure client is ready
var client = await service.GetRemoteHostClientAsync(CancellationToken.None);
var client = await service.TryGetRemoteHostClientAsync(CancellationToken.None);
var checksumService = workspace.Services.GetService<ISolutionSynchronizationService>();
var asset = checksumService.GetGlobalAsset(analyzerReference, CancellationToken.None);
......@@ -84,7 +84,7 @@ public async Task SynchronizeGlobalAssets()
service.Enable();
// make sure client is ready
var client = await service.GetRemoteHostClientAsync(CancellationToken.None) as InProcRemoteHostClient;
var client = await service.TryGetRemoteHostClientAsync(CancellationToken.None) as InProcRemoteHostClient;
Assert.Equal(1, client.AssetStorage.GetGlobalAssetsOfType<AnalyzerReference>(CancellationToken.None).Count());
......@@ -107,7 +107,7 @@ public async Task UpdaterService()
service.Enable();
// make sure client is ready
var client = await service.GetRemoteHostClientAsync(CancellationToken.None);
var client = await service.TryGetRemoteHostClientAsync(CancellationToken.None);
// add solution
workspace.AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Default));
......@@ -134,11 +134,14 @@ public async Task TestSessionWithNoSolution()
service.Enable();
var mock = new MockLogService();
var client = await service.GetRemoteHostClientAsync(CancellationToken.None);
using (var session = await client.TryCreateServiceSessionAsync(WellKnownServiceHubServices.RemoteSymbolSearchUpdateEngine, mock, CancellationToken.None))
{
await session.InvokeAsync(nameof(IRemoteSymbolSearchUpdateEngine.UpdateContinuouslyAsync), "emptySource", Path.GetTempPath());
}
var client = await service.TryGetRemoteHostClientAsync(CancellationToken.None);
var session = await client.TryCreateKeepAliveSessionAsync(WellKnownServiceHubServices.RemoteSymbolSearchUpdateEngine, mock, CancellationToken.None);
var result = await session.TryInvokeAsync(nameof(IRemoteSymbolSearchUpdateEngine.UpdateContinuouslyAsync), "emptySource", Path.GetTempPath());
Assert.True(result);
session.Shutdown();
service.Disable();
}
......@@ -152,8 +155,8 @@ public async Task TestRequestNewRemoteHost()
var completionTask = new TaskCompletionSource<bool>();
var client1 = await service.GetRemoteHostClientAsync(CancellationToken.None);
client1.ConnectionChanged += (s, connected) =>
var client1 = await service.TryGetRemoteHostClientAsync(CancellationToken.None);
client1.StatusChanged += (s, connected) =>
{
// mark done
completionTask.SetResult(connected);
......@@ -164,7 +167,7 @@ public async Task TestRequestNewRemoteHost()
var result = await completionTask.Task;
Assert.False(result);
var client2 = await service.GetRemoteHostClientAsync(CancellationToken.None);
var client2 = await service.TryGetRemoteHostClientAsync(CancellationToken.None);
Assert.NotEqual(client1, client2);
......
......@@ -73,7 +73,7 @@ public async Task TestTodoComments()
var solution = workspace.CurrentSolution;
var comments = await client.RunCodeAnalysisServiceOnRemoteHostAsync<IList<TodoComment>>(
var comments = await client.TryRunCodeAnalysisRemoteAsync<IList<TodoComment>>(
solution, nameof(IRemoteTodoCommentService.GetTodoCommentsAsync),
new object[] { solution.Projects.First().DocumentIds.First(), ImmutableArray.Create(new TodoCommentDescriptor("TODO", 0)) }, CancellationToken.None);
......@@ -93,7 +93,7 @@ class Test { }";
var solution = workspace.CurrentSolution;
var result = await client.RunCodeAnalysisServiceOnRemoteHostAsync<ImmutableArray<DesignerAttributeDocumentData>>(
var result = await client.TryRunCodeAnalysisRemoteAsync<ImmutableArray<DesignerAttributeDocumentData>>(
solution, nameof(IRemoteDesignerAttributeService.ScanDesignerAttributesAsync),
solution.Projects.First().Id, CancellationToken.None);
......@@ -110,7 +110,7 @@ public async Task TestRemoteHostSynchronizeGlobalAssets()
{
var client = (InProcRemoteHostClient)(await InProcRemoteHostClient.CreateAsync(workspace, runCacheCleanup: false, cancellationToken: CancellationToken.None));
await client.RunOnRemoteHostAsync(
await client.TryRunRemoteAsync(
WellKnownRemoteHostServices.RemoteHostService,
workspace.CurrentSolution,
nameof(IRemoteHostService.SynchronizeGlobalAssetsAsync),
......@@ -311,7 +311,7 @@ private static (Project, Document) GetProjectAndDocument(Solution solution, stri
private static async Task UpdatePrimaryWorkspace(InProcRemoteHostClient client, Solution solution)
{
await client.RunOnRemoteHostAsync(
await client.TryRunRemoteAsync(
WellKnownRemoteHostServices.RemoteHostService, solution,
nameof(IRemoteHostService.SynchronizePrimaryWorkspaceAsync),
await solution.State.GetChecksumAsync(CancellationToken.None), CancellationToken.None);
......
......@@ -146,7 +146,7 @@ void Method()
snapshotService.AddGlobalAsset(analyzerReference, asset, CancellationToken.None);
var client = await workspace.Services.GetService<IRemoteHostClientService>().TryGetRemoteHostClientAsync(CancellationToken.None);
await client.RunOnRemoteHostAsync(
await client.TryRunRemoteAsync(
WellKnownRemoteHostServices.RemoteHostService, workspace.CurrentSolution,
nameof(IRemoteHostService.SynchronizeGlobalAssetsAsync), (object)(new Checksum[] { asset.Checksum }), CancellationToken.None);
......
......@@ -23,7 +23,13 @@ internal RazorLangaugeServiceClient(RemoteHostClient client)
public async Task<Session> CreateSessionAsync(Solution solution, object callbackTarget = null, CancellationToken cancellationToken = default(CancellationToken))
{
var innerSession = await _client.TryCreateServiceSessionAsync(RazorServiceName, solution, callbackTarget, cancellationToken).ConfigureAwait(false);
if (solution == null)
{
// keep old behavior for Razor
return null;
}
var innerSession = await _client.TryCreateSessionAsync(RazorServiceName, solution, callbackTarget, cancellationToken).ConfigureAwait(false);
if (innerSession == null)
{
return null;
......@@ -34,9 +40,9 @@ public async Task<Session> CreateSessionAsync(Solution solution, object callback
public sealed class Session : IDisposable
{
private readonly RemoteHostClient.Session _inner;
private readonly SessionWithSolution _inner;
internal Session(RemoteHostClient.Session inner)
internal Session(SessionWithSolution inner)
{
_inner = inner;
}
......
......@@ -28,7 +28,7 @@ public sealed class Storage
// additional assets that is not part of solution but added explicitly
private ConcurrentDictionary<Checksum, CustomAsset> _additionalAssets;
public Storage(AssetStorages owner, SolutionState solutionState)
public Storage(SolutionState solutionState)
{
SolutionState = solutionState;
......
......@@ -56,7 +56,7 @@ public void RemoveGlobalAsset(object value, CancellationToken cancellationToken)
public Storage CreateStorage(SolutionState solutionState)
{
return new Storage(this, solutionState);
return new Storage(solutionState);
}
public RemotableData GetRemotableData(PinnedRemotableDataScope scope, Checksum checksum, CancellationToken cancellationToken)
......
......@@ -9,16 +9,47 @@
namespace Microsoft.CodeAnalysis.Execution
{
/// <summary>
/// Information related to pinned solution
/// </summary>
internal class PinnedSolutionInfo
{
/// <summary>
/// Unique ID for this pinned solution
///
/// This later used to find matching solution between VS and remote host
/// </summary>
public readonly int ScopeId;
/// <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 readonly bool FromPrimaryBranch;
public readonly Checksum SolutionChecksum;
public PinnedSolutionInfo(int scopeId, bool fromPrimaryBranch, Checksum solutionChecksum)
{
ScopeId = scopeId;
FromPrimaryBranch = fromPrimaryBranch;
SolutionChecksum = solutionChecksum;
}
}
/// <summary>
/// checksum scope that one can use to pin assets in memory while working on remote host
/// </summary>
internal sealed class PinnedRemotableDataScope : IDisposable
{
private static int s_scopeId = 1;
private readonly AssetStorages _storages;
private readonly AssetStorages.Storage _storage;
private bool _disposed;
public readonly Checksum SolutionChecksum;
public readonly PinnedSolutionInfo SolutionInfo;
public PinnedRemotableDataScope(
AssetStorages storages,
......@@ -30,20 +61,16 @@ internal sealed class PinnedRemotableDataScope : IDisposable
_storages = storages;
_storage = storage;
SolutionChecksum = solutionChecksum;
SolutionInfo = new PinnedSolutionInfo(
Interlocked.Increment(ref s_scopeId),
_storage.SolutionState.BranchId == Workspace.PrimaryBranchId,
solutionChecksum);
_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;
public Checksum SolutionChecksum => SolutionInfo.SolutionChecksum;
/// <summary>
/// Add asset that is not part of solution to be part of this snapshot.
......
......@@ -93,23 +93,20 @@ internal static partial class DeclarationFinder
private static async Task<(bool, ImmutableArray<SymbolAndProjectId>)> TryFindAllDeclarationsWithNormalQueryInRemoteProcessAsync(
Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken)
{
using (var session = await SymbolFinder.TryGetRemoteSessionAsync(
project.Solution, cancellationToken).ConfigureAwait(false))
{
if (session != null)
{
var result = await session.InvokeAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
nameof(IRemoteSymbolFinder.FindAllDeclarationsWithNormalQueryAsync),
project.Id, query.Name, query.Kind, criteria).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
var result = await project.Solution.TryRunCodeAnalysisRemoteAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
RemoteFeatureOptions.SymbolFinderEnabled,
nameof(IRemoteSymbolFinder.FindAllDeclarationsWithNormalQueryAsync),
new object[] { project.Id, query.Name, query.Kind, criteria }, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
}
if (result.IsDefault)
{
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
}
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
}
private static async Task<ImmutableArray<SymbolAndProjectId>> RehydrateAsync(
......
......@@ -114,64 +114,58 @@ internal static partial class DeclarationFinder
private static async Task<(bool, ImmutableArray<SymbolAndProjectId>)> TryFindSourceDeclarationsWithNormalQueryInRemoteProcessAsync(
Solution solution, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken)
{
using (var session = await SymbolFinder.TryGetRemoteSessionAsync(solution, cancellationToken).ConfigureAwait(false))
{
if (session != null)
{
var result = await session.InvokeAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
nameof(IRemoteSymbolFinder.FindSolutionSourceDeclarationsWithNormalQueryAsync),
name, ignoreCase, criteria).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
solution, result, cancellationToken).ConfigureAwait(false);
var result = await solution.TryRunCodeAnalysisRemoteAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
RemoteFeatureOptions.SymbolFinderEnabled,
nameof(IRemoteSymbolFinder.FindSolutionSourceDeclarationsWithNormalQueryAsync),
new object[] { name, ignoreCase, criteria }, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
}
if (result.IsDefault)
{
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
}
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
var rehydrated = await RehydrateAsync(
solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
}
private static async Task<(bool, ImmutableArray<SymbolAndProjectId>)> TryFindSourceDeclarationsWithNormalQueryInRemoteProcessAsync(
Project project, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken)
{
using (var session = await SymbolFinder.TryGetRemoteSessionAsync(project.Solution, cancellationToken).ConfigureAwait(false))
{
if (session != null)
{
var result = await session.InvokeAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithNormalQueryAsync),
project.Id, name, ignoreCase, criteria).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
var result = await project.Solution.TryRunCodeAnalysisRemoteAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
RemoteFeatureOptions.SymbolFinderEnabled,
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithNormalQueryAsync),
new object[] { project.Id, name, ignoreCase, criteria }, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
}
if (result.IsDefault)
{
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
}
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
}
private static async Task<(bool, ImmutableArray<SymbolAndProjectId>)> TryFindSourceDeclarationsWithPatternInRemoteProcessAsync(
Project project, string pattern, SymbolFilter criteria, CancellationToken cancellationToken)
{
using (var session = await SymbolFinder.TryGetRemoteSessionAsync(project.Solution, cancellationToken).ConfigureAwait(false))
{
if (session != null)
{
var result = await session.InvokeAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithPatternAsync),
project.Id, pattern, criteria).ConfigureAwait(false);
var result = await project.Solution.TryRunCodeAnalysisRemoteAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
RemoteFeatureOptions.SymbolFinderEnabled,
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithPatternAsync),
new object[] { project.Id, pattern, criteria }, cancellationToken).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
}
if (result.IsDefault)
{
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
}
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
}
#endregion
......
......@@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Remote;
namespace Microsoft.CodeAnalysis.FindSymbols
{
......@@ -52,20 +53,12 @@ public static partial class SymbolFinder
// the 'progress' parameter which will then update the UI.
var serverCallback = new FindLiteralsServerCallback(solution, progress, cancellationToken);
using (var session = await TryGetRemoteSessionAsync(
solution, serverCallback, cancellationToken).ConfigureAwait(false))
{
if (session == null)
{
return false;
}
await session.InvokeAsync(
nameof(IRemoteSymbolFinder.FindLiteralReferencesAsync),
value, typeCode).ConfigureAwait(false);
return true;
}
return await solution.TryRunCodeAnalysisRemoteAsync(
RemoteFeatureOptions.SymbolFinderEnabled,
serverCallback,
nameof(IRemoteSymbolFinder.FindLiteralReferencesAsync),
new object[] { value, typeCode },
cancellationToken).ConfigureAwait(false);
}
}
}
\ No newline at end of file
......@@ -67,19 +67,12 @@ public static partial class SymbolFinder
// the 'progress' parameter which will then update the UI.
var serverCallback = new FindReferencesServerCallback(solution, progress, cancellationToken);
using (var session = await TryGetRemoteSessionAsync(
solution, serverCallback, cancellationToken).ConfigureAwait(false))
{
if (session != null)
{
await session.InvokeAsync(
nameof(IRemoteSymbolFinder.FindReferencesAsync),
SerializableSymbolAndProjectId.Dehydrate(symbolAndProjectId),
documents?.Select(d => d.Id).ToArray()).ConfigureAwait(false);
return true;
}
}
return await solution.TryRunCodeAnalysisRemoteAsync(
RemoteFeatureOptions.SymbolFinderEnabled,
serverCallback,
nameof(IRemoteSymbolFinder.FindReferencesAsync),
new object[] { SerializableSymbolAndProjectId.Dehydrate(symbolAndProjectId), documents?.Select(d => d.Id).ToArray() },
cancellationToken).ConfigureAwait(false);
}
return false;
......
// 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;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Remote;
namespace Microsoft.CodeAnalysis.FindSymbols
{
public static partial class SymbolFinder
{
internal static Task<RemoteHostClient.Session> TryGetRemoteSessionAsync(
Solution solution, CancellationToken cancellationToken)
=> TryGetRemoteSessionAsync(solution, serverCallback: null, cancellationToken: cancellationToken);
private static Task<RemoteHostClient.Session> TryGetRemoteSessionAsync(
Solution solution, object serverCallback, CancellationToken cancellationToken)
{
return solution.TryCreateCodeAnalysisServiceSessionAsync(
RemoteFeatureOptions.SymbolFinderEnabled, serverCallback, cancellationToken);
}
}
}
\ No newline at end of file
......@@ -31,11 +31,6 @@ public RemoteHostClientService(Workspace workspace)
_lazyInstance = CreateNewLazyRemoteHostClient();
}
public Task<RemoteHostClient> GetRemoteHostClientAsync(CancellationToken cancellationToken)
{
return TryGetRemoteHostClientAsync(cancellationToken);
}
public Task<RemoteHostClient> TryGetRemoteHostClientAsync(CancellationToken cancellationToken)
{
if (_lazyInstance == null)
......@@ -48,7 +43,7 @@ public Task<RemoteHostClient> TryGetRemoteHostClientAsync(CancellationToken canc
public async Task RequestNewRemoteHostAsync(CancellationToken cancellationToken)
{
var instance = await GetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
var instance = await TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
if (instance == null)
{
return;
......
......@@ -18,20 +18,17 @@ internal interface IRemoteHostClientService : IWorkspaceService
/// this is designed to be not distruptive to existing callers and to support scenarioes where
/// features required to reload user extension dlls without re-launching VS.
///
/// if someone requests new remote host, all new callers for <see cref="GetRemoteHostClientAsync(CancellationToken)"/> will
/// if someone requests new remote host, all new callers for <see cref="TryGetRemoteHostClientAsync(CancellationToken)"/> will
/// receive a new remote host client that connects to a new remote host.
///
/// existing remoteHostClient will still remain connected to old host and that old host will eventually go away once all existing clients
/// are done with their requests.
///
/// callers can subscribe to <see cref="RemoteHostClient.ConnectionChanged"/> event to see whether client is going away if
/// callers can subscribe to <see cref="RemoteHostClient.StatusChanged"/> event to see whether client is going away if
/// caller is designed to hold onto a service for a while to react to remote host change.
/// </summary>
Task RequestNewRemoteHostAsync(CancellationToken cancellationToken);
[Obsolete("use TryGetRemoteHostClientAsync instead")]
Task<RemoteHostClient> GetRemoteHostClientAsync(CancellationToken cancellationToken);
/// <summary>
/// Get <see cref="RemoteHostClient"/> to current RemoteHost
/// </summary>
......
......@@ -11,142 +11,145 @@
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// This lets users create a session to communicate with remote host (i.e. ServiceHub)
/// This represents client in client/servier model.
///
/// user can create a connection to communicate with the server (remote host) through this client
/// </summary>
internal abstract class RemoteHostClient
internal abstract partial class RemoteHostClient
{
private readonly Workspace _workspace;
public readonly Workspace Workspace;
protected RemoteHostClient(Workspace workspace)
{
_workspace = workspace;
Workspace = workspace;
}
public event EventHandler<bool> ConnectionChanged;
public event EventHandler<bool> StatusChanged;
/// <summary>
/// Create <see cref="RemoteHostClient.Session"/> for the <paramref name="serviceName"/> if possible.
/// Create <see cref="RemoteHostClient.Connection"/> for the <paramref name="serviceName"/> if possible.
/// otherwise, return null.
///
/// Creating session could fail if remote host is not available. one of example will be user killing
/// remote host.
/// </summary>
public Task<Session> TryCreateServiceSessionAsync(string serviceName, CancellationToken cancellationToken)
{
return TryCreateServiceSessionAsync(serviceName, callbackTarget: null, cancellationToken: cancellationToken);
}
/// <summary>
/// Create <see cref="RemoteHostClient.Session"/> for the <paramref name="serviceName"/> if possible.
/// otherwise, return null.
///
/// Creating session could fail if remote host is not available. one of example will be user killing
/// remote host.
/// </summary>
public Task<Session> TryCreateServiceSessionAsync(string serviceName, object callbackTarget, CancellationToken cancellationToken)
{
return TryCreateServiceSessionAsync(serviceName, getSnapshotAsync: null, callbackTarget: callbackTarget, cancellationToken: cancellationToken);
}
/// <summary>
/// Create <see cref="RemoteHostClient.Session"/> for the <paramref name="serviceName"/> if possible.
/// otherwise, return null.
///
/// Creating session could fail if remote host is not available. one of example will be user killing
/// remote host.
/// </summary>
public Task<Session> TryCreateServiceSessionAsync(string serviceName, Solution solution, CancellationToken cancellationToken)
{
return TryCreateServiceSessionAsync(serviceName, solution, callbackTarget: null, cancellationToken: cancellationToken);
}
/// <summary>
/// Create <see cref="RemoteHostClient.Session"/> for the <paramref name="serviceName"/> if possible.
/// otherwise, return null.
///
/// Creating session could fail if remote host is not available. one of example will be user killing
/// remote host.
/// </summary>
public async Task<Session> TryCreateServiceSessionAsync(string serviceName, Solution solution, object callbackTarget, CancellationToken cancellationToken)
{
Func<CancellationToken, Task<PinnedRemotableDataScope>> getSnapshotAsync = ct => GetPinnedScopeAsync(solution, ct);
return await TryCreateServiceSessionAsync(serviceName, getSnapshotAsync, callbackTarget, cancellationToken).ConfigureAwait(false);
}
protected abstract void OnConnected();
public abstract Task<Connection> TryCreateConnectionAsync(string serviceName, object callbackTarget, CancellationToken cancellationToken);
protected abstract void OnDisconnected();
protected abstract void OnStarted();
protected abstract Task<Session> TryCreateServiceSessionAsync(string serviceName, Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync, object callbackTarget, CancellationToken cancellationToken);
protected abstract void OnStopped();
internal void Shutdown()
{
// this should be only used by RemoteHostService to shutdown this remote host
Disconnected();
Stopped();
}
protected void Connected()
protected void Started()
{
OnConnected();
OnStarted();
OnConnectionChanged(true);
OnStatusChanged(true);
}
protected void Disconnected()
protected void Stopped()
{
OnDisconnected();
OnStopped();
OnConnectionChanged(false);
OnStatusChanged(false);
}
private void OnConnectionChanged(bool connected)
private void OnStatusChanged(bool started)
{
ConnectionChanged?.Invoke(this, connected);
StatusChanged?.Invoke(this, started);
}
private async Task<PinnedRemotableDataScope> GetPinnedScopeAsync(Solution solution, CancellationToken cancellationToken)
/// <summary>
/// NoOpClient is used if a user killed our remote host process. Basically this client never
/// create a session
/// </summary>
public class NoOpClient : RemoteHostClient
{
if (solution == null)
public NoOpClient(Workspace workspace) :
base(workspace)
{
return null;
}
Contract.ThrowIfFalse(solution.Workspace == _workspace);
public override Task<Connection> TryCreateConnectionAsync(string serviceName, object callbackTarget, CancellationToken cancellationToken)
{
return SpecializedTasks.Default<Connection>();
}
var service = _workspace.Services.GetService<ISolutionSynchronizationService>();
return await service.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false);
protected override void OnStarted()
{
// do nothing
}
protected override void OnStopped()
{
// do nothing
}
}
// TODO: make this to not exposed to caller. abstract all of these under Request and Response mechanism
public abstract class Session : IDisposable
/// <summary>
/// This is a connection between client and server. user can use this to communicate with remote host.
///
/// This doesn't know anything specific to Roslyn. this is general pure connection between client and server.
/// </summary>
public abstract class Connection : IDisposable
{
protected readonly PinnedRemotableDataScope PinnedScopeOpt;
// this will be removed soon.
protected readonly CancellationToken CancellationToken;
// scope will be also removed from the connection
private readonly object _gate;
private PinnedRemotableDataScope _scopeDoNotAccessDirectly;
private bool _disposed;
protected Session(PinnedRemotableDataScope scope, CancellationToken cancellationToken)
protected Connection(CancellationToken cancellationToken)
{
_gate = new object();
_disposed = false;
_scopeDoNotAccessDirectly = null;
PinnedScopeOpt = scope;
CancellationToken = cancellationToken;
}
public abstract Task InvokeAsync(string targetName, params object[] arguments);
protected abstract Task OnRegisterPinnedRemotableDataScopeAsync(PinnedRemotableDataScope scope);
public abstract Task<T> InvokeAsync<T>(string targetName, params object[] arguments);
public abstract Task InvokeAsync(string targetName, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task> funcWithDirectStreamAsync);
public abstract Task<T> InvokeAsync<T>(string targetName, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task<T>> funcWithDirectStreamAsync);
public PinnedRemotableDataScope PinnedRemotableDataScope
{
get
{
lock (_gate)
{
return _scopeDoNotAccessDirectly;
}
}
set
{
lock (_gate)
{
_scopeDoNotAccessDirectly = value;
}
}
}
public void AddAdditionalAssets(CustomAsset asset)
public virtual Task RegisterPinnedRemotableDataScopeAsync(PinnedRemotableDataScope scope)
{
Contract.ThrowIfNull(PinnedScopeOpt);
PinnedScopeOpt.AddAdditionalAsset(asset, CancellationToken);
// make sure all thread can read the info
PinnedRemotableDataScope = scope;
return OnRegisterPinnedRemotableDataScopeAsync(scope);
}
public abstract Task InvokeAsync(string targetName, params object[] arguments);
public abstract Task<T> InvokeAsync<T>(string targetName, params object[] arguments);
public abstract Task InvokeAsync(string targetName, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task> funcWithDirectStreamAsync);
public abstract Task<T> InvokeAsync<T>(string targetName, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task<T>> funcWithDirectStreamAsync);
protected virtual void OnDisposed()
{
// do nothing
......@@ -162,32 +165,6 @@ public void Dispose()
_disposed = true;
OnDisposed();
PinnedScopeOpt?.Dispose();
}
}
public class NoOpClient : RemoteHostClient
{
public NoOpClient(Workspace workspace) :
base(workspace)
{
}
protected override Task<Session> TryCreateServiceSessionAsync(
string serviceName, Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync, object callbackTarget, CancellationToken cancellationToken)
{
return SpecializedTasks.Default<Session>();
}
protected override void OnConnected()
{
// do nothing
}
protected override void OnDisconnected()
{
// do nothing
}
}
}
......
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// This will tie <see cref="Solution"/> and <see cref="RemoteHostClient.Connection"/>'s lifetime together
/// so that one can handle those more easily
/// </summary>
internal sealed class SessionWithSolution : IDisposable
{
private readonly RemoteHostClient.Connection _connection;
private readonly PinnedRemotableDataScope _scope;
private readonly CancellationToken _cancellationToken;
public static async Task<SessionWithSolution> CreateAsync(RemoteHostClient.Connection connection, PinnedRemotableDataScope scope, CancellationToken cancellationToken)
{
var sessionWithSolution = new SessionWithSolution(connection, scope, cancellationToken);
try
{
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
return sessionWithSolution;
}
catch
{
sessionWithSolution.Dispose();
// we only expect this to happen on cancellation. otherwise, rethrow
cancellationToken.ThrowIfCancellationRequested();
throw;
}
}
private SessionWithSolution(RemoteHostClient.Connection connection, PinnedRemotableDataScope scope, CancellationToken cancellationToken)
{
_connection = connection;
_scope = scope;
_cancellationToken = cancellationToken;
}
public void AddAdditionalAssets(CustomAsset asset)
{
_scope.AddAdditionalAsset(asset, _cancellationToken);
}
public void Dispose()
{
_scope.Dispose();
_connection.Dispose();
}
public Task InvokeAsync(string targetName, params object[] arguments)
=> _connection.InvokeAsync(targetName, arguments);
public Task<T> InvokeAsync<T>(string targetName, params object[] arguments)
=> _connection.InvokeAsync<T>(targetName, arguments);
public Task InvokeAsync(string targetName, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task> funcWithDirectStreamAsync)
=> _connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync);
public Task<T> InvokeAsync<T>(string targetName, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task<T>> funcWithDirectStreamAsync)
=> _connection.InvokeAsync<T>(targetName, arguments, funcWithDirectStreamAsync);
}
/// <summary>
/// This will let one to hold onto <see cref="RemoteHostClient.Connection"/> for a while.
/// this helper will let you not care about remote host being gone while you hold onto the connection if that ever happen
///
/// and also make sure state is correct even if multiple threads call TryInvokeAsync at the same time. but this
/// is not optimized to handle highly concurrent usage. if highly concurrent usage is required, either using
/// <see cref="RemoteHostClient.Connection"/> direclty or using <see cref="SessionWithSolution"/> would be better choice
/// </summary>
internal sealed class KeepAliveSession
{
private readonly SemaphoreSlim _gate;
private readonly IRemoteHostClientService _remoteHostClientService;
private readonly CancellationToken _cancellationToken;
private readonly string _serviceName;
private readonly object _callbackTarget;
private RemoteHostClient _client;
private RemoteHostClient.Connection _connection;
public KeepAliveSession(RemoteHostClient client, RemoteHostClient.Connection connection, string serviceName, object callbackTarget, CancellationToken cancellationToken)
{
Initialize_NoLock(client, connection);
_gate = new SemaphoreSlim(initialCount: 1);
_remoteHostClientService = client.Workspace.Services.GetService<IRemoteHostClientService>();
_cancellationToken = cancellationToken;
_serviceName = serviceName;
_callbackTarget = callbackTarget;
}
public void Shutdown()
{
using (_gate.DisposableWait(_cancellationToken))
{
if (_client != null)
{
_client.StatusChanged -= OnStatusChanged;
}
_connection?.Dispose();
_client = null;
_connection = null;
}
}
public async Task<bool> TryInvokeAsync(string targetName, params object[] arguments)
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync().ConfigureAwait(false);
if (connection == null)
{
return false;
}
await connection.InvokeAsync(targetName, arguments).ConfigureAwait(false);
return true;
}
}
public async Task<T> TryInvokeAsync<T>(string targetName, params object[] arguments)
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync().ConfigureAwait(false);
if (connection == null)
{
return default;
}
return await connection.InvokeAsync<T>(targetName, arguments).ConfigureAwait(false);
}
}
public async Task<bool> TryInvokeAsync(string targetName, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task> funcWithDirectStreamAsync)
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync().ConfigureAwait(false);
if (connection == null)
{
return false;
}
await connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync).ConfigureAwait(false);
return true;
}
}
public async Task<T> TryInvokeAsync<T>(string targetName, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task<T>> funcWithDirectStreamAsync)
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync().ConfigureAwait(false);
if (connection == null)
{
return default;
}
return await connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync).ConfigureAwait(false);
}
}
public async Task<bool> TryInvokeAsync(string targetName, Solution solution, params object[] arguments)
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
using (var scope = await solution.GetPinnedScopeAsync(_cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync().ConfigureAwait(false);
if (connection == null)
{
return false;
}
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
await connection.InvokeAsync(targetName, arguments).ConfigureAwait(false);
return true;
}
}
public async Task<T> TryInvokeAsync<T>(string targetName, Solution solution, params object[] arguments)
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
using (var scope = await solution.GetPinnedScopeAsync(_cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync().ConfigureAwait(false);
if (connection == null)
{
return default;
}
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
return await connection.InvokeAsync<T>(targetName, arguments).ConfigureAwait(false);
}
}
public async Task<bool> TryInvokeAsync(
string targetName, Solution solution, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task> funcWithDirectStreamAsync)
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
using (var scope = await solution.GetPinnedScopeAsync(_cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync().ConfigureAwait(false);
if (connection == null)
{
return false;
}
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
await connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync).ConfigureAwait(false);
return true;
}
}
public async Task<T> TryInvokeAsync<T>(string targetName, Solution solution, IEnumerable<object> arguments, Func<Stream, CancellationToken, Task<T>> funcWithDirectStreamAsync)
{
using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false))
using (var scope = await solution.GetPinnedScopeAsync(_cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync().ConfigureAwait(false);
if (connection == null)
{
return default;
}
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
return await connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync).ConfigureAwait(false);
}
}
private async Task<RemoteHostClient.Connection> TryGetConnection_NoLockAsync()
{
if (_connection != null)
{
return _connection;
}
var client = await _remoteHostClientService.TryGetRemoteHostClientAsync(_cancellationToken).ConfigureAwait(false);
if (client == null)
{
return null;
}
var session = await client.TryCreateConnectionAsync(_serviceName, _callbackTarget, _cancellationToken).ConfigureAwait(false);
if (session == null)
{
return null;
}
Initialize_NoLock(client, session);
return _connection;
}
private void OnStatusChanged(object sender, bool connection)
{
if (connection)
{
return;
}
Shutdown();
}
private void Initialize_NoLock(RemoteHostClient client, RemoteHostClient.Connection connection)
{
Contract.ThrowIfNull(client);
Contract.ThrowIfNull(connection);
_client = client;
_client.StatusChanged += OnStatusChanged;
_connection = connection;
}
}
}
......@@ -8,7 +8,7 @@ internal static class WellKnownServiceHubServices
public const string SnapshotService = "snapshotService";
public const string CodeAnalysisService = "codeAnalysisService";
public const string CodeAnalysisService_CalculateDiagnosticsAsync = "CalculateDiagnosticsAsync";
public const string RemoteSymbolSearchUpdateEngine = "remoteSymbolSearchUpdateEngine";
// CodeLens methods.
public const string CodeAnalysisService_GetReferenceCountAsync = "GetReferenceCountAsync";
......@@ -16,7 +16,7 @@ internal static class WellKnownServiceHubServices
public const string CodeAnalysisService_FindReferenceMethodsAsync = "FindReferenceMethodsAsync";
public const string CodeAnalysisService_GetFullyQualifiedName = "GetFullyQualifiedName";
public const string RemoteSymbolSearchUpdateEngine = "remoteSymbolSearchUpdateEngine";
public const string CodeAnalysisService_CalculateDiagnosticsAsync = "CalculateDiagnosticsAsync";
public const string AssetService_RequestAssetAsync = "RequestAssetAsync";
}
......
// 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;
namespace Microsoft.CodeAnalysis.Versions
{
internal static class Extensions
......@@ -37,40 +35,5 @@ public static bool CanReusePersistedDependentProjectVersion(this Project project
PersistedVersionStampLogger.LogPersistedDependentProjectVersionUsage(canReuse);
return canReuse;
}
public static bool CanReusePersistedSemanticVersion(
this Project project, VersionStamp projectVersion, VersionStamp semanticVersion, VersionStamp persistedVersion)
{
var canReuse = CanReusePersistedSemanticVersionInternal(
project, projectVersion, semanticVersion, persistedVersion, (s, p, v) => s.GetInitialProjectVersionFromSemanticVersion(p, v));
PersistedVersionStampLogger.LogPersistedSemanticVersionUsage(canReuse);
return canReuse;
}
public static bool CanReusePersistedDependentSemanticVersion(
this Project project, VersionStamp dependentProjectVersion, VersionStamp dependentSemanticVersion, VersionStamp persistedVersion)
{
var canReuse = CanReusePersistedSemanticVersionInternal(
project, dependentProjectVersion, dependentSemanticVersion, persistedVersion, (s, p, v) => s.GetInitialDependentProjectVersionFromDependentSemanticVersion(p, v));
PersistedVersionStampLogger.LogPersistedDependentSemanticVersionUsage(canReuse);
return canReuse;
}
private static bool CanReusePersistedSemanticVersionInternal(
Project project,
VersionStamp projectVersion,
VersionStamp semanticVersion,
VersionStamp persistedVersion,
Func<ISemanticVersionTrackingService, Project, VersionStamp, VersionStamp> versionGetter)
{
// * NOTE *
// Disabled semantic version tracking
// we need better version for it to reliably work.
//
// see tracking issue here : https://github.com/dotnet/roslyn/issues/2311
return VersionStamp.CanReusePersistedVersion(semanticVersion, persistedVersion);
}
}
}
// 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;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Versions
{
/// <summary>
/// A service that knows how to convert semantic version to project version
/// </summary>
internal interface ISemanticVersionTrackingService : IWorkspaceService
{
VersionStamp GetInitialProjectVersionFromSemanticVersion(Project project, VersionStamp semanticVersion);
VersionStamp GetInitialDependentProjectVersionFromDependentSemanticVersion(Project project, VersionStamp dependentSemanticVersion);
void LoadInitialSemanticVersions(Solution solution);
void LoadInitialSemanticVersions(Project project);
Task RecordSemanticVersionsAsync(Project project, CancellationToken cancellationToken);
}
}
......@@ -207,6 +207,7 @@
<Compile Include="Classification\AbstractClassificationService.cs" />
<Compile Include="Classification\IClassificationService.cs" />
<Compile Include="ExtensionManager\IInfoBarService.cs" />
<Compile Include="Remote\RemoteHostSessionHelpers.cs" />
<Compile Include="Remote\RemoteFeatureOptions.cs" />
</ItemGroup>
<ItemGroup>
......@@ -383,7 +384,6 @@
<Compile Include="FindSymbols\SymbolFinder_Declarations_CustomQueries.cs" />
<Compile Include="FindSymbols\SymbolFinder_FindReferences_Current.cs" />
<Compile Include="FindSymbols\SymbolFinder_FindRenamableReferences.cs" />
<Compile Include="FindSymbols\SymbolFinder_Remote.cs" />
<Compile Include="FindSymbols\SyntaxTree\SyntaxTreeIndex.LiteralInfo.cs" />
<Compile Include="LanguageServices\SyntaxFactsService\AbstractDocumentationCommentService.cs" />
<Compile Include="LanguageServices\SyntaxFactsService\IDocumentationCommentService.cs" />
......@@ -637,7 +637,6 @@
<Compile Include="Utilities\ValuesSources\CachedWeakValueSource.cs" />
<Compile Include="Utilities\WeakEventHandler.cs" />
<Compile Include="Versions\Extensions.cs" />
<Compile Include="Versions\ISemanticVersionTrackingService.cs" />
<Compile Include="Versions\PersistedVersionStampLogger.cs" />
<Compile Include="Workspace\AdhocWorkspace.cs" />
<Compile Include="Workspace\DocumentActiveContextChangedEventArgs.cs" />
......
......@@ -20,12 +20,12 @@ internal class AssetService
// PREVIEW: unfortunately, I need dummy workspace since workspace services can be workspace specific
private static readonly Serializer s_serializer = new Serializer(new AdhocWorkspace(RoslynServices.HostServices, workspaceKind: "dummy"));
private readonly int _sessionId;
private readonly int _scopeId;
private readonly AssetStorage _assetStorage;
public AssetService(int sessionId, AssetStorage assetStorage)
public AssetService(int scopeId, AssetStorage assetStorage)
{
_sessionId = sessionId;
_scopeId = scopeId;
_assetStorage = assetStorage;
}
......@@ -150,7 +150,7 @@ private async Task<object> RequestAssetAsync(Checksum checksum, CancellationToke
return SpecializedCollections.EmptyList<ValueTuple<Checksum, object>>();
}
var source = _assetStorage.TryGetAssetSource(_sessionId);
var source = _assetStorage.TryGetAssetSource(_scopeId);
cancellationToken.ThrowIfCancellationRequested();
Contract.ThrowIfNull(source);
......@@ -158,7 +158,7 @@ private async Task<object> RequestAssetAsync(Checksum checksum, CancellationToke
try
{
// ask one of asset source for data
return await source.RequestAssetsAsync(_sessionId, checksums, cancellationToken).ConfigureAwait(false);
return await source.RequestAssetsAsync(_scopeId, checksums, cancellationToken).ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
......
......@@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
{
......@@ -14,21 +13,21 @@ namespace Microsoft.CodeAnalysis.Remote
internal abstract class AssetSource
{
private readonly AssetStorage _assetStorage;
private readonly int _sessionId;
private readonly int _scopeId;
protected AssetSource(AssetStorage assetStorage, int sessionId)
protected AssetSource(AssetStorage assetStorage, int scopeId)
{
_assetStorage = assetStorage;
_sessionId = sessionId;
_scopeId = scopeId;
_assetStorage.RegisterAssetSource(_sessionId, this);
_assetStorage.RegisterAssetSource(_scopeId, this);
}
public abstract Task<IList<ValueTuple<Checksum, object>>> RequestAssetsAsync(int serviceId, ISet<Checksum> checksums, CancellationToken cancellationToken);
public abstract Task<IList<ValueTuple<Checksum, object>>> RequestAssetsAsync(int scopeId, ISet<Checksum> checksums, CancellationToken cancellationToken);
public void Done()
{
_assetStorage.UnregisterAssetSource(_sessionId);
_assetStorage.UnregisterAssetSource(_scopeId);
}
}
}
......@@ -45,10 +45,10 @@ public AssetStorage(TimeSpan cleanupInterval, TimeSpan purgeAfter)
Task.Run(CleanAssetsAsync, CancellationToken.None);
}
public AssetSource TryGetAssetSource(int sessionId)
public AssetSource TryGetAssetSource(int scopeId)
{
AssetSource source;
if (_assetSources.TryGetValue(sessionId, out source))
if (_assetSources.TryGetValue(scopeId, out source))
{
return source;
}
......@@ -56,15 +56,15 @@ public AssetSource TryGetAssetSource(int sessionId)
return null;
}
public void RegisterAssetSource(int sessionId, AssetSource assetSource)
public void RegisterAssetSource(int scopeId, AssetSource assetSource)
{
Contract.ThrowIfFalse(_assetSources.TryAdd(sessionId, assetSource));
Contract.ThrowIfFalse(_assetSources.TryAdd(scopeId, assetSource));
}
public void UnregisterAssetSource(int sessionId)
public void UnregisterAssetSource(int scopeId)
{
AssetSource dummy;
_assetSources.TryRemove(sessionId, out dummy);
_assetSources.TryRemove(scopeId, out dummy);
}
public bool TryAddGlobalAsset(Checksum checksum, object value)
......
......@@ -22,13 +22,13 @@ internal class RoslynServices
.Add(typeof(CSharp.CodeLens.CSharpCodeLensDisplayInfoService).Assembly)
.Add(typeof(VisualBasic.CodeLens.VisualBasicDisplayInfoService).Assembly));
private readonly int _sessionId;
private readonly int _scopeId;
public RoslynServices(int sessionId, AssetStorage storage)
public RoslynServices(int scopeId, AssetStorage storage)
{
_sessionId = sessionId;
_scopeId = scopeId;
AssetService = new AssetService(_sessionId, storage);
AssetService = new AssetService(_scopeId, storage);
SolutionService = new SolutionService(AssetService);
CompilationService = new CompilationService(SolutionService);
}
......
......@@ -25,13 +25,13 @@ private class JsonRpcAssetSource : AssetSource
{
private readonly SnapshotService _owner;
public JsonRpcAssetSource(SnapshotService owner, int sessionId) :
base(owner.AssetStorage, sessionId)
public JsonRpcAssetSource(SnapshotService owner, int scopeId) :
base(owner.AssetStorage, scopeId)
{
_owner = owner;
}
public override async Task<IList<ValueTuple<Checksum, object>>> RequestAssetsAsync(int sessionId, ISet<Checksum> checksums, CancellationToken callerCancellationToken)
public override async Task<IList<ValueTuple<Checksum, object>>> RequestAssetsAsync(int scopeId, ISet<Checksum> checksums, CancellationToken callerCancellationToken)
{
// it should succeed as long as matching VS is alive
// TODO: add logging mechanism using Logger
......@@ -43,16 +43,16 @@ private class JsonRpcAssetSource : AssetSource
//
// 2. Request to required this asset has cancelled. (callerCancellationToken)
using (var mergedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(_owner.CancellationToken, callerCancellationToken))
using (RoslynLogger.LogBlock(FunctionId.SnapshotService_RequestAssetAsync, GetRequestLogInfo, sessionId, checksums, mergedCancellationToken.Token))
using (RoslynLogger.LogBlock(FunctionId.SnapshotService_RequestAssetAsync, GetRequestLogInfo, scopeId, checksums, mergedCancellationToken.Token))
{
return await _owner.Rpc.InvokeAsync(WellKnownServiceHubServices.AssetService_RequestAssetAsync,
new object[] { sessionId, checksums.ToArray() },
(s, c) => ReadAssets(s, sessionId, checksums, c), mergedCancellationToken.Token).ConfigureAwait(false);
new object[] { scopeId, checksums.ToArray() },
(s, c) => ReadAssets(s, scopeId, checksums, c), mergedCancellationToken.Token).ConfigureAwait(false);
}
}
private IList<ValueTuple<Checksum, object>> ReadAssets(
Stream stream, int sessionId, ISet<Checksum> checksums, CancellationToken cancellationToken)
Stream stream, int scopeId, ISet<Checksum> checksums, CancellationToken cancellationToken)
{
var results = new List<ValueTuple<Checksum, object>>();
......@@ -62,8 +62,8 @@ private class JsonRpcAssetSource : AssetSource
@"We only ge a reader for data transmitted between live processes.
This data should always be correct as we're never persisting the data between sessions.");
var responseSessionId = reader.ReadInt32();
Contract.ThrowIfFalse(sessionId == responseSessionId);
var responseScopeId = reader.ReadInt32();
Contract.ThrowIfFalse(scopeId == responseScopeId);
var count = reader.ReadInt32();
Contract.ThrowIfFalse(count == checksums.Count);
......
......@@ -2,6 +2,7 @@
using System;
using System.IO;
using Microsoft.CodeAnalysis.Execution;
using StreamJsonRpc;
namespace Microsoft.CodeAnalysis.Remote
......@@ -27,9 +28,9 @@ internal partial class SnapshotService : ServiceHubServiceBase
Rpc.StartListening();
}
public override void Initialize(int sessionId, bool primary, Checksum solutionChecksum)
public override void Initialize(PinnedSolutionInfo solutionInfo)
{
base.Initialize(sessionId, primary, solutionChecksum);
base.Initialize(solutionInfo);
lock (_gate)
{
......@@ -38,7 +39,7 @@ public override void Initialize(int sessionId, bool primary, Checksum solutionCh
return;
}
_source = new JsonRpcAssetSource(this, sessionId);
_source = new JsonRpcAssetSource(this, solutionInfo.ScopeId);
}
}
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Text;
using Newtonsoft.Json;
using Roslyn.Utilities;
......@@ -44,6 +45,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
Add(builder, new TextSpanJsonConverter());
Add(builder, new TextChangeJsonConverter());
Add(builder, new SymbolKeyJsonConverter());
Add(builder, new PinnedSolutionInfoJsonConverter());
AppendRoslynSpecificJsonConverters(builder);
......@@ -177,5 +179,39 @@ protected override Checksum ReadValue(JsonReader reader, JsonSerializer serializ
protected override void WriteValue(JsonWriter writer, Checksum value, JsonSerializer serializer)
=> writer.WriteValue(value?.ToString());
}
private class PinnedSolutionInfoJsonConverter : BaseJsonConverter<PinnedSolutionInfo>
{
protected override PinnedSolutionInfo ReadValue(JsonReader reader, JsonSerializer serializer)
{
Contract.ThrowIfFalse(reader.TokenType == JsonToken.StartObject);
// all integer is long
var scopeId = ReadProperty<long>(reader);
var fromPrimaryBranch = ReadProperty<bool>(reader);
var checksum = ReadProperty<Checksum>(serializer, reader);
Contract.ThrowIfFalse(reader.Read());
Contract.ThrowIfFalse(reader.TokenType == JsonToken.EndObject);
return new PinnedSolutionInfo((int)scopeId, fromPrimaryBranch, checksum);
}
protected override void WriteValue(JsonWriter writer, PinnedSolutionInfo scope, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("scopeId");
writer.WriteValue(scope.ScopeId);
writer.WritePropertyName("primary");
writer.WriteValue(scope.FromPrimaryBranch);
writer.WritePropertyName("checksum");
serializer.Serialize(writer, scope.SolutionChecksum);
writer.WriteEndObject();
}
}
}
}
\ No newline at end of file
......@@ -5,6 +5,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.VisualStudio.LanguageServices.Remote;
using Roslyn.Utilities;
using StreamJsonRpc;
......@@ -27,23 +28,17 @@ internal abstract class ServiceHubServiceBase : IDisposable
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
/// PinnedSolutionInfo.ScopeId. scope id of the solution. caller and callee share this id which one
/// can use to find matching caller and callee while exchanging data
///
/// PinnedSolutionInfo.FromPrimaryBranch Marks 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"/>
///
/// PinnedSolutionInfo.SolutionChecksum indicates solution this connection belong to
/// </summary>
private bool _fromPrimaryBranch;
/// <summary>
/// solution this connection belong to
/// </summary>
private Checksum _solutionChecksumOpt;
private PinnedSolutionInfo _solutionInfo;
private RoslynServices _lazyRoslynServices;
......@@ -96,7 +91,7 @@ protected RoslynServices RoslynServices
{
if (_lazyRoslynServices == null)
{
_lazyRoslynServices = new RoslynServices(_sessionId, AssetStorage);
_lazyRoslynServices = new RoslynServices(_solutionInfo.ScopeId, AssetStorage);
}
return _lazyRoslynServices;
......@@ -105,10 +100,10 @@ protected RoslynServices RoslynServices
protected Task<Solution> GetSolutionAsync()
{
Contract.ThrowIfNull(_solutionChecksumOpt);
Contract.ThrowIfNull(_solutionInfo);
var solutionController = (ISolutionController)RoslynServices.SolutionService;
return solutionController.GetSolutionAsync(_solutionChecksumOpt, _fromPrimaryBranch, CancellationToken);
return solutionController.GetSolutionAsync(_solutionInfo.SolutionChecksum, _solutionInfo.FromPrimaryBranch, CancellationToken);
}
protected virtual void Dispose(bool disposing)
......@@ -121,16 +116,11 @@ protected void LogError(string message)
Log(TraceEventType.Error, message);
}
public virtual void Initialize(int sessionId, bool fromPrimaryBranch, Checksum solutionChecksum)
public virtual void Initialize(PinnedSolutionInfo info)
{
// set session related information
_sessionId = sessionId;
_fromPrimaryBranch = fromPrimaryBranch;
if (solutionChecksum != null)
{
_solutionChecksumOpt = solutionChecksum;
}
// set pinned solution info
_lazyRoslynServices = null;
_solutionInfo = info;
}
public void Dispose()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册