未验证 提交 c1e8715c 编写于 作者: T Tomáš Matoušek 提交者: GitHub

Connection refactoring (#44512)

* RemoveHostClientProvider

* Do not start ServiceHub in command line mode

* Unused usings

* CreateConnection can no longer return null

* Connection refactoring

* Feedback

* Feedback

* Workaround for CodeLens issue

* Address NuGet PackageInstall failure

* Remove NuGet workaroud fixed in 15.9

* Fix potential hang
上级 3c1c0fcb
......@@ -31,11 +31,8 @@ internal static partial class SymbolSearchUpdateEngineFactory
if (client != null)
{
var callbackObject = new CallbackObject(logService, progressService);
var session = await client.TryCreateKeepAliveSessionAsync(WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine, callbackObject, cancellationToken).ConfigureAwait(false);
if (session != null)
{
return new RemoteUpdateEngine(workspace, session);
}
var session = await client.CreateConnectionAsync(WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine, callbackObject, cancellationToken).ConfigureAwait(false);
return new RemoteUpdateEngine(workspace, session);
}
// Couldn't go out of proc. Just do everything inside the current process.
......@@ -47,11 +44,11 @@ private sealed partial class RemoteUpdateEngine : ISymbolSearchUpdateEngine
private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1);
private readonly Workspace _workspace;
private readonly KeepAliveSession _session;
private readonly RemoteServiceConnection _session;
public RemoteUpdateEngine(
Workspace workspace,
KeepAliveSession session)
RemoteServiceConnection session)
{
_workspace = workspace;
_session = session;
......
......@@ -52,7 +52,7 @@ public async Task FindImplementationsAsync(Document document, int position, IFin
// the 'progress' parameter which will then update the UI.
var serverCallback = new FindUsagesServerCallback(solution, context);
var success = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteFindUsagesService.FindImplementationsAsync),
solution,
......@@ -62,14 +62,13 @@ public async Task FindImplementationsAsync(Document document, int position, IFin
},
serverCallback,
cancellationToken).ConfigureAwait(false);
if (success)
return;
}
// Couldn't effectively search in OOP. Perform the search in-process.
await FindImplementationsInCurrentProcessAsync(
symbol, project, context).ConfigureAwait(false);
else
{
// Couldn't effectively search in OOP. Perform the search in-process.
await FindImplementationsInCurrentProcessAsync(
symbol, project, context).ConfigureAwait(false);
}
}
private static async Task FindImplementationsInCurrentProcessAsync(
......
......@@ -149,7 +149,7 @@ internal abstract partial class AbstractFindUsagesService
// the 'progress' parameter which will then update the UI.
var serverCallback = new FindUsagesServerCallback(solution, context);
var success = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteFindUsagesService.FindReferencesAsync),
solution,
......@@ -160,14 +160,13 @@ internal abstract partial class AbstractFindUsagesService
},
serverCallback,
cancellationToken).ConfigureAwait(false);
if (success)
return;
}
// Couldn't effectively search in OOP. Perform the search in-process.
await FindReferencesInCurrentProcessAsync(
context, symbol, project, options).ConfigureAwait(false);
else
{
// Couldn't effectively search in OOP. Perform the search in-process.
await FindReferencesInCurrentProcessAsync(
context, symbol, project, options).ConfigureAwait(false);
}
}
private static Task FindReferencesInCurrentProcessAsync(
......
......@@ -25,6 +25,7 @@ namespace Roslyn.Test.Utilities.Remote
{
internal sealed class InProcRemoteHostClient : RemoteHostClient, IRemoteHostServiceCallback
{
private readonly HostWorkspaceServices _services;
private readonly InProcRemoteServices _inprocServices;
private readonly RemoteEndPoint _endPoint;
......@@ -34,7 +35,7 @@ public static async Task<RemoteHostClient> CreateAsync(HostWorkspaceServices ser
var remoteHostStream = await inprocServices.RequestServiceAsync(WellKnownServiceHubService.RemoteHost).ConfigureAwait(false);
var clientId = CreateClientId(Process.GetCurrentProcess().Id.ToString());
var clientId = $"InProc ({Guid.NewGuid()})";
var instance = new InProcRemoteHostClient(clientId, services, inprocServices, remoteHostStream);
// make sure connection is done right
......@@ -58,9 +59,9 @@ public static async Task<RemoteHostClient> CreateAsync(HostWorkspaceServices ser
HostWorkspaceServices services,
InProcRemoteServices inprocServices,
Stream stream)
: base(services)
{
ClientId = clientId;
_services = services;
_inprocServices = inprocServices;
......@@ -76,14 +77,15 @@ public Task GetAssetsAsync(int scopeId, Checksum[] checksums, string pipeName, C
=> RemoteEndPoint.WriteDataToNamedPipeAsync(
pipeName,
(scopeId, checksums),
(writer, data, cancellationToken) => RemoteHostAssetSerialization.WriteDataAsync(writer, RemotableDataService, data.scopeId, data.checksums, cancellationToken),
(writer, data, cancellationToken) => RemoteHostAssetSerialization.WriteDataAsync(
writer, _services.GetRequiredService<IRemotableDataService>(), data.scopeId, data.checksums, cancellationToken),
cancellationToken);
/// <summary>
/// Remote API.
/// </summary>
public Task<bool> IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken)
=> Task.FromResult(Services.GetRequiredService<IExperimentationService>().IsExperimentEnabled(experimentName));
=> Task.FromResult(_services.GetRequiredService<IExperimentationService>().IsExperimentEnabled(experimentName));
public AssetStorage AssetStorage => _inprocServices.AssetStorage;
......@@ -95,14 +97,13 @@ public Task<Stream> RequestServiceAsync(RemoteServiceName serviceName)
public override string ClientId { get; }
protected override async Task<Connection?> TryCreateConnectionAsync(
RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken)
public override async Task<RemoteServiceConnection> CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken)
{
// 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).ConfigureAwait(false);
return new JsonRpcConnection(Services, _inprocServices.Logger, callbackTarget, serviceStream);
return new JsonRpcConnection(_services, _inprocServices.Logger, callbackTarget, serviceStream, poolReclamation: null);
}
public override void Dispose()
......
......@@ -62,6 +62,9 @@
<Compile Include="..\..\VisualStudio\Core\Def\Implementation\Remote\JsonRpcConnection.cs">
<Link>Remote\JsonRpcConnection.cs</Link>
</Compile>
<Compile Include="..\..\VisualStudio\Core\Def\Implementation\Remote\IPooledConnectionReclamation.cs">
<Link>Remote\IPooledConnectionReclamation.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.CSharp.CommandLine.UnitTests" />
......
......@@ -59,7 +59,7 @@ internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSynta
{
var callbackTarget = new RemoteSymbolSearchService(symbolSearchService);
var result = await client.TryRunRemoteAsync<IList<AddImportFixData>>(
var result = await client.RunRemoteAsync<IList<AddImportFixData>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteAddImportFeatureService.GetFixesAsync),
document.Project.Solution,
......@@ -76,10 +76,7 @@ internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSynta
callbackTarget,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value.ToImmutableArray();
}
return result.ToImmutableArray();
}
return await GetFixesInCurrentProcessAsync(
......
......@@ -44,7 +44,7 @@ async Task<(ImmutableArray<SerializableImportCompletionItem>, StatisticCounter)>
var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<(IList<SerializableImportCompletionItem> items, StatisticCounter counter)>(
var result = await client.RunRemoteAsync<(IList<SerializableImportCompletionItem> items, StatisticCounter counter)>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteExtensionMethodImportCompletionService.GetUnimportedExtensionMethodsAsync),
project.Solution,
......@@ -52,10 +52,7 @@ async Task<(ImmutableArray<SerializableImportCompletionItem>, StatisticCounter)>
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return (result.Value.items.ToImmutableArray(), result.Value.counter);
}
return (result.items.ToImmutableArray(), result.counter);
}
return await GetUnimportedExtensionMethodsInCurrentProcessAsync(document, position, receiverTypeSymbol, namespaceInScope, forceIndexCreation, cancellationToken).ConfigureAwait(false);
......
......@@ -167,7 +167,7 @@ private static string GetTitle(Scope scope)
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var resultOpt = await client.TryRunRemoteAsync<SerializableConvertTupleToStructResult>(
var result = await client.RunRemoteAsync<SerializableConvertTupleToStructResult>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteConvertTupleToStructCodeRefactoringProvider.ConvertToStructAsync),
solution,
......@@ -180,14 +180,11 @@ private static string GetTitle(Scope scope)
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (resultOpt.HasValue)
{
var result = resultOpt.Value;
var resultSolution = await RemoteUtilities.UpdateSolutionAsync(
solution, result.DocumentTextChanges, cancellationToken).ConfigureAwait(false);
return await AddRenameTokenAsync(
resultSolution, result.RenamedToken, cancellationToken).ConfigureAwait(false);
}
var resultSolution = await RemoteUtilities.UpdateSolutionAsync(
solution, result.DocumentTextChanges, cancellationToken).ConfigureAwait(false);
return await AddRenameTokenAsync(
resultSolution, result.RenamedToken, cancellationToken).ConfigureAwait(false);
}
}
......
......@@ -91,7 +91,7 @@ private async Task FireAndForgetReportAnalyzerPerformanceAsync(Project project,
try
{
_ = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteDiagnosticAnalyzerService.ReportAnalyzerPerformance),
solution: null,
......@@ -128,7 +128,7 @@ private async Task FireAndForgetReportAnalyzerPerformanceAsync(Project project,
forcedAnalysis, analyzerDriver.AnalysisOptions.ReportSuppressedDiagnostics, analyzerDriver.AnalysisOptions.LogAnalyzerExecutionTime,
project.Id, analyzerMap.Keys.ToArray());
var result = await client.TryRunRemoteAsync(
return await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteDiagnosticAnalyzerService.CalculateDiagnosticsAsync),
solution,
......@@ -136,8 +136,6 @@ private async Task FireAndForgetReportAnalyzerPerformanceAsync(Project project,
callbackTarget: null,
(s, c) => ReadCompilerAnalysisResultAsync(s, analyzerMap, project, c),
cancellationToken).ConfigureAwait(false);
return result.HasValue ? result.Value : DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>.Empty;
}
private static async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ReadCompilerAnalysisResultAsync(Stream stream, Dictionary<string, DiagnosticAnalyzer> analyzerMap, Project project, CancellationToken cancellationToken)
......
......@@ -545,7 +545,7 @@ private async Task ReportAnalyzerPerformanceAsync(Document document, Compilation
return;
}
_ = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteDiagnosticAnalyzerService.ReportAnalyzerPerformance),
solution: null,
......
......@@ -30,7 +30,7 @@ internal abstract partial class AbstractDocumentHighlightsService : IDocumentHig
var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<IList<SerializableDocumentHighlights>>(
var result = await client.RunRemoteAsync<IList<SerializableDocumentHighlights>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteDocumentHighlights.GetDocumentHighlightsAsync),
solution,
......@@ -43,10 +43,7 @@ internal abstract partial class AbstractDocumentHighlightsService : IDocumentHig
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value.SelectAsArray(h => h.Rehydrate(solution));
}
return result.SelectAsArray(h => h.Rehydrate(solution));
}
return await GetDocumentHighlightsInCurrentProcessAsync(
......
......@@ -104,7 +104,7 @@ private ImmutableArray<CodeAction> EncapsulateOneField(Document document, IField
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<(DocumentId, TextChange[])[]>(
var result = await client.RunRemoteAsync<(DocumentId, TextChange[])[]>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteEncapsulateFieldService.EncapsulateFieldsAsync),
solution,
......@@ -117,11 +117,8 @@ private ImmutableArray<CodeAction> EncapsulateOneField(Document document, IField
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return await RemoteUtilities.UpdateSolutionAsync(
solution, result.Value, cancellationToken).ConfigureAwait(false);
}
return await RemoteUtilities.UpdateSolutionAsync(
solution, result, cancellationToken).ConfigureAwait(false);
}
}
......
......@@ -38,7 +38,7 @@ internal abstract partial class AbstractNavigateToSearchService : INavigateToSea
{
var solution = document.Project.Solution;
var result = await client.TryRunRemoteAsync<IList<SerializableNavigateToSearchResult>>(
var result = await client.RunRemoteAsync<IList<SerializableNavigateToSearchResult>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteNavigateToSearchService.SearchDocumentAsync),
solution,
......@@ -46,10 +46,7 @@ internal abstract partial class AbstractNavigateToSearchService : INavigateToSea
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value.SelectAsArray(r => r.Rehydrate(solution));
}
return result.SelectAsArray(r => r.Rehydrate(solution));
}
return await SearchDocumentInCurrentProcessAsync(
......@@ -64,7 +61,7 @@ internal abstract partial class AbstractNavigateToSearchService : INavigateToSea
{
var solution = project.Solution;
var result = await client.TryRunRemoteAsync<IList<SerializableNavigateToSearchResult>>(
var result = await client.RunRemoteAsync<IList<SerializableNavigateToSearchResult>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteNavigateToSearchService.SearchProjectAsync),
solution,
......@@ -72,10 +69,7 @@ internal abstract partial class AbstractNavigateToSearchService : INavigateToSea
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value.SelectAsArray(r => r.Rehydrate(solution));
}
return result.SelectAsArray(r => r.Rehydrate(solution));
}
return await SearchProjectInCurrentProcessAsync(
......
......@@ -27,7 +27,7 @@ internal RazorRemoteHostClient(RemoteHostClient client)
return client == null ? null : new RazorRemoteHostClient(client);
}
public Task<Optional<T>> TryRunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
=> _client.TryRunRemoteAsync<T>(WellKnownServiceHubService.Razor, targetName, solution, arguments, callbackTarget: null, cancellationToken);
public async Task<Optional<T>> TryRunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
=> await _client.RunRemoteAsync<T>(WellKnownServiceHubService.Razor, targetName, solution, arguments, callbackTarget: null, cancellationToken).ConfigureAwait(false);
}
}
......@@ -134,9 +134,16 @@ public List<Guid> GetDocumentId(Guid projectGuid, string filePath, CancellationT
}
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var node = root.FindNode(span.ToTextSpan());
var textSpan = span.ToTextSpan();
return (document.Id, node);
// TODO: This check avoids ArgumentOutOfRangeException but it's not clear if this is the right solution
// https://github.com/dotnet/roslyn/issues/44639
if (!root.FullSpan.Contains(textSpan))
{
return default;
}
return (document.Id, root.FindNode(textSpan));
}
private async Task<int> GetMaxResultCapAsync(CancellationToken cancellationToken)
......
......@@ -40,18 +40,13 @@ public RemoteCodeLensReferencesService()
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<ReferenceCount>(
return await client.RunRemoteAsync<ReferenceCount>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteCodeLensReferencesService.GetReferenceCountAsync),
solution,
new object[] { documentId, syntaxNode.Span, maxSearchResults },
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value;
}
}
return await CodeLensReferencesServiceFactory.Instance.GetReferenceCountAsync(solution, documentId, syntaxNode, maxSearchResults, cancellationToken).ConfigureAwait(false);
......@@ -87,18 +82,13 @@ public RemoteCodeLensReferencesService()
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<IEnumerable<ReferenceMethodDescriptor>>(
return await client.RunRemoteAsync<IEnumerable<ReferenceMethodDescriptor>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteCodeLensReferencesService.FindReferenceMethodsAsync),
solution,
new object[] { documentId, syntaxNode.Span },
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value;
}
}
return await CodeLensReferencesServiceFactory.Instance.FindReferenceMethodsAsync(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false);
......@@ -118,18 +108,13 @@ public RemoteCodeLensReferencesService()
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<string>(
return await client.RunRemoteAsync<string>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteCodeLensReferencesService.GetFullyQualifiedNameAsync),
solution,
new object[] { documentId, syntaxNode.Span },
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value;
}
}
return await CodeLensReferencesServiceFactory.Instance.GetFullyQualifiedNameAsync(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false);
......@@ -252,18 +237,13 @@ private static string GetLineTextOrEmpty(TextLineCollection lines, int index)
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<IEnumerable<ReferenceLocationDescriptor>>(
return await client.RunRemoteAsync<IEnumerable<ReferenceLocationDescriptor>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteCodeLensReferencesService.FindReferenceLocationsAsync),
solution,
new object[] { documentId, syntaxNode.Span },
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value;
}
}
// remote host is not running. this can happen if remote host is disabled.
......
......@@ -48,10 +48,10 @@ internal class VisualStudioDesignerAttributeService
private readonly IServiceProvider _serviceProvider;
/// <summary>
/// Our connections to the remote OOP server. Created on demand when we startup and then
/// Our connection to the remote OOP server. Created on demand when we startup and then
/// kept around for the lifetime of this service.
/// </summary>
private KeepAliveSession? _keepAliveSession;
private RemoteServiceConnection? _connection;
/// <summary>
/// Cache from project to the CPS designer service for it. Computed on demand (which
......@@ -118,14 +118,12 @@ private async Task StartWorkerAsync(CancellationToken cancellationToken)
// Pass ourselves in as the callback target for the OOP service. As it discovers
// designer attributes it will call back into us to notify VS about it.
_keepAliveSession = await client.TryCreateKeepAliveSessionAsync(
_connection = await client.CreateConnectionAsync(
WellKnownServiceHubService.RemoteDesignerAttributeService,
callbackTarget: this, cancellationToken).ConfigureAwait(false);
if (_keepAliveSession == null)
return;
// Now kick off scanning in the OOP process.
await _keepAliveSession.RunRemoteAsync(
await _connection.RunRemoteAsync(
nameof(IRemoteDesignerAttributeService.StartScanningForDesignerAttributesAsync),
solution: null,
arguments: Array.Empty<object>(),
......
......@@ -45,10 +45,10 @@ internal class VisualStudioProjectTelemetryService
private readonly VisualStudioWorkspaceImpl _workspace;
/// <summary>
/// Our connections to the remote OOP server. Created on demand when we startup and then
/// Our connection to the remote OOP server. Created on demand when we startup and then
/// kept around for the lifetime of this service.
/// </summary>
private KeepAliveSession? _keepAliveSession;
private RemoteServiceConnection? _connection;
/// <summary>
/// Queue where we enqueue the information we get from OOP to process in batch in the future.
......@@ -95,14 +95,12 @@ private async Task StartWorkerAsync(CancellationToken cancellationToken)
// Pass ourselves in as the callback target for the OOP service. As it discovers
// designer attributes it will call back into us to notify VS about it.
_keepAliveSession = await client.TryCreateKeepAliveSessionAsync(
_connection = await client.CreateConnectionAsync(
WellKnownServiceHubService.RemoteProjectTelemetryService,
callbackTarget: this, cancellationToken).ConfigureAwait(false);
if (_keepAliveSession == null)
return;
// Now kick off scanning in the OOP process.
await _keepAliveSession.RunRemoteAsync(
await _connection.RunRemoteAsync(
nameof(IRemoteProjectTelemetryService.ComputeProjectTelemetryAsync),
solution: null,
arguments: Array.Empty<object>(),
......
......@@ -95,7 +95,7 @@ private async Task<GlobalNotificationState> SendStartNotificationAsync(Task<Glob
return previousTask.Result;
}
_ = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteGlobalNotificationDeliveryService.OnGlobalOperationStarted),
solution: null,
......@@ -130,7 +130,7 @@ private async Task<GlobalNotificationState> SendStoppedNotificationAsync(Task<Gl
return previousTask.Result;
}
_ = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteGlobalNotificationDeliveryService.OnGlobalOperationStopped),
solution: null,
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
namespace Microsoft.VisualStudio.LanguageServices.Remote
{
internal interface IPooledConnectionReclamation
{
/// <summary>
/// Returns <see cref="JsonRpcConnection"/> instance to the pool it was allocated from.
/// </summary>
void Return(JsonRpcConnection connection);
}
}
......@@ -11,49 +11,135 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
namespace Microsoft.VisualStudio.LanguageServices.Remote
{
internal class JsonRpcConnection : RemoteHostClient.Connection
internal sealed class JsonRpcConnection : RemoteServiceConnection
{
private readonly HostWorkspaceServices _services;
// communication channel related to service information
private readonly RemoteEndPoint _serviceEndPoint;
private readonly IRemotableDataService _remotableDataService;
// Non-null if the connection is pooled.
private IPooledConnectionReclamation? _poolReclamation;
// True if the underlying end-point has been disposed.
private bool _disposed;
public JsonRpcConnection(
HostWorkspaceServices services,
TraceSource logger,
object? callbackTarget,
Stream serviceStream)
Stream serviceStream,
IPooledConnectionReclamation? poolReclamation)
{
_remotableDataService = services.GetRequiredService<IRemotableDataService>();
_services = services;
_serviceEndPoint = new RemoteEndPoint(serviceStream, logger, callbackTarget);
_serviceEndPoint.UnexpectedExceptionThrown += UnexpectedExceptionThrown;
_serviceEndPoint.StartListening();
_poolReclamation = poolReclamation;
#if DEBUG
_creationCallStack = Environment.StackTrace;
#endif
}
private void UnexpectedExceptionThrown(Exception exception)
=> RemoteHostCrashInfoBar.ShowInfoBar(_services, exception);
#if DEBUG
private readonly string _creationCallStack;
public override Task InvokeAsync(string targetName, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
=> _serviceEndPoint.InvokeAsync(targetName, arguments, cancellationToken);
~JsonRpcConnection()
{
// this can happen if someone kills OOP.
// when that happen, we don't want to crash VS, so this is debug only check
if (!Environment.HasShutdownStarted)
{
Debug.Assert(false,
$"Unless OOP process (RoslynCodeAnalysisService) is explicitly killed, this should have been disposed!\r\n {_creationCallStack}");
}
}
#endif
public override void Dispose()
{
// If the connection was taken from a pool, return it to the pool.
// Otherwise, dispose the underlying end-point and transition to "disposed" state.
public override Task<T> InvokeAsync<T>(string targetName, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
=> _serviceEndPoint.InvokeAsync<T>(targetName, arguments, cancellationToken);
var poolReclamation = Interlocked.Exchange(ref _poolReclamation, null);
if (poolReclamation != null)
{
poolReclamation.Return(this);
return;
}
public override Task<T> InvokeAsync<T>(string targetName, IReadOnlyList<object?> arguments, Func<Stream, CancellationToken, Task<T>> dataReader, CancellationToken cancellationToken)
=> _serviceEndPoint.InvokeAsync(targetName, arguments, dataReader, cancellationToken);
if (_disposed)
{
return;
}
_disposed = true;
protected override void DisposeImpl()
{
// dispose service and snapshot channels
_serviceEndPoint.UnexpectedExceptionThrown -= UnexpectedExceptionThrown;
_serviceEndPoint.Dispose();
base.DisposeImpl();
#if DEBUG
GC.SuppressFinalize(this);
#endif
}
private void UnexpectedExceptionThrown(Exception exception)
=> RemoteHostCrashInfoBar.ShowInfoBar(_services, exception);
public override async Task RunRemoteAsync(string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
{
if (solution != null)
{
using var scope = await _remotableDataService.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false);
using var _ = ArrayBuilder<object?>.GetInstance(arguments.Count + 1, out var argumentsBuilder);
argumentsBuilder.Add(scope.SolutionInfo);
argumentsBuilder.AddRange(arguments);
await _serviceEndPoint.InvokeAsync(targetName, argumentsBuilder, cancellationToken).ConfigureAwait(false);
}
else
{
await _serviceEndPoint.InvokeAsync(targetName, arguments, cancellationToken).ConfigureAwait(false);
}
}
public override async Task<T> RunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, Func<Stream, CancellationToken, Task<T>>? dataReader, CancellationToken cancellationToken)
{
if (solution != null)
{
using var scope = await _remotableDataService.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false);
using var _ = ArrayBuilder<object?>.GetInstance(arguments.Count + 1, out var argumentsBuilder);
argumentsBuilder.Add(scope.SolutionInfo);
argumentsBuilder.AddRange(arguments);
if (dataReader != null)
{
return await _serviceEndPoint.InvokeAsync(targetName, argumentsBuilder, dataReader, cancellationToken).ConfigureAwait(false);
}
else
{
return await _serviceEndPoint.InvokeAsync<T>(targetName, argumentsBuilder, cancellationToken).ConfigureAwait(false);
}
}
else if (dataReader != null)
{
return await _serviceEndPoint.InvokeAsync(targetName, arguments, dataReader, cancellationToken).ConfigureAwait(false);
}
else
{
return await _serviceEndPoint.InvokeAsync<T>(targetName, arguments, cancellationToken).ConfigureAwait(false);
}
}
}
}
......@@ -13,68 +13,86 @@
namespace Microsoft.VisualStudio.LanguageServices.Remote
{
internal sealed partial class ServiceHubRemoteHostClient
internal partial class ServiceHubRemoteHostClient
{
private delegate Task<Connection> ConnectionFactory(RemoteServiceName serviceName, CancellationToken cancellationToken);
public delegate Task<RemoteServiceConnection> ConnectionFactory(RemoteServiceName serviceName, IPooledConnectionReclamation poolReclamation, CancellationToken cancellationToken);
private sealed partial class ConnectionPool : IDisposable
internal sealed class ConnectionPools : IDisposable
{
private sealed class Pool : IPooledConnectionReclamation
{
private readonly ConcurrentQueue<JsonRpcConnection> _queue;
private readonly ConnectionPools _owner;
public Pool(ConnectionPools connectionPools)
{
_queue = new ConcurrentQueue<JsonRpcConnection>();
_owner = connectionPools;
}
public void Return(JsonRpcConnection connection)
=> _owner.Free(_queue, connection);
public bool TryAcquire(out JsonRpcConnection connection)
=> _queue.TryDequeue(out connection);
internal void DisposeConnections()
{
while (_queue.TryDequeue(out var connection))
{
connection.Dispose();
}
}
}
private readonly ConnectionFactory _connectionFactory;
private readonly ReaderWriterLockSlim _shutdownLock;
private readonly int _capacity;
private readonly int _capacityPerService;
// keyed to serviceName. each connection is for specific service such as CodeAnalysisService
private readonly ConcurrentDictionary<RemoteServiceName, ConcurrentQueue<Connection>> _pools;
private readonly ConcurrentDictionary<RemoteServiceName, Pool> _pools;
private bool _isDisposed;
public ConnectionPool(ConnectionFactory connectionFactory, int capacity)
public ConnectionPools(ConnectionFactory connectionFactory, int capacity)
{
_connectionFactory = connectionFactory;
_capacity = capacity;
_capacityPerService = capacity;
// initial value 4 is chosen to stop concurrent dictionary creating too many locks.
// and big enough for all our services such as codeanalysis, remotehost, snapshot and etc services
_pools = new ConcurrentDictionary<RemoteServiceName, ConcurrentQueue<Connection>>(concurrencyLevel: 4, capacity: 4);
_pools = new ConcurrentDictionary<RemoteServiceName, Pool>(concurrencyLevel: 4, capacity: 4);
_shutdownLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
}
public async Task<Connection> GetOrCreateConnectionAsync(RemoteServiceName serviceName, CancellationToken cancellationToken)
public async Task<RemoteServiceConnection> GetOrCreateConnectionAsync(RemoteServiceName serviceName, CancellationToken cancellationToken)
{
var queue = _pools.GetOrAdd(serviceName, _ => new ConcurrentQueue<Connection>());
if (queue.TryDequeue(out var connection))
var pool = _pools.GetOrAdd(serviceName, _ => new Pool(this));
if (pool.TryAcquire(out var connection))
{
return new PooledConnection(this, serviceName, connection);
return connection;
}
var newConnection = await _connectionFactory(serviceName, cancellationToken).ConfigureAwait(false);
return new PooledConnection(this, serviceName, newConnection);
return await _connectionFactory(serviceName, pool, cancellationToken).ConfigureAwait(false);
}
private void Free(RemoteServiceName serviceName, Connection connection)
internal void Free(ConcurrentQueue<JsonRpcConnection> pool, JsonRpcConnection connection)
{
using (_shutdownLock.DisposableRead())
{
if (_isDisposed)
// There is a race between checking the current pool capacity i nthe condition and
// and queueing connections to the pool in the else branch.
// The amount of pooled connections may thus exceed the capacity at times,
// or some connections might not end up returned into the pool and reused.
if (_isDisposed || pool.Count >= _capacityPerService)
{
// pool is not being used or
// manager is already shutdown
connection.Dispose();
return;
}
// queue must exist
var queue = _pools[serviceName];
if (queue.Count >= _capacity)
else
{
// let the connection actually go away
connection.Dispose();
return;
pool.Enqueue(connection);
}
// pool the connection
queue.Enqueue(connection);
}
}
......@@ -84,13 +102,9 @@ public void Dispose()
{
_isDisposed = true;
// let all connections in the pool to go away
foreach (var (_, queue) in _pools)
foreach (var (_, pool) in _pools)
{
while (queue.TryDequeue(out var connection))
{
connection.Dispose();
}
pool.DisposeConnections();
}
_pools.Clear();
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Remote;
namespace Microsoft.VisualStudio.LanguageServices.Remote
{
internal sealed partial class ServiceHubRemoteHostClient
{
private partial class ConnectionPool
{
private class PooledConnection : Connection
{
private readonly ConnectionPool _pool;
private readonly RemoteServiceName _serviceName;
private readonly Connection _connection;
public PooledConnection(ConnectionPool pool, RemoteServiceName serviceName, Connection connection)
{
_pool = pool;
_serviceName = serviceName;
_connection = connection;
}
public override Task InvokeAsync(string targetName, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
=> _connection.InvokeAsync(targetName, arguments, cancellationToken);
public override Task<T> InvokeAsync<T>(string targetName, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
=> _connection.InvokeAsync<T>(targetName, arguments, cancellationToken);
public override Task<T> InvokeAsync<T>(string targetName, IReadOnlyList<object> arguments, Func<Stream, CancellationToken, Task<T>> dataReader, CancellationToken cancellationToken)
=> _connection.InvokeAsync(targetName, arguments, dataReader, cancellationToken);
protected override void DisposeImpl()
{
_pool.Free(_serviceName, _connection);
base.DisposeImpl();
}
}
}
}
}
......@@ -5,6 +5,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
......@@ -16,14 +17,12 @@
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.ServiceHub.Client;
using Microsoft.VisualStudio.Telemetry;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
using StreamJsonRpc;
using Workspace = Microsoft.CodeAnalysis.Workspace;
namespace Microsoft.VisualStudio.LanguageServices.Remote
{
......@@ -31,23 +30,25 @@ internal sealed partial class ServiceHubRemoteHostClient : RemoteHostClient, IRe
{
private const int ConnectionPoolCapacity = 15;
private readonly HostWorkspaceServices _services;
private readonly IRemotableDataService _remotableDataService;
private readonly RemoteEndPoint _endPoint;
private readonly HubClient _hubClient;
private readonly HostGroup _hostGroup;
private readonly ConnectionPool? _connectionPool;
private readonly ConnectionPools? _connectionPools;
private ServiceHubRemoteHostClient(
HostWorkspaceServices services,
HubClient hubClient,
HostGroup hostGroup,
Stream stream)
: base(services)
{
_connectionPool = new ConnectionPool(
connectionFactory: (serviceName, cancellationToken) => CreateConnectionAsync(serviceName, callbackTarget: null, cancellationToken),
_connectionPools = new ConnectionPools(
connectionFactory: (serviceName, pool, cancellationToken) => CreateConnectionImplAsync(serviceName, callbackTarget: null, pool, cancellationToken),
capacity: ConnectionPoolCapacity);
_services = services;
_hubClient = hubClient;
_hostGroup = hostGroup;
......@@ -55,10 +56,12 @@ internal sealed partial class ServiceHubRemoteHostClient : RemoteHostClient, IRe
_endPoint.Disconnected += OnDisconnected;
_endPoint.UnexpectedExceptionThrown += OnUnexpectedExceptionThrown;
_endPoint.StartListening();
_remotableDataService = services.GetRequiredService<IRemotableDataService>();
}
private void OnUnexpectedExceptionThrown(Exception unexpectedException)
=> RemoteHostCrashInfoBar.ShowInfoBar(Services, unexpectedException);
=> RemoteHostCrashInfoBar.ShowInfoBar(_services, unexpectedException);
public static async Task<RemoteHostClient> CreateAsync(HostWorkspaceServices services, CancellationToken cancellationToken)
{
......@@ -67,7 +70,7 @@ public static async Task<RemoteHostClient> CreateAsync(HostWorkspaceServices ser
Logger.Log(FunctionId.RemoteHost_Bitness, KeyValueLogMessage.Create(LogType.Trace, m => m["64bit"] = RemoteHostOptions.IsServiceHubProcess64Bit(services)));
// let each client to have unique id so that we can distinguish different clients when service is restarted
var clientId = CreateClientId(Process.GetCurrentProcess().Id.ToString());
var clientId = $"VS ({Process.GetCurrentProcess().Id}) ({Guid.NewGuid()})";
var hostGroup = new HostGroup(clientId);
var hubClient = new HubClient("ManagedLanguage.IDE.RemoteHostClient");
......@@ -115,6 +118,9 @@ public static async Task<RemoteHostClient> CreateAsync(HostWorkspaceServices ser
{
var is64bit = RemoteHostOptions.IsServiceHubProcess64Bit(services);
// Make sure we are on the thread pool to avoid UI thread dependencies if external code uses ConfigureAwait(true)
await TaskScheduler.Default;
var descriptor = new ServiceDescriptor(serviceName.ToString(is64bit)) { HostGroup = hostGroup };
try
{
......@@ -153,24 +159,24 @@ static bool ReportNonFatalWatson(Exception e, CancellationToken cancellationToke
public override string ClientId => _hostGroup.Id;
protected override Task<Connection?> TryCreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken)
public override Task<RemoteServiceConnection> CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken)
{
// When callbackTarget is given, we can't share/pool connection since callbackTarget attaches a state to connection.
// so connection is only valid for that specific callbackTarget. it is up to the caller to keep connection open
// if he wants to reuse same connection.
if (callbackTarget == null && _connectionPool != null)
if (callbackTarget == null && _connectionPools != null)
{
return _connectionPool.GetOrCreateConnectionAsync(serviceName, cancellationToken).AsNullable();
return _connectionPools.GetOrCreateConnectionAsync(serviceName, cancellationToken);
}
return CreateConnectionAsync(serviceName, callbackTarget, cancellationToken).AsNullable();
return CreateConnectionImplAsync(serviceName, callbackTarget, poolReclamation: null, cancellationToken);
}
private async Task<Connection> CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken)
private async Task<RemoteServiceConnection> CreateConnectionImplAsync(RemoteServiceName serviceName, object? callbackTarget, IPooledConnectionReclamation? poolReclamation, CancellationToken cancellationToken)
{
var serviceStream = await RequestServiceAsync(Services, _hubClient, serviceName, _hostGroup, cancellationToken).ConfigureAwait(false);
return new JsonRpcConnection(Services, _hubClient.Logger, callbackTarget, serviceStream);
var serviceStream = await RequestServiceAsync(_services, _hubClient, serviceName, _hostGroup, cancellationToken).ConfigureAwait(false);
return new JsonRpcConnection(_services, _hubClient.Logger, callbackTarget, serviceStream, poolReclamation);
}
public override void Dispose()
......@@ -179,7 +185,7 @@ public override void Dispose()
_endPoint.UnexpectedExceptionThrown -= OnUnexpectedExceptionThrown;
_endPoint.Dispose();
_connectionPool?.Dispose();
_connectionPools?.Dispose();
_hubClient.Dispose();
base.Dispose();
......@@ -202,7 +208,7 @@ public async Task GetAssetsAsync(int scopeId, Checksum[] checksums, string pipeN
await RemoteEndPoint.WriteDataToNamedPipeAsync(
pipeName,
(scopeId, checksums),
(writer, data, cancellationToken) => RemoteHostAssetSerialization.WriteDataAsync(writer, RemotableDataService, data.scopeId, data.checksums, cancellationToken),
(writer, data, cancellationToken) => RemoteHostAssetSerialization.WriteDataAsync(writer, _remotableDataService, data.scopeId, data.checksums, cancellationToken),
cancellationToken).ConfigureAwait(false);
}
}
......@@ -219,7 +225,7 @@ public Task<bool> IsExperimentEnabledAsync(string experimentName, CancellationTo
{
try
{
return Task.FromResult(Services.GetRequiredService<IExperimentationService>().IsExperimentEnabled(experimentName));
return Task.FromResult(_services.GetRequiredService<IExperimentationService>().IsExperimentEnabled(experimentName));
}
catch (Exception ex) when (FatalError.ReportWithoutCrashUnlessCanceledAndPropagate(ex, cancellationToken))
{
......
......@@ -133,7 +133,7 @@ private async Task SynchronizePrimaryWorkspaceAsync(CancellationToken cancellati
{
var checksum = await solution.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false);
_ = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.RemoteHost,
nameof(IRemoteHostService.SynchronizePrimaryWorkspaceAsync),
solution,
......@@ -204,7 +204,7 @@ private void PushTextChanges(Document oldDocument, Document newDocument)
var state = await oldDocument.State.GetStateChecksumsAsync(CancellationToken).ConfigureAwait(false);
_ = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.RemoteHost,
nameof(IRemoteHostService.SynchronizeTextAsync),
solution: null,
......
......@@ -47,7 +47,7 @@ internal class VisualStudioTodoCommentsService
/// Our connections to the remote OOP server. Created on demand when we startup and then
/// kept around for the lifetime of this service.
/// </summary>
private KeepAliveSession? _keepAliveSession;
private RemoteServiceConnection? _connection;
/// <summary>
/// Queue where we enqueue the information we get from OOP to process in batch in the future.
......@@ -105,17 +105,15 @@ private async Task StartWorkerAsync(CancellationToken cancellationToken)
// Pass ourselves in as the callback target for the OOP service. As it discovers
// todo comments it will call back into us to notify VS about it.
_keepAliveSession = await client.TryCreateKeepAliveSessionAsync(
_connection = await client.CreateConnectionAsync(
WellKnownServiceHubService.RemoteTodoCommentsService,
callbackTarget: this, cancellationToken).ConfigureAwait(false);
if (_keepAliveSession == null)
return;
// Now that we've started, let the VS todo list know to start listening to us
_eventListenerTracker.EnsureEventListener(_workspace, this);
// Now kick off scanning in the OOP process.
await _keepAliveSession.RunRemoteAsync(
await _connection.RunRemoteAsync(
nameof(IRemoteTodoCommentsService.ComputeTodoCommentsAsync),
solution: null,
arguments: Array.Empty<object>(),
......
......@@ -10,8 +10,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Utilities
{
internal static class IVsShellExtensions
{
// tri-state: uninitialized (0), devenv is in command line mode (1), devenv is not in command line mode (-1)
private static volatile int s_isInCommandLineMode;
/// <summary>
/// Returns true if devenv is invoked in command line mode for build, e.g. devenv /rebuild MySolution.sln
/// </summary>
public static bool IsInCommandLineMode
{
get
......
......@@ -361,11 +361,6 @@ private string GetInstalledVersion(string packageName, EnvDTE.Project dteProject
var metadata = installedPackages.FirstOrDefault(m => m.Id == packageName);
return metadata?.VersionString;
}
catch (ArgumentException e) when (IsKnownNugetIssue(e))
{
// Nuget may throw an ArgumentException when there is something about the project
// they do not like/support.
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
}
......@@ -373,15 +368,6 @@ private string GetInstalledVersion(string packageName, EnvDTE.Project dteProject
return null;
}
private bool IsKnownNugetIssue(ArgumentException exception)
{
// See https://github.com/NuGet/Home/issues/4706
// Nuget throws on legal projects. We do not want to report this exception
// as it is known (and NFWs are expensive), but we do want to report if we
// run into anything else.
return exception.Message.Contains("is not a valid version string");
}
private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{
ThisCanBeCalledOnAnyThread();
......@@ -561,12 +547,7 @@ private void ProcessProjectChange(Solution solution, ProjectId projectId)
isEnabled = true;
}
catch (ArgumentException e) when (IsKnownNugetIssue(e))
{
// Nuget may throw an ArgumentException when there is something about the project
// they do not like/support.
}
catch (InvalidOperationException e) when (e.StackTrace.Contains("NuGet.PackageManagement.VisualStudio.NetCorePackageReferenceProject.GetPackageSpecsAsync"))
catch (InvalidOperationException)
{
// NuGet throws an InvalidOperationException if details
// for the project fail to load. We don't need to report
......
......@@ -83,8 +83,8 @@ public async Task TestSessionWithNoSolution()
var mock = new MockLogAndProgressService();
var client = await service.TryGetRemoteHostClientAsync(CancellationToken.None);
using var session = await client.TryCreateKeepAliveSessionAsync(WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine, callbackTarget: mock, CancellationToken.None);
await session.RunRemoteAsync(
using var connection = await client.CreateConnectionAsync(WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine, callbackTarget: mock, CancellationToken.None);
await connection.RunRemoteAsync(
nameof(IRemoteSymbolSearchUpdateEngine.UpdateContinuouslyAsync),
solution: null,
new object[] { "emptySource", Path.GetTempPath() },
......@@ -108,10 +108,10 @@ public async Task TestSessionClosed()
});
// create session that stay alive until client alive (ex, SymbolSearchUpdateEngine)
using var session = await client.TryCreateKeepAliveSessionAsync(serviceName, callbackTarget: null, CancellationToken.None);
using var connection = await client.CreateConnectionAsync(serviceName, callbackTarget: null, CancellationToken.None);
// mimic unfortunate call that happens to be in the middle of communication.
var task = session.RunRemoteAsync("TestMethodAsync", solution: null, arguments: null, CancellationToken.None);
var task = connection.RunRemoteAsync("TestMethodAsync", solution: null, arguments: null, CancellationToken.None);
// make client to go away
client.Dispose();
......
......@@ -117,13 +117,13 @@ private async Task<ImmutableArray<SymbolInformation>> GetVsSearchResultsAsync(Te
private async Task UpdatePrimaryWorkspace(InProcRemoteHostClient client, Solution solution)
{
Assert.True(await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.RemoteHost,
nameof(IRemoteHostService.SynchronizePrimaryWorkspaceAsync),
solution,
new object[] { await solution.State.GetChecksumAsync(CancellationToken.None), _solutionVersion++ },
callbackTarget: null,
CancellationToken.None));
CancellationToken.None);
}
}
}
......@@ -102,7 +102,7 @@ public async Task TestRemoteHostTextSynchronize()
var newText = oldText.WithChanges(new TextChange(TextSpan.FromBounds(0, 0), "/* test */"));
// sync
_ = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.RemoteHost,
nameof(IRemoteHostService.SynchronizeTextAsync),
solution: null,
......@@ -139,12 +139,12 @@ public async Task TestTodoComments()
var callback = new TodoCommentsListener();
using var client = await InProcRemoteHostClient.CreateAsync(workspace.Services, runCacheCleanup: false);
using var session = await client.TryCreateKeepAliveSessionAsync(
using var connection = await client.CreateConnectionAsync(
WellKnownServiceHubService.RemoteTodoCommentsService,
callback,
cancellationTokenSource.Token);
var invokeTask = session.RunRemoteAsync(
var invokeTask = connection.RunRemoteAsync(
nameof(IRemoteTodoCommentsService.ComputeTodoCommentsAsync),
solution: null,
arguments: Array.Empty<object>(),
......@@ -224,12 +224,12 @@ class Test { }");
var callback = new DesignerAttributeListener();
using var client = await InProcRemoteHostClient.CreateAsync(workspace.Services, runCacheCleanup: false);
using var session = await client.TryCreateKeepAliveSessionAsync(
using var connection = await client.CreateConnectionAsync(
WellKnownServiceHubService.RemoteDesignerAttributeService,
callback,
cancellationTokenSource.Token);
var invokeTask = session.RunRemoteAsync(
var invokeTask = connection.RunRemoteAsync(
nameof(IRemoteDesignerAttributeService.StartScanningForDesignerAttributesAsync),
solution: null,
arguments: Array.Empty<object>(),
......@@ -468,13 +468,13 @@ private static (Project, Document) GetProjectAndDocument(Solution solution, stri
private async Task UpdatePrimaryWorkspace(InProcRemoteHostClient client, Solution solution)
{
Assert.True(await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.RemoteHost,
nameof(IRemoteHostService.SynchronizePrimaryWorkspaceAsync),
solution,
new object[] { await solution.State.GetChecksumAsync(CancellationToken.None), _solutionVersion++ },
callbackTarget: null,
CancellationToken.None));
CancellationToken.None);
}
private static Solution Populate(Solution solution)
......
......@@ -149,7 +149,7 @@ public async Task TestCancellationOnSessionWithSolution()
var service = solution.Workspace.Services.GetService<IRemotableDataService>();
var source = new CancellationTokenSource();
using var session = new KeepAliveSession(new InvokeThrowsCancellationConnection(source), service);
using var session = new InvokeThrowsCancellationConnection(source);
var exception = await Assert.ThrowsAnyAsync<OperationCanceledException>(() => SessionWithSolution.CreateAsync(session, solution, source.Token));
Assert.Equal(exception.CancellationToken, source.Token);
......@@ -325,7 +325,7 @@ public MyUpdateSource(Workspace workspace)
public override Workspace Workspace => _workspace;
}
private sealed class InvokeThrowsCancellationConnection : RemoteHostClient.Connection
private sealed class InvokeThrowsCancellationConnection : RemoteServiceConnection
{
private readonly CancellationTokenSource _source;
......@@ -334,22 +334,27 @@ public InvokeThrowsCancellationConnection(CancellationTokenSource source)
_source = source;
}
public override Task InvokeAsync(string targetName, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
public override void Dispose()
{
}
public override Task RunRemoteAsync(string targetName, Solution solution, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
{
// cancel and throw cancellation exception
_source.Cancel();
_source.Token.ThrowIfCancellationRequested();
throw Utilities.ExceptionUtilities.Unreachable;
throw ExceptionUtilities.Unreachable;
}
public override Task<T> InvokeAsync<T>(
string targetName, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
=> throw new NotImplementedException();
public override Task<T> RunRemoteAsync<T>(string targetName, Solution solution, IReadOnlyList<object> arguments, Func<Stream, CancellationToken, Task<T>> dataReader, CancellationToken cancellationToken)
{
// cancel and throw cancellation exception
_source.Cancel();
_source.Token.ThrowIfCancellationRequested();
public override Task<T> InvokeAsync<T>(
string targetName, IReadOnlyList<object> arguments, Func<Stream, CancellationToken, Task<T>> dataReader, CancellationToken cancellationToken)
=> throw new NotImplementedException();
throw ExceptionUtilities.Unreachable;
}
}
}
}
......@@ -27,8 +27,8 @@ internal RazorLanguageServiceClient(RemoteHostClient client, string serviceName)
_serviceName = new RemoteServiceName(serviceName);
}
public Task<Optional<T>> TryRunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, object? callbackTarget, CancellationToken cancellationToken)
=> _client.TryRunRemoteAsync<T>(_serviceName, targetName, solution, arguments, callbackTarget, cancellationToken);
public async Task<Optional<T>> TryRunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, object? callbackTarget, CancellationToken cancellationToken)
=> await _client.RunRemoteAsync<T>(_serviceName, targetName, solution, arguments, callbackTarget, cancellationToken).ConfigureAwait(false);
[Obsolete("Use TryRunRemoteAsync instead")]
public async Task<Session?> CreateSessionAsync(Solution solution, object? callbackTarget = null, CancellationToken cancellationToken = default)
......@@ -39,23 +39,19 @@ public Task<Optional<T>> TryRunRemoteAsync<T>(string targetName, Solution? solut
return null;
}
var keepAliveSession = await _client.TryCreateKeepAliveSessionAsync(_serviceName, callbackTarget: null, cancellationToken).ConfigureAwait(false);
if (keepAliveSession == null)
{
return null;
}
var connection = await _client.CreateConnectionAsync(_serviceName, callbackTarget: null, cancellationToken).ConfigureAwait(false);
SessionWithSolution? session = null;
try
{
// transfer ownership of the connection to the session object:
session = await SessionWithSolution.CreateAsync(keepAliveSession, solution, cancellationToken).ConfigureAwait(false);
session = await SessionWithSolution.CreateAsync(connection, solution, cancellationToken).ConfigureAwait(false);
}
finally
{
if (session == null)
{
keepAliveSession.Dispose();
connection.Dispose();
}
}
......
......@@ -69,7 +69,7 @@ public static void SetLoggers(IGlobalOptionService optionService, IThreadingCont
var functionIds = GetFunctionIds(options).ToList();
threadingContext.JoinableTaskFactory.Run(() => client.TryRunRemoteAsync(
threadingContext.JoinableTaskFactory.Run(() => client.RunRemoteAsync(
WellKnownServiceHubService.RemoteHost,
nameof(IRemoteHostService.SetLoggingFunctionIds),
solution: null,
......
......@@ -63,7 +63,7 @@ private static async Task<bool> TryAddSemanticClassificationsInRemoteProcessAsyn
if (client == null)
return false;
var classifiedSpans = await client.TryRunRemoteAsync<SerializableClassifiedSpans>(
var classifiedSpans = await client.RunRemoteAsync<SerializableClassifiedSpans>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteSemanticClassificationService.GetSemanticClassificationsAsync),
document.Project.Solution,
......@@ -71,10 +71,7 @@ private static async Task<bool> TryAddSemanticClassificationsInRemoteProcessAsyn
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (!classifiedSpans.HasValue)
return false;
classifiedSpans.Value.Rehydrate(result);
classifiedSpans.Rehydrate(result);
return true;
}
......
......@@ -28,7 +28,7 @@ public static async Task<Optional<T>> TryRunRemoteAsync<T>(Workspace workspace,
return default;
}
return await client.TryRunRemoteAsync<T>(WellKnownServiceHubService.IntelliCode, targetName, solution, arguments, callbackTarget: null, cancellationToken).ConfigureAwait(false);
return await client.RunRemoteAsync<T>(WellKnownServiceHubService.IntelliCode, targetName, solution, arguments, callbackTarget: null, cancellationToken).ConfigureAwait(false);
}
}
}
......@@ -13,29 +13,29 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api
{
internal readonly struct UnitTestingKeepAliveSessionWrapper
{
internal KeepAliveSession? UnderlyingObject { get; }
internal RemoteServiceConnection UnderlyingObject { get; }
internal UnitTestingKeepAliveSessionWrapper(KeepAliveSession? underlyingObject)
internal UnitTestingKeepAliveSessionWrapper(RemoteServiceConnection underlyingObject)
=> UnderlyingObject = underlyingObject;
public bool IsDefault => UnderlyingObject == null;
public Task<T> TryInvokeAsync<T>(string targetName, Solution solution, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
=> UnderlyingObject!.RunRemoteAsync<T>(targetName, solution, arguments, cancellationToken);
=> UnderlyingObject.RunRemoteAsync<T>(targetName, solution, arguments, cancellationToken);
public async Task<bool> TryInvokeAsync(string targetName, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
{
await UnderlyingObject!.RunRemoteAsync(targetName, solution: null, arguments, cancellationToken).ConfigureAwait(false);
await UnderlyingObject.RunRemoteAsync(targetName, solution: null, arguments, cancellationToken).ConfigureAwait(false);
return true;
}
public async Task<bool> TryInvokeAsync(string targetName, Solution solution, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
{
await UnderlyingObject!.RunRemoteAsync(targetName, solution, arguments, cancellationToken).ConfigureAwait(false);
await UnderlyingObject.RunRemoteAsync(targetName, solution, arguments, cancellationToken).ConfigureAwait(false);
return true;
}
public Task<T> TryInvokeAsync<T>(string targetName, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
=> UnderlyingObject!.RunRemoteAsync<T>(targetName, solution: null, arguments, cancellationToken);
=> UnderlyingObject.RunRemoteAsync<T>(targetName, solution: null, arguments, cancellationToken);
}
}
......@@ -21,29 +21,25 @@ internal UnitTestingRemoteHostClientWrapper(RemoteHostClient underlyingObject)
public async Task<UnitTestingKeepAliveSessionWrapper> TryCreateUnitTestingKeepAliveSessionWrapperAsync(string serviceName, CancellationToken cancellationToken)
{
var keepAliveSession = await UnderlyingObject.TryCreateKeepAliveSessionAsync(new RemoteServiceName(serviceName), callbackTarget: null, cancellationToken).ConfigureAwait(false);
return new UnitTestingKeepAliveSessionWrapper(keepAliveSession);
var connection = await UnderlyingObject.CreateConnectionAsync(new RemoteServiceName(serviceName), callbackTarget: null, cancellationToken).ConfigureAwait(false);
return new UnitTestingKeepAliveSessionWrapper(connection);
}
public async Task<UnitTestingSessionWithSolutionWrapper> TryCreateUnitingSessionWithSolutionWrapperAsync(string serviceName, Solution solution, CancellationToken cancellationToken)
{
var keepAliveSession = await UnderlyingObject.TryCreateKeepAliveSessionAsync(new RemoteServiceName(serviceName), callbackTarget: null, cancellationToken).ConfigureAwait(false);
if (keepAliveSession == null)
{
return default;
}
var connection = await UnderlyingObject.CreateConnectionAsync(new RemoteServiceName(serviceName), callbackTarget: null, cancellationToken).ConfigureAwait(false);
SessionWithSolution? session = null;
try
{
// transfer ownership of the connection to the session object:
session = await SessionWithSolution.CreateAsync(keepAliveSession, solution, cancellationToken).ConfigureAwait(false);
session = await SessionWithSolution.CreateAsync(connection, solution, cancellationToken).ConfigureAwait(false);
}
finally
{
if (session == null)
{
keepAliveSession.Dispose();
connection.Dispose();
}
}
......
......@@ -41,7 +41,7 @@ internal static partial class DeclarationFinder
{
var solution = project.Solution;
var result = await client.TryRunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
var result = await client.RunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteSymbolFinder.FindAllDeclarationsWithNormalQueryAsync),
solution,
......@@ -49,10 +49,7 @@ internal static partial class DeclarationFinder
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return await RehydrateAsync(solution, result.Value, cancellationToken).ConfigureAwait(false);
}
return await RehydrateAsync(solution, result, cancellationToken).ConfigureAwait(false);
}
return await FindAllDeclarationsWithNormalQueryInCurrentProcessAsync(
......
......@@ -44,7 +44,7 @@ internal static partial class DeclarationFinder
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
var result = await client.RunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteSymbolFinder.FindSolutionSourceDeclarationsWithNormalQueryAsync),
solution,
......@@ -52,10 +52,7 @@ internal static partial class DeclarationFinder
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return await RehydrateAsync(solution, result.Value, cancellationToken).ConfigureAwait(false);
}
return await RehydrateAsync(solution, result, cancellationToken).ConfigureAwait(false);
}
return await FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
......@@ -83,7 +80,7 @@ internal static partial class DeclarationFinder
var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
var result = await client.RunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithNormalQueryAsync),
project.Solution,
......@@ -91,10 +88,7 @@ internal static partial class DeclarationFinder
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return await RehydrateAsync(project.Solution, result.Value, cancellationToken).ConfigureAwait(false);
}
return await RehydrateAsync(project.Solution, result, cancellationToken).ConfigureAwait(false);
}
return await FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
......@@ -118,7 +112,7 @@ internal static partial class DeclarationFinder
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
var result = await client.RunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteSymbolFinder.FindSolutionSourceDeclarationsWithPatternAsync),
solution,
......@@ -126,10 +120,7 @@ internal static partial class DeclarationFinder
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return await RehydrateAsync(solution, result.Value, cancellationToken).ConfigureAwait(false);
}
return await RehydrateAsync(solution, result, cancellationToken).ConfigureAwait(false);
}
return await FindSourceDeclarationsWithPatternInCurrentProcessAsync(
......@@ -152,7 +143,7 @@ internal static partial class DeclarationFinder
var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
var result = await client.RunRemoteAsync<IList<SerializableSymbolAndProjectId>>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithPatternAsync),
project.Solution,
......@@ -160,10 +151,7 @@ internal static partial class DeclarationFinder
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return await RehydrateAsync(project.Solution, result.Value, cancellationToken).ConfigureAwait(false);
}
return await RehydrateAsync(project.Solution, result, cancellationToken).ConfigureAwait(false);
}
return await FindSourceDeclarationsWithPatternInCurrentProcessAsync(
......
......@@ -32,7 +32,7 @@ internal static partial class DependentTypeFinder
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
var result = await client.RunRemoteAsync<ImmutableArray<SerializableSymbolAndProjectId>>(
WellKnownServiceHubService.CodeAnalysis,
remoteFunctionName,
solution,
......@@ -45,10 +45,7 @@ internal static partial class DependentTypeFinder
null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return await RehydrateAsync(solution, result.Value, cancellationToken).ConfigureAwait(false);
}
return await RehydrateAsync(solution, result, cancellationToken).ConfigureAwait(false);
}
}
}
......
......@@ -29,21 +29,18 @@ public static partial class SymbolFinder
// the 'progress' parameter which will then update the UI.
var serverCallback = new FindLiteralsServerCallback(solution, progress);
var success = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteSymbolFinder.FindLiteralReferencesAsync),
solution,
new object[] { value, typeCode },
serverCallback,
cancellationToken).ConfigureAwait(false);
if (success)
{
return;
}
}
await FindLiteralReferencesInCurrentProcessAsync(value, solution, progress, cancellationToken).ConfigureAwait(false);
else
{
await FindLiteralReferencesInCurrentProcessAsync(value, solution, progress, cancellationToken).ConfigureAwait(false);
}
}
}
......
......@@ -38,7 +38,7 @@ public static partial class SymbolFinder
// the 'progress' parameter which will then update the UI.
var serverCallback = new FindReferencesServerCallback(solution, progress, cancellationToken);
var success = await client.TryRunRemoteAsync(
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteSymbolFinder.FindReferencesAsync),
solution,
......@@ -51,8 +51,7 @@ public static partial class SymbolFinder
serverCallback,
cancellationToken).ConfigureAwait(false);
if (success)
return;
return;
}
}
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
namespace Microsoft.CodeAnalysis.Remote
{
/// <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
///
/// when this is used, solution must be explicitly passed around between client (VS) and remote host (OOP)
/// </summary>
internal sealed class KeepAliveSession : IDisposable
{
private readonly IRemotableDataService _remotableDataService;
private readonly RemoteHostClient.Connection _connection;
public KeepAliveSession(RemoteHostClient.Connection connection, IRemotableDataService remotableDataService)
{
_remotableDataService = remotableDataService;
_connection = connection;
}
public void Dispose()
{
_connection.Dispose();
}
public Task RunRemoteAsync(string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
=> RemoteHostClient.RunRemoteAsync(_connection, _remotableDataService, targetName, solution, arguments, cancellationToken);
public Task<T> RunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
=> RemoteHostClient.RunRemoteAsync<T>(_connection, _remotableDataService, targetName, solution, arguments, dataReader: null, cancellationToken);
}
}
......@@ -9,15 +9,9 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Host;
#if DEBUG
using System.Diagnostics;
#endif
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
......@@ -27,17 +21,8 @@ namespace Microsoft.CodeAnalysis.Remote
/// </summary>
internal abstract class RemoteHostClient : IDisposable
{
public readonly HostWorkspaceServices Services;
public event EventHandler<bool>? StatusChanged;
internal readonly IRemotableDataService RemotableDataService;
protected RemoteHostClient(HostWorkspaceServices services)
{
Services = services;
RemotableDataService = services.GetRequiredService<IRemotableDataService>();
}
/// <summary>
/// Return an unique string per client.
///
......@@ -46,15 +31,6 @@ protected RemoteHostClient(HostWorkspaceServices services)
/// </summary>
public abstract string ClientId { get; }
/// <summary>
/// Create <see cref="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>
protected abstract Task<Connection?> TryCreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken);
protected void Started()
{
OnStatusChanged(started: true);
......@@ -66,9 +42,6 @@ public virtual void Dispose()
private void OnStatusChanged(bool started)
=> StatusChanged?.Invoke(this, started);
public static string CreateClientId(string prefix)
=> $"VS ({prefix}) ({Guid.NewGuid()})";
public static Task<RemoteHostClient?> TryGetClientAsync(Project project, CancellationToken cancellationToken)
{
if (!RemoteSupportedLanguages.IsSupported(project.Language))
......@@ -93,163 +66,21 @@ public static string CreateClientId(string prefix)
return service.TryGetRemoteHostClientAsync(cancellationToken);
}
/// <summary>
/// Creates <see cref="KeepAliveSession"/> for the <paramref name="serviceName"/>, otherwise returns <see langword="null"/>.
/// </summary>
public async Task<KeepAliveSession?> TryCreateKeepAliveSessionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken)
{
var connection = await TryCreateConnectionAsync(serviceName, callbackTarget, cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return null;
}
return new KeepAliveSession(connection, RemotableDataService);
}
public async Task<bool> TryRunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList<object?> arguments, object? callbackTarget, CancellationToken cancellationToken)
{
using var connection = await TryCreateConnectionAsync(serviceName, callbackTarget, cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return false;
}
await RunRemoteAsync(connection, RemotableDataService, targetName, solution, arguments, cancellationToken).ConfigureAwait(false);
return true;
}
public Task<Optional<T>> TryRunRemoteAsync<T>(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList<object?> arguments, object? callbackTarget, CancellationToken cancellationToken)
=> TryRunRemoteAsync<T>(serviceName, targetName, solution, arguments, callbackTarget, dataReader: null, cancellationToken);
public async Task<Optional<T>> TryRunRemoteAsync<T>(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList<object?> arguments, object? callbackTarget, Func<Stream, CancellationToken, Task<T>>? dataReader, CancellationToken cancellationToken)
{
using var connection = await TryCreateConnectionAsync(serviceName, callbackTarget, cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return default;
}
return await RunRemoteAsync<T>(connection, RemotableDataService, targetName, solution, arguments, dataReader, cancellationToken).ConfigureAwait(false);
}
internal static async Task RunRemoteAsync(Connection connection, IRemotableDataService remoteDataService, string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
{
if (solution != null)
{
using var scope = await remoteDataService.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false);
using var _ = ArrayBuilder<object?>.GetInstance(arguments.Count + 1, out var argumentsBuilder);
argumentsBuilder.Add(scope.SolutionInfo);
argumentsBuilder.AddRange(arguments);
await connection.InvokeAsync(targetName, argumentsBuilder, cancellationToken).ConfigureAwait(false);
}
else
{
await connection.InvokeAsync(targetName, arguments, cancellationToken).ConfigureAwait(false);
}
}
public abstract Task<RemoteServiceConnection> CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken);
internal static async Task<T> RunRemoteAsync<T>(Connection connection, IRemotableDataService remoteDataService, string targetName, Solution? solution, IReadOnlyList<object?> arguments, Func<Stream, CancellationToken, Task<T>>? dataReader, CancellationToken cancellationToken)
public async Task RunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList<object?> arguments, object? callbackTarget, CancellationToken cancellationToken)
{
if (solution != null)
{
using var scope = await remoteDataService.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false);
using var _ = ArrayBuilder<object?>.GetInstance(arguments.Count + 1, out var argumentsBuilder);
argumentsBuilder.Add(scope.SolutionInfo);
argumentsBuilder.AddRange(arguments);
if (dataReader != null)
{
return await connection.InvokeAsync(targetName, argumentsBuilder, dataReader, cancellationToken).ConfigureAwait(false);
}
else
{
return await connection.InvokeAsync<T>(targetName, argumentsBuilder, cancellationToken).ConfigureAwait(false);
}
}
else if (dataReader != null)
{
return await connection.InvokeAsync(targetName, arguments, dataReader, cancellationToken).ConfigureAwait(false);
}
else
{
return await connection.InvokeAsync<T>(targetName, arguments, cancellationToken).ConfigureAwait(false);
}
using var connection = await CreateConnectionAsync(serviceName, callbackTarget, cancellationToken).ConfigureAwait(false);
await connection.RunRemoteAsync(targetName, solution, arguments, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// NoOpClient is used if a user killed our remote host process. Basically this client never
/// create a session
/// </summary>
public class NoOpClient : RemoteHostClient
{
public NoOpClient(HostWorkspaceServices services)
: base(services)
{
}
public Task<T> RunRemoteAsync<T>(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList<object?> arguments, object? callbackTarget, CancellationToken cancellationToken)
=> RunRemoteAsync<T>(serviceName, targetName, solution, arguments, callbackTarget, dataReader: null, cancellationToken);
public override string ClientId => nameof(NoOpClient);
protected override Task<Connection?> TryCreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken)
=> SpecializedTasks.Null<Connection>();
}
/// <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
public async Task<T> RunRemoteAsync<T>(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList<object?> arguments, object? callbackTarget, Func<Stream, CancellationToken, Task<T>>? dataReader, CancellationToken cancellationToken)
{
private bool _disposed;
protected Connection()
{
#if DEBUG
_creationCallStack = Environment.StackTrace;
#endif
_disposed = false;
}
public abstract Task InvokeAsync(string targetName, IReadOnlyList<object?> arguments, CancellationToken cancellationToken);
public abstract Task<T> InvokeAsync<T>(string targetName, IReadOnlyList<object?> arguments, CancellationToken cancellationToken);
public abstract Task<T> InvokeAsync<T>(string targetName, IReadOnlyList<object?> arguments, Func<Stream, CancellationToken, Task<T>> dataReader, CancellationToken cancellationToken);
protected virtual void DisposeImpl()
{
// do nothing
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
DisposeImpl();
GC.SuppressFinalize(this);
}
#if DEBUG
private readonly string _creationCallStack;
~Connection()
{
// this can happen if someone kills OOP.
// when that happen, we don't want to crash VS, so this is debug only check
if (!Environment.HasShutdownStarted)
{
Debug.Assert(false,
$"Unless OOP process (RoslynCodeAnalysisService) is explicitly killed, this should have been disposed!\r\n {_creationCallStack}");
}
}
#endif
using var connection = await CreateConnectionAsync(serviceName, callbackTarget, cancellationToken).ConfigureAwait(false);
return await connection.RunRemoteAsync(targetName, solution, arguments, dataReader, cancellationToken).ConfigureAwait(false);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.Remote
{
internal abstract class RemoteServiceConnection : IDisposable
{
public abstract void Dispose();
public abstract Task RunRemoteAsync(string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken);
public abstract Task<T> RunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, Func<Stream, CancellationToken, Task<T>>? dataReader, CancellationToken cancellationToken);
public Task<T> RunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
=> RunRemoteAsync<T>(targetName, solution, arguments, dataReader: null, cancellationToken);
}
}
......@@ -15,12 +15,12 @@ namespace Microsoft.CodeAnalysis.Remote
[Obsolete("Only used by Razor and LUT", error: false)]
internal sealed class SessionWithSolution : IDisposable
{
internal readonly KeepAliveSession KeepAliveSession;
internal readonly RemoteServiceConnection KeepAliveSession;
private readonly PinnedRemotableDataScope _scope;
public static async Task<SessionWithSolution> CreateAsync(KeepAliveSession keepAliveSession, Solution solution, CancellationToken cancellationToken)
public static async Task<SessionWithSolution> CreateAsync(RemoteServiceConnection connection, Solution solution, CancellationToken cancellationToken)
{
Contract.ThrowIfNull(keepAliveSession);
Contract.ThrowIfNull(connection);
Contract.ThrowIfNull(solution);
var service = solution.Workspace.Services.GetRequiredService<IRemotableDataService>();
......@@ -31,14 +31,14 @@ public static async Task<SessionWithSolution> CreateAsync(KeepAliveSession keepA
{
// set connection state for this session.
// we might remove this in future. see https://github.com/dotnet/roslyn/issues/24836
await keepAliveSession.RunRemoteAsync(
await connection.RunRemoteAsync(
"Initialize",
solution: null,
new object[] { scope.SolutionInfo },
cancellationToken).ConfigureAwait(false);
// transfer ownership of connection and scope to the session object:
session = new SessionWithSolution(keepAliveSession, scope);
session = new SessionWithSolution(connection, scope);
}
finally
{
......@@ -51,9 +51,9 @@ public static async Task<SessionWithSolution> CreateAsync(KeepAliveSession keepA
return session;
}
private SessionWithSolution(KeepAliveSession keepAliveSession, PinnedRemotableDataScope scope)
private SessionWithSolution(RemoteServiceConnection connection, PinnedRemotableDataScope scope)
{
KeepAliveSession = keepAliveSession;
KeepAliveSession = connection;
_scope = scope;
}
......
......@@ -52,7 +52,7 @@ internal static partial class ConflictResolver
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<SerializableConflictResolution?>(
var result = await client.RunRemoteAsync<SerializableConflictResolution?>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteRenamer.ResolveConflictsAsync),
solution,
......@@ -65,8 +65,8 @@ internal static partial class ConflictResolver
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue && result.Value != null)
return await result.Value.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false);
if (result != null)
return await result.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false);
}
}
......
......@@ -135,7 +135,7 @@ internal sealed partial class RenameLocations
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<SerializableRenameLocations?>(
var result = await client.RunRemoteAsync<SerializableRenameLocations?>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteRenamer.FindRenameLocationsAsync),
solution,
......@@ -147,10 +147,11 @@ internal sealed partial class RenameLocations
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue && result.Value != null)
if (result != null)
{
var rehydrated = await RenameLocations.TryRehydrateAsync(
solution, result.Value, cancellationToken).ConfigureAwait(false);
solution, result, cancellationToken).ConfigureAwait(false);
if (rehydrated != null)
return rehydrated;
}
......
......@@ -127,7 +127,7 @@ internal static Task<RenameLocations> FindRenameLocationsAsync(Solution solution
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<SerializableConflictResolution?>(
var result = await client.RunRemoteAsync<SerializableConflictResolution?>(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteRenamer.RenameSymbolAsync),
solution,
......@@ -141,8 +141,8 @@ internal static Task<RenameLocations> FindRenameLocationsAsync(Solution solution
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue && result.Value != null)
return await result.Value.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false);
if (result != null)
return await result.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false);
}
}
}
......
......@@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api
{
/// <summary>
/// Because PinnedSolutionInfo is internal, and it is directly passed into the callee in
/// <see cref="KeepAliveSession.RunRemoteAsync{T}(string, Solution, System.Collections.Generic.IReadOnlyList{object}, System.Threading.CancellationToken)"/>
/// <see cref="RemoteServiceConnection.RunRemoteAsync{T}(string, Solution, System.Collections.Generic.IReadOnlyList{object}, System.Threading.CancellationToken)"/>
/// the type of <param name="underlyingObject"/> has to be object. Its runtime type is <see cref="JObject"/>.
/// </summary>
public UnitTestingPinnedSolutionInfoWrapper(object underlyingObject)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册