From c1e8715cf86ddcfb86f7daefb79a6c1b496de5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Sat, 30 May 2020 15:15:16 -0700 Subject: [PATCH] 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 --- .../SymbolSearchUpdateEngineFactory.cs | 11 +- ...ctFindUsagesService_FindImplementations.cs | 15 +- ...bstractFindUsagesService_FindReferences.cs | 15 +- .../Remote/InProcRemostHostClient.cs | 15 +- .../Roslyn.Services.Test.Utilities.csproj | 3 + .../AbstractAddImportFeatureService.cs | 7 +- .../ExtensionMethodImportCompletionHelper.cs | 7 +- ...ertTupleToStructCodeRefactoringProvider.cs | 15 +- ...alyzer.InProcOrRemoteHostAnalyzerRunner.cs | 6 +- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 2 +- .../AbstractDocumentHighlightsService.cs | 7 +- .../AbstractEncapsulateFieldService.cs | 9 +- .../AbstractNavigateToSearchService.cs | 14 +- .../Razor/RazorRemoteHostClient.cs | 4 +- .../CodeLens/CodeLensCallbackListener.cs | 11 +- .../RemoteCodeLensReferencesService.cs | 28 +-- .../VisualStudioDesignerAttributeService.cs | 10 +- .../VisualStudioProjectTelemetryService.cs | 10 +- ...GlobalNotificationRemoteDeliveryService.cs | 4 +- .../Remote/IPooledConnectionReclamation.cs | 16 ++ .../Remote/JsonRpcConnection.cs | 116 +++++++++-- ...ceHubRemoteHostClient.ConnectionManager.cs | 101 ---------- ...viceHubRemoteHostClient.ConnectionPools.cs | 115 +++++++++++ ...iceHubRemoteHostClient.PooledConnection.cs | 48 ----- .../Remote/ServiceHubRemoteHostClient.cs | 44 +++-- .../Remote/SolutionChecksumUpdater.cs | 4 +- .../VisualStudioTodoCommentsService.cs | 8 +- .../Utilities/IVsShellExtensions.cs | 4 + .../PackageInstallerServiceFactory.cs | 21 +- .../RemoteHostClientServiceFactoryTests.cs | 8 +- .../Services/LanguageServiceTests.cs | 4 +- .../Services/ServiceHubServicesTests.cs | 14 +- ...alStudioDiagnosticAnalyzerExecutorTests.cs | 25 ++- .../Razor/RazorLanguageServiceClient.cs | 14 +- .../OptionPages/PerformanceLoggersPage.cs | 2 +- .../AbstractClassificationService.cs | 7 +- .../Pythia/Api/PythiaRemoteHostClient.cs | 2 +- .../Api/UnitTestingKeepAliveSessionWrapper.cs | 12 +- .../Api/UnitTestingRemoteHostClientWrapper.cs | 14 +- .../DeclarationFinder_AllDeclarations.cs | 7 +- .../DeclarationFinder_SourceDeclarations.cs | 28 +-- .../DependentTypeFinder_Remote.cs | 7 +- .../SymbolFinder_FindLiteralReferences.cs | 13 +- .../SymbolFinder_FindReferences_Current.cs | 5 +- .../Core/Portable/Remote/KeepAliveSession.cs | 43 ---- .../Core/Portable/Remote/RemoteHostClient.cs | 187 +----------------- .../Remote/RemoteServiceConnection.cs | 25 +++ .../Portable/Remote/SessionWithSolution.cs | 14 +- .../Rename/ConflictEngine/ConflictResolver.cs | 6 +- .../Core/Portable/Rename/RenameLocations.cs | 7 +- .../Core/Portable/Rename/Renamer.cs | 6 +- .../UnitTestingPinnedSolutionInfoWrapper.cs | 2 +- 52 files changed, 462 insertions(+), 660 deletions(-) create mode 100644 src/VisualStudio/Core/Def/Implementation/Remote/IPooledConnectionReclamation.cs delete mode 100644 src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.ConnectionManager.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.ConnectionPools.cs delete mode 100644 src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.PooledConnection.cs delete mode 100644 src/Workspaces/Core/Portable/Remote/KeepAliveSession.cs create mode 100644 src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs diff --git a/src/EditorFeatures/Core.Wpf/SymbolSearch/SymbolSearchUpdateEngineFactory.cs b/src/EditorFeatures/Core.Wpf/SymbolSearch/SymbolSearchUpdateEngineFactory.cs index 332a67dda32..e933f573f15 100644 --- a/src/EditorFeatures/Core.Wpf/SymbolSearch/SymbolSearchUpdateEngineFactory.cs +++ b/src/EditorFeatures/Core.Wpf/SymbolSearch/SymbolSearchUpdateEngineFactory.cs @@ -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; diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindImplementations.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindImplementations.cs index 2f1ca461923..cbe6ed362d6 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindImplementations.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindImplementations.cs @@ -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( diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs index 569545b757b..af22ebae159 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs @@ -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( diff --git a/src/EditorFeatures/TestUtilities/Remote/InProcRemostHostClient.cs b/src/EditorFeatures/TestUtilities/Remote/InProcRemostHostClient.cs index 2d71ccb5abc..3719bb0807b 100644 --- a/src/EditorFeatures/TestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/EditorFeatures/TestUtilities/Remote/InProcRemostHostClient.cs @@ -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 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 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(), data.scopeId, data.checksums, cancellationToken), cancellationToken); /// /// Remote API. /// public Task IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken) - => Task.FromResult(Services.GetRequiredService().IsExperimentEnabled(experimentName)); + => Task.FromResult(_services.GetRequiredService().IsExperimentEnabled(experimentName)); public AssetStorage AssetStorage => _inprocServices.AssetStorage; @@ -95,14 +97,13 @@ public Task RequestServiceAsync(RemoteServiceName serviceName) public override string ClientId { get; } - protected override async Task TryCreateConnectionAsync( - RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken) + public override async Task 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() diff --git a/src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj b/src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj index 8dc550264c9..cfcbdf68f9f 100644 --- a/src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj +++ b/src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj @@ -62,6 +62,9 @@ Remote\JsonRpcConnection.cs + + Remote\IPooledConnectionReclamation.cs + diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index a16368bb9d9..70791bca5c6 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -59,7 +59,7 @@ internal abstract partial class AbstractAddImportFeatureService>( + var result = await client.RunRemoteAsync>( WellKnownServiceHubService.CodeAnalysis, nameof(IRemoteAddImportFeatureService.GetFixesAsync), document.Project.Solution, @@ -76,10 +76,7 @@ internal abstract partial class AbstractAddImportFeatureService, StatisticCounter)> var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false); if (client != null) { - var result = await client.TryRunRemoteAsync<(IList items, StatisticCounter counter)>( + var result = await client.RunRemoteAsync<(IList items, StatisticCounter counter)>( WellKnownServiceHubService.CodeAnalysis, nameof(IRemoteExtensionMethodImportCompletionService.GetUnimportedExtensionMethodsAsync), project.Solution, @@ -52,10 +52,7 @@ async Task<(ImmutableArray, 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); diff --git a/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs index be698b4c6e7..c0c8293521d 100644 --- a/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs @@ -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( + var result = await client.RunRemoteAsync( 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); } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs index 6e0cb7f1d1b..f712be5e954 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs @@ -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.Empty; } private static async Task> ReadCompilerAnalysisResultAsync(Stream stream, Dictionary analyzerMap, Project project, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 475dfe13513..cb6b5f0be52 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -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, diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index 1a7d55cdc08..d40d75bd90f 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -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>( + var result = await client.RunRemoteAsync>( 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( diff --git a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs index 6c332b4ec8d..c20ed3e9ac3 100644 --- a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs +++ b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs @@ -104,7 +104,7 @@ private ImmutableArray 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 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); } } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index a29d1714266..1e375b2f07c 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -38,7 +38,7 @@ internal abstract partial class AbstractNavigateToSearchService : INavigateToSea { var solution = document.Project.Solution; - var result = await client.TryRunRemoteAsync>( + var result = await client.RunRemoteAsync>( 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>( + var result = await client.RunRemoteAsync>( 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( diff --git a/src/Tools/ExternalAccess/Razor/RazorRemoteHostClient.cs b/src/Tools/ExternalAccess/Razor/RazorRemoteHostClient.cs index 3e8f4a5fb82..b1692caa1c8 100644 --- a/src/Tools/ExternalAccess/Razor/RazorRemoteHostClient.cs +++ b/src/Tools/ExternalAccess/Razor/RazorRemoteHostClient.cs @@ -27,7 +27,7 @@ internal RazorRemoteHostClient(RemoteHostClient client) return client == null ? null : new RazorRemoteHostClient(client); } - public Task> TryRunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, CancellationToken cancellationToken) - => _client.TryRunRemoteAsync(WellKnownServiceHubService.Razor, targetName, solution, arguments, callbackTarget: null, cancellationToken); + public async Task> TryRunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, CancellationToken cancellationToken) + => await _client.RunRemoteAsync(WellKnownServiceHubService.Razor, targetName, solution, arguments, callbackTarget: null, cancellationToken).ConfigureAwait(false); } } diff --git a/src/VisualStudio/Core/Def/Implementation/CodeLens/CodeLensCallbackListener.cs b/src/VisualStudio/Core/Def/Implementation/CodeLens/CodeLensCallbackListener.cs index b874194fcd8..82ae4939971 100644 --- a/src/VisualStudio/Core/Def/Implementation/CodeLens/CodeLensCallbackListener.cs +++ b/src/VisualStudio/Core/Def/Implementation/CodeLens/CodeLensCallbackListener.cs @@ -134,9 +134,16 @@ public List 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 GetMaxResultCapAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Implementation/CodeLens/RemoteCodeLensReferencesService.cs b/src/VisualStudio/Core/Def/Implementation/CodeLens/RemoteCodeLensReferencesService.cs index 3f642bc37d5..87558a9ea80 100644 --- a/src/VisualStudio/Core/Def/Implementation/CodeLens/RemoteCodeLensReferencesService.cs +++ b/src/VisualStudio/Core/Def/Implementation/CodeLens/RemoteCodeLensReferencesService.cs @@ -40,18 +40,13 @@ public RemoteCodeLensReferencesService() var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); if (client != null) { - var result = await client.TryRunRemoteAsync( + return await client.RunRemoteAsync( 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>( + return await client.RunRemoteAsync>( 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( + return await client.RunRemoteAsync( 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>( + return await client.RunRemoteAsync>( 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. diff --git a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs index e33e70d70c4..9c29ba92c59 100644 --- a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -48,10 +48,10 @@ internal class VisualStudioDesignerAttributeService private readonly IServiceProvider _serviceProvider; /// - /// 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. /// - private KeepAliveSession? _keepAliveSession; + private RemoteServiceConnection? _connection; /// /// 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(), diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs b/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs index 9c49771e276..3a3a7f7d19c 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs @@ -45,10 +45,10 @@ internal class VisualStudioProjectTelemetryService private readonly VisualStudioWorkspaceImpl _workspace; /// - /// 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. /// - private KeepAliveSession? _keepAliveSession; + private RemoteServiceConnection? _connection; /// /// 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(), diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/GlobalNotificationRemoteDeliveryService.cs b/src/VisualStudio/Core/Def/Implementation/Remote/GlobalNotificationRemoteDeliveryService.cs index 22ffb886598..2a3f596237c 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/GlobalNotificationRemoteDeliveryService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/GlobalNotificationRemoteDeliveryService.cs @@ -95,7 +95,7 @@ private async Task SendStartNotificationAsync(Task SendStoppedNotificationAsync(Task + /// Returns instance to the pool it was allocated from. + /// + void Return(JsonRpcConnection connection); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/JsonRpcConnection.cs b/src/VisualStudio/Core/Def/Implementation/Remote/JsonRpcConnection.cs index bf0453cd3aa..0574bcc275b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/JsonRpcConnection.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/JsonRpcConnection.cs @@ -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(); _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 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 InvokeAsync(string targetName, IReadOnlyList arguments, CancellationToken cancellationToken) - => _serviceEndPoint.InvokeAsync(targetName, arguments, cancellationToken); + var poolReclamation = Interlocked.Exchange(ref _poolReclamation, null); + if (poolReclamation != null) + { + poolReclamation.Return(this); + return; + } - public override Task InvokeAsync(string targetName, IReadOnlyList arguments, Func> 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 arguments, CancellationToken cancellationToken) + { + if (solution != null) + { + using var scope = await _remotableDataService.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder.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 RunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, Func>? dataReader, CancellationToken cancellationToken) + { + if (solution != null) + { + using var scope = await _remotableDataService.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder.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(targetName, argumentsBuilder, cancellationToken).ConfigureAwait(false); + } + } + else if (dataReader != null) + { + return await _serviceEndPoint.InvokeAsync(targetName, arguments, dataReader, cancellationToken).ConfigureAwait(false); + } + else + { + return await _serviceEndPoint.InvokeAsync(targetName, arguments, cancellationToken).ConfigureAwait(false); + } } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.ConnectionManager.cs b/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.ConnectionManager.cs deleted file mode 100644 index d045050dd57..00000000000 --- a/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.ConnectionManager.cs +++ /dev/null @@ -1,101 +0,0 @@ -// 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.Concurrent; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Remote; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Remote -{ - internal sealed partial class ServiceHubRemoteHostClient - { - private delegate Task ConnectionFactory(RemoteServiceName serviceName, CancellationToken cancellationToken); - - private sealed partial class ConnectionPool : IDisposable - { - private readonly ConnectionFactory _connectionFactory; - private readonly ReaderWriterLockSlim _shutdownLock; - private readonly int _capacity; - - // keyed to serviceName. each connection is for specific service such as CodeAnalysisService - private readonly ConcurrentDictionary> _pools; - - private bool _isDisposed; - - public ConnectionPool(ConnectionFactory connectionFactory, int capacity) - { - _connectionFactory = connectionFactory; - _capacity = 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>(concurrencyLevel: 4, capacity: 4); - - _shutdownLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - } - - public async Task GetOrCreateConnectionAsync(RemoteServiceName serviceName, CancellationToken cancellationToken) - { - var queue = _pools.GetOrAdd(serviceName, _ => new ConcurrentQueue()); - if (queue.TryDequeue(out var connection)) - { - return new PooledConnection(this, serviceName, connection); - } - - var newConnection = await _connectionFactory(serviceName, cancellationToken).ConfigureAwait(false); - return new PooledConnection(this, serviceName, newConnection); - } - - private void Free(RemoteServiceName serviceName, Connection connection) - { - using (_shutdownLock.DisposableRead()) - { - if (_isDisposed) - { - // pool is not being used or - // manager is already shutdown - connection.Dispose(); - return; - } - - // queue must exist - var queue = _pools[serviceName]; - if (queue.Count >= _capacity) - { - // let the connection actually go away - connection.Dispose(); - return; - } - - // pool the connection - queue.Enqueue(connection); - } - } - - public void Dispose() - { - using (_shutdownLock.DisposableWrite()) - { - _isDisposed = true; - - // let all connections in the pool to go away - foreach (var (_, queue) in _pools) - { - while (queue.TryDequeue(out var connection)) - { - connection.Dispose(); - } - } - - _pools.Clear(); - } - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.ConnectionPools.cs b/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.ConnectionPools.cs new file mode 100644 index 00000000000..5a768d8224a --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.ConnectionPools.cs @@ -0,0 +1,115 @@ +// 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.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Remote; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Remote +{ + internal partial class ServiceHubRemoteHostClient + { + public delegate Task ConnectionFactory(RemoteServiceName serviceName, IPooledConnectionReclamation poolReclamation, CancellationToken cancellationToken); + + internal sealed class ConnectionPools : IDisposable + { + private sealed class Pool : IPooledConnectionReclamation + { + private readonly ConcurrentQueue _queue; + private readonly ConnectionPools _owner; + + public Pool(ConnectionPools connectionPools) + { + _queue = new ConcurrentQueue(); + _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 _capacityPerService; + + // keyed to serviceName. each connection is for specific service such as CodeAnalysisService + private readonly ConcurrentDictionary _pools; + + private bool _isDisposed; + + public ConnectionPools(ConnectionFactory connectionFactory, int capacity) + { + _connectionFactory = connectionFactory; + _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(concurrencyLevel: 4, capacity: 4); + + _shutdownLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + } + + public async Task GetOrCreateConnectionAsync(RemoteServiceName serviceName, CancellationToken cancellationToken) + { + var pool = _pools.GetOrAdd(serviceName, _ => new Pool(this)); + if (pool.TryAcquire(out var connection)) + { + return connection; + } + + return await _connectionFactory(serviceName, pool, cancellationToken).ConfigureAwait(false); + } + + internal void Free(ConcurrentQueue pool, JsonRpcConnection connection) + { + using (_shutdownLock.DisposableRead()) + { + // 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) + { + connection.Dispose(); + } + else + { + pool.Enqueue(connection); + } + } + } + + public void Dispose() + { + using (_shutdownLock.DisposableWrite()) + { + _isDisposed = true; + + foreach (var (_, pool) in _pools) + { + pool.DisposeConnections(); + } + + _pools.Clear(); + } + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.PooledConnection.cs b/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.PooledConnection.cs deleted file mode 100644 index abd039c8f04..00000000000 --- a/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.PooledConnection.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 arguments, CancellationToken cancellationToken) - => _connection.InvokeAsync(targetName, arguments, cancellationToken); - - public override Task InvokeAsync(string targetName, IReadOnlyList arguments, CancellationToken cancellationToken) - => _connection.InvokeAsync(targetName, arguments, cancellationToken); - - public override Task InvokeAsync(string targetName, IReadOnlyList arguments, Func> dataReader, CancellationToken cancellationToken) - => _connection.InvokeAsync(targetName, arguments, dataReader, cancellationToken); - - protected override void DisposeImpl() - { - _pool.Free(_serviceName, _connection); - base.DisposeImpl(); - } - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.cs b/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.cs index 5b413e66549..7f3bf2caa73 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/ServiceHubRemoteHostClient.cs @@ -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(); } private void OnUnexpectedExceptionThrown(Exception unexpectedException) - => RemoteHostCrashInfoBar.ShowInfoBar(Services, unexpectedException); + => RemoteHostCrashInfoBar.ShowInfoBar(_services, unexpectedException); public static async Task CreateAsync(HostWorkspaceServices services, CancellationToken cancellationToken) { @@ -67,7 +70,7 @@ public static async Task 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 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 TryCreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken) + public override Task 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 CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken) + private async Task 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 IsExperimentEnabledAsync(string experimentName, CancellationTo { try { - return Task.FromResult(Services.GetRequiredService().IsExperimentEnabled(experimentName)); + return Task.FromResult(_services.GetRequiredService().IsExperimentEnabled(experimentName)); } catch (Exception ex) when (FatalError.ReportWithoutCrashUnlessCanceledAndPropagate(ex, cancellationToken)) { diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/SolutionChecksumUpdater.cs b/src/VisualStudio/Core/Def/Implementation/Remote/SolutionChecksumUpdater.cs index 62b833890d9..64ced3e4c95 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/SolutionChecksumUpdater.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/SolutionChecksumUpdater.cs @@ -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, diff --git a/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs b/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs index fff8655acd5..e83f57821ea 100644 --- a/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs +++ b/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs @@ -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. /// - private KeepAliveSession? _keepAliveSession; + private RemoteServiceConnection? _connection; /// /// 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(), diff --git a/src/VisualStudio/Core/Def/Implementation/Utilities/IVsShellExtensions.cs b/src/VisualStudio/Core/Def/Implementation/Utilities/IVsShellExtensions.cs index 5082d3479d7..b61f6bf4f57 100644 --- a/src/VisualStudio/Core/Def/Implementation/Utilities/IVsShellExtensions.cs +++ b/src/VisualStudio/Core/Def/Implementation/Utilities/IVsShellExtensions.cs @@ -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; + /// + /// Returns true if devenv is invoked in command line mode for build, e.g. devenv /rebuild MySolution.sln + /// public static bool IsInCommandLineMode { get diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index f059df84028..20224dfd475 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -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 diff --git a/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs b/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs index 06bb386b925..1ddf2e69ca3 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs @@ -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(); diff --git a/src/VisualStudio/Core/Test.Next/Services/LanguageServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/LanguageServiceTests.cs index 82e658ab7bd..24d52f56d74 100644 --- a/src/VisualStudio/Core/Test.Next/Services/LanguageServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/LanguageServiceTests.cs @@ -117,13 +117,13 @@ private async Task> 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); } } } diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 3c7bc0b93ec..efe824e8037 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -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(), @@ -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(), @@ -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) diff --git a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs index ce004ef77fa..76bfbf33fee 100644 --- a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs @@ -149,7 +149,7 @@ public async Task TestCancellationOnSessionWithSolution() var service = solution.Workspace.Services.GetService(); var source = new CancellationTokenSource(); - using var session = new KeepAliveSession(new InvokeThrowsCancellationConnection(source), service); + using var session = new InvokeThrowsCancellationConnection(source); var exception = await Assert.ThrowsAnyAsync(() => 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 arguments, CancellationToken cancellationToken) + public override void Dispose() + { + } + + public override Task RunRemoteAsync(string targetName, Solution solution, IReadOnlyList arguments, CancellationToken cancellationToken) { // cancel and throw cancellation exception _source.Cancel(); _source.Token.ThrowIfCancellationRequested(); - throw Utilities.ExceptionUtilities.Unreachable; + throw ExceptionUtilities.Unreachable; } - public override Task InvokeAsync( - string targetName, IReadOnlyList arguments, CancellationToken cancellationToken) - => throw new NotImplementedException(); + public override Task RunRemoteAsync(string targetName, Solution solution, IReadOnlyList arguments, Func> dataReader, CancellationToken cancellationToken) + { + // cancel and throw cancellation exception + _source.Cancel(); + _source.Token.ThrowIfCancellationRequested(); - public override Task InvokeAsync( - string targetName, IReadOnlyList arguments, Func> dataReader, CancellationToken cancellationToken) - => throw new NotImplementedException(); + throw ExceptionUtilities.Unreachable; + } } } } diff --git a/src/VisualStudio/Razor/RazorLanguageServiceClient.cs b/src/VisualStudio/Razor/RazorLanguageServiceClient.cs index 18f13426925..41bf7047d25 100644 --- a/src/VisualStudio/Razor/RazorLanguageServiceClient.cs +++ b/src/VisualStudio/Razor/RazorLanguageServiceClient.cs @@ -27,8 +27,8 @@ internal RazorLanguageServiceClient(RemoteHostClient client, string serviceName) _serviceName = new RemoteServiceName(serviceName); } - public Task> TryRunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, object? callbackTarget, CancellationToken cancellationToken) - => _client.TryRunRemoteAsync(_serviceName, targetName, solution, arguments, callbackTarget, cancellationToken); + public async Task> TryRunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, object? callbackTarget, CancellationToken cancellationToken) + => await _client.RunRemoteAsync(_serviceName, targetName, solution, arguments, callbackTarget, cancellationToken).ConfigureAwait(false); [Obsolete("Use TryRunRemoteAsync instead")] public async Task CreateSessionAsync(Solution solution, object? callbackTarget = null, CancellationToken cancellationToken = default) @@ -39,23 +39,19 @@ public Task> TryRunRemoteAsync(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(); } } diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs index 04cc71a6329..e2ce11724b7 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs @@ -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, diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index 0a92938fd1a..838df901664 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -63,7 +63,7 @@ private static async Task TryAddSemanticClassificationsInRemoteProcessAsyn if (client == null) return false; - var classifiedSpans = await client.TryRunRemoteAsync( + var classifiedSpans = await client.RunRemoteAsync( WellKnownServiceHubService.CodeAnalysis, nameof(IRemoteSemanticClassificationService.GetSemanticClassificationsAsync), document.Project.Solution, @@ -71,10 +71,7 @@ private static async Task TryAddSemanticClassificationsInRemoteProcessAsyn callbackTarget: null, cancellationToken).ConfigureAwait(false); - if (!classifiedSpans.HasValue) - return false; - - classifiedSpans.Value.Rehydrate(result); + classifiedSpans.Rehydrate(result); return true; } diff --git a/src/Workspaces/Core/Portable/ExternalAccess/Pythia/Api/PythiaRemoteHostClient.cs b/src/Workspaces/Core/Portable/ExternalAccess/Pythia/Api/PythiaRemoteHostClient.cs index 9cb44e32789..0fadd704e75 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/Pythia/Api/PythiaRemoteHostClient.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/Pythia/Api/PythiaRemoteHostClient.cs @@ -28,7 +28,7 @@ public static async Task> TryRunRemoteAsync(Workspace workspace, return default; } - return await client.TryRunRemoteAsync(WellKnownServiceHubService.IntelliCode, targetName, solution, arguments, callbackTarget: null, cancellationToken).ConfigureAwait(false); + return await client.RunRemoteAsync(WellKnownServiceHubService.IntelliCode, targetName, solution, arguments, callbackTarget: null, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingKeepAliveSessionWrapper.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingKeepAliveSessionWrapper.cs index b20c735ead3..b120c1073a6 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingKeepAliveSessionWrapper.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingKeepAliveSessionWrapper.cs @@ -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 TryInvokeAsync(string targetName, Solution solution, IReadOnlyList arguments, CancellationToken cancellationToken) - => UnderlyingObject!.RunRemoteAsync(targetName, solution, arguments, cancellationToken); + => UnderlyingObject.RunRemoteAsync(targetName, solution, arguments, cancellationToken); public async Task TryInvokeAsync(string targetName, IReadOnlyList 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 TryInvokeAsync(string targetName, Solution solution, IReadOnlyList 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 TryInvokeAsync(string targetName, IReadOnlyList arguments, CancellationToken cancellationToken) - => UnderlyingObject!.RunRemoteAsync(targetName, solution: null, arguments, cancellationToken); + => UnderlyingObject.RunRemoteAsync(targetName, solution: null, arguments, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClientWrapper.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClientWrapper.cs index 522f50e24ac..8308ff5f0d0 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClientWrapper.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClientWrapper.cs @@ -21,29 +21,25 @@ internal UnitTestingRemoteHostClientWrapper(RemoteHostClient underlyingObject) public async Task 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 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(); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs index 5045169422a..2226c881f90 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs @@ -41,7 +41,7 @@ internal static partial class DeclarationFinder { var solution = project.Solution; - var result = await client.TryRunRemoteAsync>( + var result = await client.RunRemoteAsync>( 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( diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs index bb44fc8c882..06221d5689b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs @@ -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>( + var result = await client.RunRemoteAsync>( 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>( + var result = await client.RunRemoteAsync>( 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>( + var result = await client.RunRemoteAsync>( 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>( + var result = await client.RunRemoteAsync>( 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( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_Remote.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_Remote.cs index b840e0c6862..516b9899a34 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_Remote.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_Remote.cs @@ -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>( + var result = await client.RunRemoteAsync>( 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); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs index 5bddaff7eb7..e7b0da75121 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs @@ -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); + } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs index 667f912bd40..397e278440e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs @@ -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; } } diff --git a/src/Workspaces/Core/Portable/Remote/KeepAliveSession.cs b/src/Workspaces/Core/Portable/Remote/KeepAliveSession.cs deleted file mode 100644 index 4c78f7c86ed..00000000000 --- a/src/Workspaces/Core/Portable/Remote/KeepAliveSession.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 -{ - /// - /// This will let one to hold onto 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) - /// - 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 arguments, CancellationToken cancellationToken) - => RemoteHostClient.RunRemoteAsync(_connection, _remotableDataService, targetName, solution, arguments, cancellationToken); - - public Task RunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, CancellationToken cancellationToken) - => RemoteHostClient.RunRemoteAsync(_connection, _remotableDataService, targetName, solution, arguments, dataReader: null, cancellationToken); - } -} diff --git a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs index f6f8a267e79..25f51244ad3 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs @@ -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 { /// @@ -27,17 +21,8 @@ namespace Microsoft.CodeAnalysis.Remote /// internal abstract class RemoteHostClient : IDisposable { - public readonly HostWorkspaceServices Services; public event EventHandler? StatusChanged; - internal readonly IRemotableDataService RemotableDataService; - - protected RemoteHostClient(HostWorkspaceServices services) - { - Services = services; - RemotableDataService = services.GetRequiredService(); - } - /// /// Return an unique string per client. /// @@ -46,15 +31,6 @@ protected RemoteHostClient(HostWorkspaceServices services) /// public abstract string ClientId { get; } - /// - /// Create for the if possible. - /// otherwise, return null. - /// - /// Creating session could fail if remote host is not available. one of example will be user killing - /// remote host. - /// - protected abstract Task 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 TryGetClientAsync(Project project, CancellationToken cancellationToken) { if (!RemoteSupportedLanguages.IsSupported(project.Language)) @@ -93,163 +66,21 @@ public static string CreateClientId(string prefix) return service.TryGetRemoteHostClientAsync(cancellationToken); } - /// - /// Creates for the , otherwise returns . - /// - public async Task 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 TryRunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList 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> TryRunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList arguments, object? callbackTarget, CancellationToken cancellationToken) - => TryRunRemoteAsync(serviceName, targetName, solution, arguments, callbackTarget, dataReader: null, cancellationToken); - - public async Task> TryRunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList arguments, object? callbackTarget, Func>? dataReader, CancellationToken cancellationToken) - { - using var connection = await TryCreateConnectionAsync(serviceName, callbackTarget, cancellationToken).ConfigureAwait(false); - if (connection == null) - { - return default; - } - - return await RunRemoteAsync(connection, RemotableDataService, targetName, solution, arguments, dataReader, cancellationToken).ConfigureAwait(false); - } - - internal static async Task RunRemoteAsync(Connection connection, IRemotableDataService remoteDataService, string targetName, Solution? solution, IReadOnlyList arguments, CancellationToken cancellationToken) - { - if (solution != null) - { - using var scope = await remoteDataService.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.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 CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken); - internal static async Task RunRemoteAsync(Connection connection, IRemotableDataService remoteDataService, string targetName, Solution? solution, IReadOnlyList arguments, Func>? dataReader, CancellationToken cancellationToken) + public async Task RunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList arguments, object? callbackTarget, CancellationToken cancellationToken) { - if (solution != null) - { - using var scope = await remoteDataService.CreatePinnedRemotableDataScopeAsync(solution, cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.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(targetName, argumentsBuilder, cancellationToken).ConfigureAwait(false); - } - } - else if (dataReader != null) - { - return await connection.InvokeAsync(targetName, arguments, dataReader, cancellationToken).ConfigureAwait(false); - } - else - { - return await connection.InvokeAsync(targetName, arguments, cancellationToken).ConfigureAwait(false); - } + using var connection = await CreateConnectionAsync(serviceName, callbackTarget, cancellationToken).ConfigureAwait(false); + await connection.RunRemoteAsync(targetName, solution, arguments, cancellationToken).ConfigureAwait(false); } - /// - /// NoOpClient is used if a user killed our remote host process. Basically this client never - /// create a session - /// - public class NoOpClient : RemoteHostClient - { - public NoOpClient(HostWorkspaceServices services) - : base(services) - { - } + public Task RunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList arguments, object? callbackTarget, CancellationToken cancellationToken) + => RunRemoteAsync(serviceName, targetName, solution, arguments, callbackTarget, dataReader: null, cancellationToken); - public override string ClientId => nameof(NoOpClient); - - protected override Task TryCreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken) - => SpecializedTasks.Null(); - } - - /// - /// 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. - /// - public abstract class Connection : IDisposable + public async Task RunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList arguments, object? callbackTarget, Func>? dataReader, CancellationToken cancellationToken) { - private bool _disposed; - - protected Connection() - { -#if DEBUG - _creationCallStack = Environment.StackTrace; -#endif - _disposed = false; - } - - public abstract Task InvokeAsync(string targetName, IReadOnlyList arguments, CancellationToken cancellationToken); - public abstract Task InvokeAsync(string targetName, IReadOnlyList arguments, CancellationToken cancellationToken); - public abstract Task InvokeAsync(string targetName, IReadOnlyList arguments, Func> 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); } } } diff --git a/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs b/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs new file mode 100644 index 00000000000..6517bad1bd7 --- /dev/null +++ b/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs @@ -0,0 +1,25 @@ +// 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 arguments, CancellationToken cancellationToken); + public abstract Task RunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, Func>? dataReader, CancellationToken cancellationToken); + + public Task RunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, CancellationToken cancellationToken) + => RunRemoteAsync(targetName, solution, arguments, dataReader: null, cancellationToken); + } +} diff --git a/src/Workspaces/Core/Portable/Remote/SessionWithSolution.cs b/src/Workspaces/Core/Portable/Remote/SessionWithSolution.cs index af967ee5440..71c3d6efc03 100644 --- a/src/Workspaces/Core/Portable/Remote/SessionWithSolution.cs +++ b/src/Workspaces/Core/Portable/Remote/SessionWithSolution.cs @@ -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 CreateAsync(KeepAliveSession keepAliveSession, Solution solution, CancellationToken cancellationToken) + public static async Task CreateAsync(RemoteServiceConnection connection, Solution solution, CancellationToken cancellationToken) { - Contract.ThrowIfNull(keepAliveSession); + Contract.ThrowIfNull(connection); Contract.ThrowIfNull(solution); var service = solution.Workspace.Services.GetRequiredService(); @@ -31,14 +31,14 @@ public static async Task 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 CreateAsync(KeepAliveSession keepA return session; } - private SessionWithSolution(KeepAliveSession keepAliveSession, PinnedRemotableDataScope scope) + private SessionWithSolution(RemoteServiceConnection connection, PinnedRemotableDataScope scope) { - KeepAliveSession = keepAliveSession; + KeepAliveSession = connection; _scope = scope; } diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs index bdd7f188b2c..be0f2f98c46 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs @@ -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( + var result = await client.RunRemoteAsync( 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); } } diff --git a/src/Workspaces/Core/Portable/Rename/RenameLocations.cs b/src/Workspaces/Core/Portable/Rename/RenameLocations.cs index 4c5b92a7b3c..daf3d742499 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameLocations.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameLocations.cs @@ -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( + var result = await client.RunRemoteAsync( 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; } diff --git a/src/Workspaces/Core/Portable/Rename/Renamer.cs b/src/Workspaces/Core/Portable/Rename/Renamer.cs index c1b9939d189..9af151b77fc 100644 --- a/src/Workspaces/Core/Portable/Rename/Renamer.cs +++ b/src/Workspaces/Core/Portable/Rename/Renamer.cs @@ -127,7 +127,7 @@ internal static Task FindRenameLocationsAsync(Solution solution var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); if (client != null) { - var result = await client.TryRunRemoteAsync( + var result = await client.RunRemoteAsync( WellKnownServiceHubService.CodeAnalysis, nameof(IRemoteRenamer.RenameSymbolAsync), solution, @@ -141,8 +141,8 @@ internal static Task 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); } } } diff --git a/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingPinnedSolutionInfoWrapper.cs b/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingPinnedSolutionInfoWrapper.cs index 7a40444bbe8..9618ab58b88 100644 --- a/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingPinnedSolutionInfoWrapper.cs +++ b/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingPinnedSolutionInfoWrapper.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api { /// /// Because PinnedSolutionInfo is internal, and it is directly passed into the callee in - /// + /// /// the type of has to be object. Its runtime type is . /// public UnitTestingPinnedSolutionInfoWrapper(object underlyingObject) -- GitLab