From e53ffb731150ce8af1b832a9c04381b990012000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Fri, 11 Sep 2020 11:29:58 -0700 Subject: [PATCH] Brokered services (#47216) * Brokered service * TodoCommentService ISB * Generate PkgDef entries * Generate VSIX manifest assets * Brokered services * Error reporting * BrokeredServiceConnection * Enable cancellation on disconnection * Error handling * Remove usage of WellKnownServiceHubService for brokered services * Semantic classification services * Fix streaming service calls * Propagate cancellation token to all services * Misc * Fixes * Feedback * Comments * Dispose feedback * RPC fixes * Rename brokered services * Prefix services with Microsoft.VisualStudio * Fix * Fix cancelation of OOP analyzer requests --- eng/Versions.props | 11 +- eng/targets/GeneratePkgDef.targets | 39 +++- ...nerateServiceHubConfigurationFiles.targets | 30 +-- eng/targets/Services.props | 32 ++++ .../Roslyn.Services.Test.Utilities.csproj | 1 - ...actDesignerAttributeIncrementalAnalyzer.cs | 6 +- ...alyzer.InProcOrRemoteHostAnalyzerRunner.cs | 23 ++- .../IRemoteDiagnosticAnalyzerService.cs | 6 +- ...AbstractTodoCommentsIncrementalAnalyzer.cs | 4 +- ...oslyn.VisualStudio.Setup.ServiceHub.csproj | 3 +- ...rocDesignerAttributeIncrementalAnalyzer.cs | 4 +- .../VisualStudioDesignerAttributeService.cs | 28 +-- .../LanguageClient/LanguageServerClient.cs | 2 - .../VisualStudioProjectTelemetryService.cs | 24 +-- .../VisualStudioRemoteHostClientProvider.cs | 42 ++++- ...icationCacheIncrementalAnalyzerProvider.cs | 6 +- ...tudioSemanticClassificationCacheService.cs | 12 +- .../InProcTodoCommentsIncrementalAnalyzer.cs | 2 +- .../VisualStudioTodoCommentsService.cs | 37 ++-- ...osoft.VisualStudio.LanguageServices.csproj | 17 +- src/VisualStudio/Core/Def/RoslynPackage.cs | 2 + .../VisualStudioWorkspaceTelemetryService.cs | 3 +- .../Core/Test.Next/Mocks/SimpleAssetSource.cs | 8 +- .../RemoteHostClientServiceFactoryTests.cs | 2 +- .../Services/ServiceHubServicesTests.cs | 42 ++--- .../Setup/Directory.Build.targets | 45 +++++ .../Setup/Roslyn.VisualStudio.Setup.csproj | 1 + .../Setup/source.extension.vsixmanifest | 18 +- .../AbstractClassificationService.cs | 49 +++-- ...emoteSemanticClassificationCacheService.cs | 4 +- .../IRemoteSemanticClassificationService.cs | 2 +- .../IDesignerAttributeListener.cs | 4 +- .../IRemoteDesignerAttributeService.cs | 2 +- .../IProjectTelemetryListener.cs | 2 +- .../IRemoteProjectTelemetryService.cs | 2 +- .../Core/Portable/Remote/RemoteHostClient.cs | 72 +++++++- .../Remote/RemoteServiceConnection.cs | 42 +++++ .../Core/Portable/Remote/RemoteServiceName.cs | 9 +- .../Remote/WellKnownServiceHubService.cs | 26 +-- .../IRemoteTodoCommentsService.cs | 2 +- .../TodoComments/ITodoCommentsListener.cs | 2 +- .../Remote/InProcRemostHostClient.cs | 173 ++++++++++++++---- ...Roslyn.Services.UnitTests.Utilities.csproj | 1 - .../Remote/Core/BrokeredServiceConnection.cs | 161 ++++++++++++++++ .../Remote/Core/IRemoteHostService.cs | 2 +- .../Remote/Core/ISolutionAssetProvider.cs | 27 +++ src/Workspaces/Remote/Core/RemoteCallback.cs | 126 +++++++++++++ src/Workspaces/Remote/Core/RemoteEndPoint.cs | 5 +- .../Core/RemoteHostAssetSerialization.cs | 4 +- .../Remote/Core/ServiceDescriptor.cs | 71 +++++++ .../Remote/Core/ServiceDescriptors.cs | 69 +++++++ .../Remote/Core/ServiceHubRemoteHostClient.cs | 61 ++++-- .../Remote/Core/SolutionAssetProvider.cs | 51 ++++++ .../Remote/ServiceHub/Host/IAssetSource.cs | 4 +- .../ServiceHub/Host/RemoteWorkspaceManager.cs | 1 - .../ServiceHub/Host/SolutionAssetSource.cs | 50 +++++ .../BrokeredServiceBase.FactoryBase.cs | 101 ++++++++++ ...erviceBase.ServiceConstructionArguments.cs | 29 +++ .../Services/BrokeredServiceBase.cs | 110 +++++++++++ .../CodeAnalysis/CodeAnalysisService.cs | 7 +- ...CodeAnalysisService_GlobalNotifications.cs | 2 +- ...oteDesignerAttributeIncrementalAnalyzer.cs | 31 ++-- ...nerAttributeIncrementalAnalyzerProvider.cs | 9 +- .../RemoteDesignerAttributeService.cs | 29 +-- .../RemoteDiagnosticAnalyzerService.cs} | 49 +++-- .../RemoteHostService.PerformanceReporter.cs | 2 +- .../Services/Host/RemoteHostService.cs | 18 +- ...moteProjectTelemetryIncrementalAnalyzer.cs | 17 +- ...ectTelemetryIncrementalAnalyzerProvider.cs | 9 +- .../RemoteProjectTelemetryService.cs | 29 +-- .../RemoteSemanticClassificationService.cs} | 19 +- ...moteSemanticClassificationCacheService.cs} | 23 ++- .../RemoteTodoCommentsIncrementalAnalyzer.cs | 18 +- ...TodoCommentsIncrementalAnalyzerProvider.cs | 10 +- .../TodoComments/RemoteTodoCommentsService.cs | 30 +-- 75 files changed, 1613 insertions(+), 403 deletions(-) create mode 100644 eng/targets/Services.props create mode 100644 src/VisualStudio/Setup/Directory.Build.targets create mode 100644 src/Workspaces/Remote/Core/BrokeredServiceConnection.cs create mode 100644 src/Workspaces/Remote/Core/ISolutionAssetProvider.cs create mode 100644 src/Workspaces/Remote/Core/RemoteCallback.cs create mode 100644 src/Workspaces/Remote/Core/ServiceDescriptor.cs create mode 100644 src/Workspaces/Remote/Core/ServiceDescriptors.cs create mode 100644 src/Workspaces/Remote/Core/SolutionAssetProvider.cs create mode 100644 src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs create mode 100644 src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs create mode 100644 src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.ServiceConstructionArguments.cs create mode 100644 src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs rename src/Workspaces/Remote/ServiceHub/Services/{CodeAnalysis/CodeAnalysisService_Diagnostics.cs => DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs} (59%) rename src/Workspaces/Remote/ServiceHub/Services/{CodeAnalysis/CodeAnalysisService_SemanticClassification.cs => SemanticClassification/RemoteSemanticClassificationService.cs} (62%) rename src/Workspaces/Remote/ServiceHub/Services/{CodeAnalysis/CodeAnalysisService_SemanticClassificationCache.cs => SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs} (93%) diff --git a/eng/Versions.props b/eng/Versions.props index 0f7e30681486c..7c8dab81a6585 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -104,8 +104,8 @@ 2.0.0-alpha-20170405-2 0.1.0 0.1.2-dev - 2.4.227 - 2.4.227 + 2.7.69-alpha + 2.7.69-alpha 10.1.0 15.8.27812-alpha 15.8.27812-alpha @@ -141,7 +141,7 @@ 15.8.27812-alpha 16.2.133-pre 2.3.6152103 - 16.3.4 + 16.3.23 16.3.14 1.16.30 16.6.29925.50-pre @@ -156,7 +156,7 @@ 15.7.1 8.0.50728 9.0.30730 - 16.3.36 + 16.3.58 8.0.0.0-alpha $(VisualStudioEditorPackagesVersion) $(VisualStudioEditorPackagesVersion) @@ -180,7 +180,6 @@ 0.1.0 4.4.0 4.10.1 - 1.1.12 4.9.2 4.0.0-rc-2048 4.8.0 @@ -242,7 +241,7 @@ create a test insertion in Visual Studio to validate. --> 12.0.2 - 2.4.34 + 2.6.21-alpha 5.0.0-preview.8.20407.11 5.0.0-preview.8.20407.11 1.1.1 diff --git a/eng/targets/GeneratePkgDef.targets b/eng/targets/GeneratePkgDef.targets index 1b116c0ace1bb..54877ced329be 100644 --- a/eng/targets/GeneratePkgDef.targets +++ b/eng/targets/GeneratePkgDef.targets @@ -28,8 +28,16 @@ - Name - DisplayName (resource id) - ProductDetauls (resource id) + + 3) PkgDefBrokeredService + Emits $RootKey$\BrokeredServices entry. + Entries with ProfferingPackageId correspond to Microsoft.VisualStudio.Shell.ServiceBroker.ProvideBrokeredServiceAttribute. + + Metadata: + - ItemSpec: service name + - ProfferingPackageId (guid, optional): GUID of the proffering package or empty for ServiceHub services - 3) PkgDefBindingRedirect + 4) PkgDefBindingRedirect Emits $RootKey$\RuntimeConfiguration\dependentAssembly\bindingRedirection entry. Project may specify BindingRedirect on any item that contributes to ReferencePath item group. @@ -39,7 +47,7 @@ - ItemSpec: full path to the binary if FusionName is not specified, otherwise it may be just a file name - FusionName: optional assembly name (read from the binary if not specified) - 4) PkgDefCodeBase + 5) PkgDefCodeBase Emits $RootKey$\RuntimeConfiguration\dependentAssembly\codeBase entry. Project may specify CodeBase on any item that contributes to ReferencePath item group. @@ -49,7 +57,7 @@ - ItemSpec: full path to the binary - FusionName: optional assembly name (read from the binary if not specified) - 5) PkgDefFileContent + 6) PkgDefFileContent Merges the content of the file whose path is specified in the identity of the item to the generated pkgdef file. The path may be relative to the project being built. @@ -64,7 +72,7 @@ - + @@ -129,7 +137,7 @@ --> <_AssemblyDependencyEntry Include="@(PkgDefBindingRedirect)" Kind="BindingRedirect" /> @@ -195,6 +203,27 @@ "ProductDetails"="%(PkgDefInstalledProduct.ProductDetails)" "UseInterface"=dword:00000000 "UseVSProductID"=dword:00000000 +]]> + + + + <_PkgDefEntry Include="@(PkgDefBrokeredService)" Condition="'%(PkgDefBrokeredService.ProfferingPackageId)' == ''"> + + + + + + + <_PkgDefEntry Include="@(PkgDefBrokeredService)" Condition="'%(PkgDefBrokeredService.ProfferingPackageId)' != ''"> + + diff --git a/eng/targets/GenerateServiceHubConfigurationFiles.targets b/eng/targets/GenerateServiceHubConfigurationFiles.targets index 9d607578c86f0..965894d8eca0b 100644 --- a/eng/targets/GenerateServiceHubConfigurationFiles.targets +++ b/eng/targets/GenerateServiceHubConfigurationFiles.targets @@ -1,19 +1,9 @@  - - - - - - - - - - + + 6cf2e545-6109-4730-8883-cf43d7aec3e1 + $(GetVsixSourceItemsDependsOn);GenerateServiceHubConfigurationFiles @@ -24,7 +14,7 @@ <_ServicesWithBitness Include="@(ServiceHubService)" FileSuffix="" HostSuffix=".x86" HostIdSuffix="32" /> <_ServicesWithBitness Include="@(ServiceHubService)" FileSuffix="64" HostSuffix="" HostIdSuffix="" /> - <_JsonFile Include="$(IntermediateOutputPath)%(_ServicesWithBitness.FileName)%(_ServicesWithBitness.FileSuffix).servicehub.service.json"> + - + + Inputs="$(MSBuildAllProjects)" + Outputs="@(ServiceHubServiceJsonFile)"> - + - - + + diff --git a/eng/targets/Services.props b/eng/targets/Services.props new file mode 100644 index 0000000000000..0c216727f9b27 --- /dev/null +++ b/eng/targets/Services.props @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj b/src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj index a6529d2d510e8..d8f6de49c3ae1 100644 --- a/src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj +++ b/src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj @@ -36,7 +36,6 @@ - diff --git a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs index 42ba15597de0a..1e375c4c10883 100644 --- a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs @@ -31,12 +31,12 @@ protected AbstractDesignerAttributeIncrementalAnalyzer(Workspace workspace) _storageService = workspace.Services.GetRequiredService(); } - protected abstract Task ReportProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken); + protected abstract ValueTask ReportProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken); - protected abstract Task ReportDesignerAttributeDataAsync(List data, CancellationToken cancellationToken); + protected abstract ValueTask ReportDesignerAttributeDataAsync(List data, CancellationToken cancellationToken); public override Task RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken) - => ReportProjectRemovedAsync(projectId, cancellationToken); + => ReportProjectRemovedAsync(projectId, cancellationToken).AsTask(); public override Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken) => AnalyzeProjectAsync(project, specificDocument: null, cancellationToken); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs index e48eafa0dd5f7..bc730756238d6 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs @@ -140,11 +140,10 @@ private async Task FireAndForgetReportAnalyzerPerformanceAsync( // +1 for project itself var count = documentAnalysisScope != null ? 1 : project.DocumentIds.Count + 1; - await client.RunRemoteAsync( - WellKnownServiceHubService.CodeAnalysis, - nameof(IRemoteDiagnosticAnalyzerService.ReportAnalyzerPerformance), - solution: null, - new object[] { analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(AnalyzerInfoCache), count }, + var performanceInfo = analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(AnalyzerInfoCache).ToImmutableArray(); + + _ = await client.TryInvokeAsync( + (service, cancellationToken) => service.ReportAnalyzerPerformanceAsync(performanceInfo, count, cancellationToken), callbackTarget: null, cancellationToken).ConfigureAwait(false); } @@ -193,17 +192,17 @@ private async Task>( solution, - new object[] { argument }, + invocation: (service, solutionInfo, stream, cancellationToken) => service.CalculateDiagnosticsAsync(solutionInfo, argument, stream, cancellationToken), + reader: (stream, cancellationToken) => ReadCompilerAnalysisResultAsync(stream, analyzerMap, documentAnalysisScope, project, cancellationToken), callbackTarget: null, - (s, c) => ReadCompilerAnalysisResultAsync(s, analyzerMap, documentAnalysisScope, project, c), cancellationToken).ConfigureAwait(false); + + return result.HasValue ? result.Value : DiagnosticAnalysisResultMap.Empty; } - private static async Task> ReadCompilerAnalysisResultAsync( + private static async ValueTask> ReadCompilerAnalysisResultAsync( Stream stream, Dictionary analyzerMap, DocumentAnalysisScope? documentAnalysisScope, @@ -213,7 +212,7 @@ private static async Task snapshot, int unitCount, CancellationToken cancellationToken); + ValueTask CalculateDiagnosticsAsync(PinnedSolutionInfo solutionInfo, DiagnosticArguments arguments, Stream outputStream, CancellationToken cancellationToken); + ValueTask ReportAnalyzerPerformanceAsync(ImmutableArray snapshot, int unitCount, CancellationToken cancellationToken); } internal readonly struct AnalyzerPerformanceInfo diff --git a/src/Features/Core/Portable/TodoComments/AbstractTodoCommentsIncrementalAnalyzer.cs b/src/Features/Core/Portable/TodoComments/AbstractTodoCommentsIncrementalAnalyzer.cs index 232374ff036e7..83f521377233b 100644 --- a/src/Features/Core/Portable/TodoComments/AbstractTodoCommentsIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/TodoComments/AbstractTodoCommentsIncrementalAnalyzer.cs @@ -25,7 +25,7 @@ protected AbstractTodoCommentsIncrementalAnalyzer() { } - protected abstract Task ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken); + protected abstract ValueTask ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken); public override bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e) => e.Option == TodoCommentOptions.TokenList; @@ -33,7 +33,7 @@ public override bool NeedsReanalysisOnOptionChanged(object sender, OptionChanged public override Task RemoveDocumentAsync(DocumentId documentId, CancellationToken cancellationToken) { // Just report this back as there being no more comments for this document. - return ReportTodoCommentDataAsync(documentId, ImmutableArray.Empty, cancellationToken); + return ReportTodoCommentDataAsync(documentId, ImmutableArray.Empty, cancellationToken).AsTask(); } private ImmutableArray GetTodoCommentDescriptors(Document document) diff --git a/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.csproj b/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.csproj index 7e4a4cd43ddf3..a07dad65823cb 100644 --- a/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.csproj +++ b/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.csproj @@ -1,5 +1,6 @@  + net472 @@ -28,7 +29,7 @@ <_ServiceHubConfigFiles Include="@(ServiceHubService)" FileSuffix="" /> <_ServiceHubConfigFiles Include="@(ServiceHubService)" FileSuffix="64" /> - <_FileEntries Include='file source="$(IntermediateOutputPath)%(_ServiceHubConfigFiles.FileName)%(_ServiceHubConfigFiles.FileSuffix).servicehub.service.json"'/> + <_FileEntries Include='file source="$(IntermediateOutputPath)%(_ServiceHubConfigFiles.Identity)%(_ServiceHubConfigFiles.FileSuffix).servicehub.service.json"'/> diff --git a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/InProcDesignerAttributeIncrementalAnalyzer.cs b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/InProcDesignerAttributeIncrementalAnalyzer.cs index 7ae9157b36202..f44a4a3185987 100644 --- a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/InProcDesignerAttributeIncrementalAnalyzer.cs +++ b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/InProcDesignerAttributeIncrementalAnalyzer.cs @@ -21,10 +21,10 @@ public InProcDesignerAttributeIncrementalAnalyzer(Workspace workspace, IDesigner _listener = listener; } - protected override Task ReportProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken) + protected override ValueTask ReportProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken) => _listener.OnProjectRemovedAsync(projectId, cancellationToken); - protected override Task ReportDesignerAttributeDataAsync(List data, CancellationToken cancellationToken) + protected override ValueTask ReportDesignerAttributeDataAsync(List data, CancellationToken cancellationToken) => _listener.ReportDesignerAttributeDataAsync(data.ToImmutableArray(), cancellationToken); } } diff --git a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs index a7e9094795047..d3b36d99eb96c 100644 --- a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -31,7 +31,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribu { [ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host), Shared] internal class VisualStudioDesignerAttributeService - : ForegroundThreadAffinitizedObject, IDesignerAttributeListener, IEventListener + : ForegroundThreadAffinitizedObject, IDesignerAttributeListener, IEventListener, IDisposable { private readonly VisualStudioWorkspaceImpl _workspace; @@ -44,7 +44,7 @@ internal class VisualStudioDesignerAttributeService /// Our connection to the remote OOP server. Created on demand when we startup and then /// kept around for the lifetime of this service. /// - private RemoteServiceConnection? _connection; + private RemoteServiceConnection? _lazyConnection; /// /// Cache from project to the CPS designer service for it. Computed on demand (which @@ -80,6 +80,11 @@ public VisualStudioDesignerAttributeService( ThreadingContext.DisposalToken); } + public void Dispose() + { + _lazyConnection?.Dispose(); + } + void IEventListener.StartListening(Workspace workspace, object _) { if (workspace is VisualStudioWorkspace) @@ -118,15 +123,12 @@ private async Task StartWorkerAsync() // 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. - _connection = await client.CreateConnectionAsync( - WellKnownServiceHubService.RemoteDesignerAttributeService, - callbackTarget: this, cancellationToken).ConfigureAwait(false); + _lazyConnection = await client.CreateConnectionAsync(callbackTarget: this, cancellationToken).ConfigureAwait(false); // Now kick off scanning in the OOP process. - await _connection.RunRemoteAsync( - nameof(IRemoteDesignerAttributeService.StartScanningForDesignerAttributesAsync), - solution: null, - arguments: Array.Empty(), + // If the call fails an error has already been reported and there is nothing more to do. + _ = await _lazyConnection.TryInvokeAsync( + (service, cancellationToken) => service.StartScanningForDesignerAttributesAsync(cancellationToken), cancellationToken).ConfigureAwait(false); } @@ -332,17 +334,17 @@ private async Task NotifyCpsProjectSystemAsync( /// /// Callback from the OOP service back into us. /// - public Task ReportDesignerAttributeDataAsync(ImmutableArray data, CancellationToken cancellationToken) + public ValueTask ReportDesignerAttributeDataAsync(ImmutableArray data, CancellationToken cancellationToken) { Contract.ThrowIfNull(_workQueue); _workQueue.AddWork(data); - return Task.CompletedTask; + return new ValueTask(); } - public Task OnProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken) + public ValueTask OnProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken) { _cpsProjects.TryRemove(projectId, out _); - return Task.CompletedTask; + return new ValueTask(); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/LanguageServerClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/LanguageServerClient.cs index 3c7b7c3cf975e..a9c7a134bbd78 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/LanguageServerClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/LanguageServerClient.cs @@ -88,14 +88,12 @@ public async Task ActivateAsync(CancellationToken cancellationToken) return null; } - var hostGroup = new HostGroup(client.ClientId); var hubClient = new HubClient(ServiceHubClientName); var stream = await ServiceHubRemoteHostClient.RequestServiceAsync( _services, hubClient, WellKnownServiceHubService.LanguageServer, - hostGroup, cancellationToken).ConfigureAwait(false); return new Connection(stream, stream); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs b/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs index 80bdccadd9be9..33c38ab72590d 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs @@ -26,7 +26,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectTelemetr { [ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host), Shared] internal class VisualStudioProjectTelemetryService - : ForegroundThreadAffinitizedObject, IProjectTelemetryListener, IEventListener + : ForegroundThreadAffinitizedObject, IProjectTelemetryListener, IEventListener, IDisposable { private const string EventPrefix = "VS/Compilers/Compilation/"; private const string PropertyPrefix = "VS.Compilers.Compilation.Inputs."; @@ -49,7 +49,7 @@ internal class VisualStudioProjectTelemetryService /// Our connection to the remote OOP server. Created on demand when we startup and then /// kept around for the lifetime of this service. /// - private RemoteServiceConnection? _connection; + private RemoteServiceConnection? _lazyConnection; /// /// Queue where we enqueue the information we get from OOP to process in batch in the future. @@ -70,6 +70,11 @@ public VisualStudioProjectTelemetryService( threadingContext.DisposalToken); } + public void Dispose() + { + _lazyConnection?.Dispose(); + } + void IEventListener.StartListening(Workspace workspace, object _) { if (workspace is VisualStudioWorkspace) @@ -105,15 +110,12 @@ private async Task StartWorkerAsync() // 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. - _connection = await client.CreateConnectionAsync( - WellKnownServiceHubService.RemoteProjectTelemetryService, - callbackTarget: this, cancellationToken).ConfigureAwait(false); + _lazyConnection = await client.CreateConnectionAsync(callbackTarget: this, cancellationToken).ConfigureAwait(false); // Now kick off scanning in the OOP process. - await _connection.RunRemoteAsync( - nameof(IRemoteProjectTelemetryService.ComputeProjectTelemetryAsync), - solution: null, - arguments: Array.Empty(), + // If the call fails an error has already been reported and there is nothing more to do. + _ = await _lazyConnection.TryInvokeAsync( + (service, cancellationToken) => service.ComputeProjectTelemetryAsync(cancellationToken), cancellationToken).ConfigureAwait(false); } @@ -184,11 +186,11 @@ private void NotifyTelemetryService(ProjectTelemetryData info) /// /// Callback from the OOP service back into us. /// - public Task ReportProjectTelemetryDataAsync(ProjectTelemetryData info, CancellationToken cancellationToken) + public ValueTask ReportProjectTelemetryDataAsync(ProjectTelemetryData info, CancellationToken cancellationToken) { Contract.ThrowIfNull(_workQueue); _workQueue.AddWork(info); - return Task.CompletedTask; + return new ValueTask(); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/VisualStudioRemoteHostClientProvider.cs b/src/VisualStudio/Core/Def/Implementation/Remote/VisualStudioRemoteHostClientProvider.cs index 778e1caf7f1ba..3342fc93a8e45 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/VisualStudioRemoteHostClientProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/VisualStudioRemoteHostClientProvider.cs @@ -6,13 +6,17 @@ using System; using System.Composition; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Telemetry; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.ServiceBroker; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Remote @@ -22,10 +26,13 @@ internal sealed class VisualStudioRemoteHostClientProvider : IRemoteHostClientPr [ExportWorkspaceServiceFactory(typeof(IRemoteHostClientProvider), WorkspaceKind.Host), Shared] internal sealed class Factory : IWorkspaceServiceFactory { + private readonly IAsyncServiceProvider _vsServiceProvider; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public Factory() + public Factory(SVsServiceProvider vsServiceProvider) { + _vsServiceProvider = (IAsyncServiceProvider)vsServiceProvider; } [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] @@ -38,23 +45,42 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) return new DefaultRemoteHostClientProvider(); } - return new VisualStudioRemoteHostClientProvider(workspaceServices); + return new VisualStudioRemoteHostClientProvider(workspaceServices, _vsServiceProvider); } } private readonly HostWorkspaceServices _services; - private readonly AsyncLazy _lazyClient; + private readonly AsyncLazy _lazyClient; + private readonly IAsyncServiceProvider _vsServiceProvider; - private VisualStudioRemoteHostClientProvider(HostWorkspaceServices services) + private VisualStudioRemoteHostClientProvider(HostWorkspaceServices services, IAsyncServiceProvider vsServiceProvider) { _services = services; - _lazyClient = new AsyncLazy(CreateHostClientAsync, cacheResult: true); + _vsServiceProvider = vsServiceProvider; + _lazyClient = new AsyncLazy(CreateHostClientAsync, cacheResult: true); } - private Task CreateHostClientAsync(CancellationToken cancellationToken) - => ServiceHubRemoteHostClient.CreateAsync(_services, cancellationToken); + private async Task CreateHostClientAsync(CancellationToken cancellationToken) + { + try + { + var brokeredServiceContainer = await _vsServiceProvider.GetServiceAsync().ConfigureAwait(false); + var serviceBroker = brokeredServiceContainer.GetFullAccessServiceBroker(); + + var client = await ServiceHubRemoteHostClient.CreateAsync(_services, serviceBroker, cancellationToken).ConfigureAwait(false); + + // proffer in-proc brokered services: + _ = brokeredServiceContainer.Proffer(SolutionAssetProvider.ServiceDescriptor, (_, _, _, _) => new ValueTask(new SolutionAssetProvider(_services))); + + return client; + } + catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceled(e)) + { + return null; + } + } public Task TryGetRemoteHostClientAsync(CancellationToken cancellationToken) - => _lazyClient.GetValueAsync(cancellationToken).AsNullable(); + => _lazyClient.GetValueAsync(cancellationToken); } } diff --git a/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/SemanticClassificationCacheIncrementalAnalyzerProvider.cs b/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/SemanticClassificationCacheIncrementalAnalyzerProvider.cs index 6c05e78dda477..7271307836773 100644 --- a/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/SemanticClassificationCacheIncrementalAnalyzerProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/SemanticClassificationCacheIncrementalAnalyzerProvider.cs @@ -64,11 +64,9 @@ public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bo var isFullyLoaded = await statusService.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); Debug.Assert(isFullyLoaded, "We should only be called by the incremental analyzer once the solution is fully loaded."); - await client.RunRemoteAsync( - WellKnownServiceHubService.CodeAnalysis, - nameof(IRemoteSemanticClassificationCacheService.CacheSemanticClassificationsAsync), + await client.TryInvokeAsync( document.Project.Solution, - arguments: new object[] { document.Id, isFullyLoaded }, + (service, solutionInfo, cancellationToken) => service.CacheSemanticClassificationsAsync(solutionInfo, document.Id, isFullyLoaded, cancellationToken), callbackTarget: null, cancellationToken).ConfigureAwait(false); } diff --git a/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/VisualStudioSemanticClassificationCacheService.cs b/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/VisualStudioSemanticClassificationCacheService.cs index 300464c1434ca..6131db8b2e3ef 100644 --- a/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/VisualStudioSemanticClassificationCacheService.cs +++ b/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/VisualStudioSemanticClassificationCacheService.cs @@ -52,18 +52,16 @@ public async Task> GetCachedSemanticClassificatio return default; } - var classifiedSpans = await client.RunRemoteAsync( - WellKnownServiceHubService.CodeAnalysis, - nameof(IRemoteSemanticClassificationCacheService.GetCachedSemanticClassificationsAsync), - solution: null, - arguments: new object[] { documentKey.Dehydrate(), textSpan, checksum }, + var classifiedSpans = await client.TryInvokeAsync( + (service, cancellationToken) => service.GetCachedSemanticClassificationsAsync(documentKey.Dehydrate(), textSpan, checksum, cancellationToken), callbackTarget: null, cancellationToken).ConfigureAwait(false); - if (classifiedSpans == null) + + if (!classifiedSpans.HasValue || classifiedSpans.Value == null) return default; var list = ClassificationUtilities.GetOrCreateClassifiedSpanList(); - classifiedSpans.Rehydrate(list); + classifiedSpans.Value.Rehydrate(list); var result = list.ToImmutableArray(); ClassificationUtilities.ReturnClassifiedSpanList(list); diff --git a/src/VisualStudio/Core/Def/Implementation/TodoComments/InProcTodoCommentsIncrementalAnalyzer.cs b/src/VisualStudio/Core/Def/Implementation/TodoComments/InProcTodoCommentsIncrementalAnalyzer.cs index ffced79472572..d45ae2064b25b 100644 --- a/src/VisualStudio/Core/Def/Implementation/TodoComments/InProcTodoCommentsIncrementalAnalyzer.cs +++ b/src/VisualStudio/Core/Def/Implementation/TodoComments/InProcTodoCommentsIncrementalAnalyzer.cs @@ -19,7 +19,7 @@ internal sealed class InProcTodoCommentsIncrementalAnalyzer : AbstractTodoCommen public InProcTodoCommentsIncrementalAnalyzer(ITodoCommentsListener listener) => _listener = listener; - protected override Task ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken) + protected override ValueTask ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken) => _listener.ReportTodoCommentDataAsync(documentId, data, cancellationToken); } } diff --git a/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs b/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs index e6f1a976e85d1..cfec1aaf6663d 100644 --- a/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs +++ b/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs @@ -36,7 +36,8 @@ internal class VisualStudioTodoCommentsService ITodoCommentsListener, ITodoListProvider, IVsTypeScriptTodoCommentService, - IEventListener + IEventListener, + IDisposable { private readonly VisualStudioWorkspaceImpl _workspace; private readonly EventListenerTracker _eventListenerTracker; @@ -45,10 +46,10 @@ private readonly ConcurrentDictionary>(); /// - /// Our connections to the remote OOP server. Created on demand when we startup and then + /// Remote service connection. Created on demand when we startup and then /// kept around for the lifetime of this service. /// - private RemoteServiceConnection? _connection; + private RemoteServiceConnection? _lazyConnection; /// /// Queue where we enqueue the information we get from OOP to process in batch in the future. @@ -70,6 +71,11 @@ public VisualStudioTodoCommentsService( _eventListenerTracker = new EventListenerTracker(eventListeners, WellKnownEventListeners.TodoListProvider); } + public void Dispose() + { + _lazyConnection?.Dispose(); + } + void IEventListener.StartListening(Workspace workspace, object _) { if (workspace is VisualStudioWorkspace) @@ -114,18 +120,15 @@ private async Task StartWorkerAsync() // 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. - _connection = await client.CreateConnectionAsync( - WellKnownServiceHubService.RemoteTodoCommentsService, - callbackTarget: this, cancellationToken).ConfigureAwait(false); + _lazyConnection = await client.CreateConnectionAsync(callbackTarget: this, cancellationToken).ConfigureAwait(false); // 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 _connection.RunRemoteAsync( - nameof(IRemoteTodoCommentsService.ComputeTodoCommentsAsync), - solution: null, - arguments: Array.Empty(), + // If the call fails an error has already been reported and there is nothing more to do. + _ = await _lazyConnection.TryInvokeAsync( + (service, cancellationToken) => service.ComputeTodoCommentsAsync(cancellationToken), cancellationToken).ConfigureAwait(false); } @@ -218,10 +221,18 @@ public IEnumerable GetTodoItemsUpdatedEventArgs( /// /// Callback from the OOP service back into us. /// - public async Task ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray infos, CancellationToken cancellationToken) + public async ValueTask ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray infos, CancellationToken cancellationToken) { - var workQueue = await _workQueueSource.Task.ConfigureAwait(false); - workQueue.AddWork(new DocumentAndComments(documentId, infos)); + try + { + var workQueue = await _workQueueSource.Task.ConfigureAwait(false); + workQueue.AddWork(new DocumentAndComments(documentId, infos)); + } + catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceledAndPropagate(e)) + { + // report NFW before returning back to the remote process + throw ExceptionUtilities.Unreachable; + } } /// diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index cced33b1a8d09..e14c428cfa0bf 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -1,6 +1,7 @@  + Library Microsoft.VisualStudio.LanguageServices @@ -28,8 +29,11 @@ + + 6cf2e545-6109-4730-8883-cf43d7aec3e1 + - + @@ -194,4 +198,15 @@ + + + + + + + + + + diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 4ba74296e229f..94bcd55686473 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.Logging; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.CodeAnalysis.Versions; using Microsoft.VisualStudio.ComponentModelHost; @@ -40,6 +41,7 @@ using Microsoft.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell.ServiceBroker; using Microsoft.VisualStudio.TaskStatusCenter; using Microsoft.VisualStudio.Telemetry; using Microsoft.VisualStudio.TextManager.Interop; diff --git a/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs b/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs index 535614d6ec869..4760fe60c367c 100644 --- a/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs @@ -6,6 +6,7 @@ using System; using System.Composition; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; @@ -59,7 +60,7 @@ await client.RunRemoteAsync( WellKnownServiceHubService.RemoteHost, nameof(IRemoteHostService.InitializeTelemetrySession), solution: null, - new object?[] { client.ClientId, settings }, + new object?[] { Process.GetCurrentProcess().Id, settings }, callbackTarget: null, CancellationToken.None).ConfigureAwait(false); }); diff --git a/src/VisualStudio/Core/Test.Next/Mocks/SimpleAssetSource.cs b/src/VisualStudio/Core/Test.Next/Mocks/SimpleAssetSource.cs index 40acf2fc4ea0a..9fffcbef61a33 100644 --- a/src/VisualStudio/Core/Test.Next/Mocks/SimpleAssetSource.cs +++ b/src/VisualStudio/Core/Test.Next/Mocks/SimpleAssetSource.cs @@ -25,7 +25,7 @@ public SimpleAssetSource(IReadOnlyDictionary map) _map = map; } - public Task> GetAssetsAsync( + public ValueTask> GetAssetsAsync( int serviceId, ISet checksums, ISerializerService serializerService, CancellationToken cancellationToken) { var results = new List<(Checksum, object)>(); @@ -42,10 +42,10 @@ public SimpleAssetSource(IReadOnlyDictionary map) } } - return Task.FromResult(results.ToImmutableArray()); + return new ValueTask>(results.ToImmutableArray()); } - public Task IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken) - => SpecializedTasks.False; + public ValueTask IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken) + => new ValueTask(false); } } diff --git a/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs b/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs index 9bcb6aa3c43b6..a0a8660271e02 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs @@ -95,7 +95,7 @@ public async Task TestSessionClosed() // register local service TestService testService = null; - client.RegisterService(serviceName, (s, p) => + client.RegisterService(serviceName, (s, p, o) => { testService = new TestService(s, p); return testService; diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index cd516becc99df..e2d786272672d 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -23,7 +23,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.TodoComments; using Microsoft.CodeAnalysis.UnitTests; -using Nerdbank; +using Nerdbank.Streams; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -45,7 +45,7 @@ public void TestRemoteHostCreation() { var remoteLogger = new TraceSource("inprocRemoteClient"); var testData = new RemoteHostTestData(new RemoteWorkspaceManager(new SolutionAssetCache()), isInProc: true); - var streams = FullDuplexStream.CreateStreams(); + var streams = FullDuplexStream.CreatePair(); using var _ = new RemoteHostService(streams.Item1, new InProcRemoteHostClient.ServiceProvider(remoteLogger, testData)); } @@ -138,15 +138,10 @@ public async Task TestTodoComments() var cancellationTokenSource = new CancellationTokenSource(); - using var connection = await client.CreateConnectionAsync( - WellKnownServiceHubService.RemoteTodoCommentsService, - callback, - cancellationTokenSource.Token); + using var connection = await client.CreateConnectionAsync(callback, cancellationTokenSource.Token); - var invokeTask = connection.RunRemoteAsync( - nameof(IRemoteTodoCommentsService.ComputeTodoCommentsAsync), - solution: null, - arguments: Array.Empty(), + var invokeTask = connection.TryInvokeAsync( + (service, cancellationToken) => service.ComputeTodoCommentsAsync(cancellationToken), cancellationTokenSource.Token); var data = await callback.Data; @@ -169,7 +164,7 @@ public async Task TestTodoComments() cancellationTokenSource.Cancel(); - await invokeTask; + Assert.True(await invokeTask); } private class TodoCommentsListener : ITodoCommentsListener @@ -178,10 +173,10 @@ private class TodoCommentsListener : ITodoCommentsListener = new TaskCompletionSource<(DocumentId, ImmutableArray)>(); public Task<(DocumentId, ImmutableArray)> Data => _dataSource.Task; - public Task ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken) + public ValueTask ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken) { _dataSource.SetResult((documentId, data)); - return Task.CompletedTask; + return new ValueTask(); } } @@ -221,15 +216,10 @@ public async Task TestDesignerAttributes() var callback = new DesignerAttributeListener(); - using var connection = await client.CreateConnectionAsync( - WellKnownServiceHubService.RemoteDesignerAttributeService, - callback, - cancellationTokenSource.Token); + using var connection = await client.CreateConnectionAsync(callback, cancellationTokenSource.Token); - var invokeTask = connection.RunRemoteAsync( - nameof(IRemoteDesignerAttributeService.StartScanningForDesignerAttributesAsync), - solution: null, - arguments: Array.Empty(), + var invokeTask = connection.TryInvokeAsync( + (service, cancellationToken) => service.StartScanningForDesignerAttributesAsync(cancellationToken), cancellationTokenSource.Token); var infos = await callback.Infos; @@ -241,7 +231,7 @@ public async Task TestDesignerAttributes() cancellationTokenSource.Cancel(); - await invokeTask; + Assert.True(await invokeTask); } private class DesignerAttributeListener : IDesignerAttributeListener @@ -250,13 +240,13 @@ private readonly TaskCompletionSource> _in = new TaskCompletionSource>(); public Task> Infos => _infosSource.Task; - public Task OnProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken) - => Task.CompletedTask; + public ValueTask OnProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken) + => new ValueTask(); - public Task ReportDesignerAttributeDataAsync(ImmutableArray infos, CancellationToken cancellationToken) + public ValueTask ReportDesignerAttributeDataAsync(ImmutableArray infos, CancellationToken cancellationToken) { _infosSource.SetResult(infos); - return Task.CompletedTask; + return new ValueTask(); } } diff --git a/src/VisualStudio/Setup/Directory.Build.targets b/src/VisualStudio/Setup/Directory.Build.targets new file mode 100644 index 0000000000000..1a342e17f2315 --- /dev/null +++ b/src/VisualStudio/Setup/Directory.Build.targets @@ -0,0 +1,45 @@ + + + + + + + + + + <_GeneratedVsixManifestPath>$(IntermediateOutputPath)source.extension.g.vsixmanifest + + + + + + + + <_Placeholder>]]> + <_Replacement>@(ServiceHubServiceJsonFile->'<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="%(FileName)%(Extension)" />', ' + ') + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index 73d0ae07cacce..ade718fcba07b 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -1,6 +1,7 @@  + Library Roslyn.VisualStudio.Setup diff --git a/src/VisualStudio/Setup/source.extension.vsixmanifest b/src/VisualStudio/Setup/source.extension.vsixmanifest index 0b55189bed8ef..1f75a815f705e 100644 --- a/src/VisualStudio/Setup/source.extension.vsixmanifest +++ b/src/VisualStudio/Setup/source.extension.vsixmanifest @@ -33,22 +33,6 @@ - - - - - - - - - - - - - - - - @@ -76,6 +60,8 @@ + + diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index 838df901664a0..290699d2f7fcb 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -44,35 +44,28 @@ public async Task AddSemanticClassificationsAsync(Document document, TextSpan te return; } - var remoteSuccess = await TryAddSemanticClassificationsInRemoteProcessAsync( - document, textSpan, result, cancellationToken).ConfigureAwait(false); - if (remoteSuccess) - return; - - using var _ = ArrayBuilder.GetInstance(out var temp); - await AddSemanticClassificationsInCurrentProcessAsync( - document, textSpan, temp, cancellationToken).ConfigureAwait(false); - AddRange(temp, result); - } - - /// if the remote call was made successfully and we should - /// use the results of it. Otherwise, fall back to processing locally - private static async Task TryAddSemanticClassificationsInRemoteProcessAsync(Document document, TextSpan textSpan, List result, CancellationToken cancellationToken) - { var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); - if (client == null) - return false; - - var classifiedSpans = await client.RunRemoteAsync( - WellKnownServiceHubService.CodeAnalysis, - nameof(IRemoteSemanticClassificationService.GetSemanticClassificationsAsync), - document.Project.Solution, - new object[] { document.Id, textSpan }, - callbackTarget: null, - cancellationToken).ConfigureAwait(false); - - classifiedSpans.Rehydrate(result); - return true; + if (client != null) + { + var classifiedSpans = await client.TryInvokeAsync( + document.Project.Solution, + (service, solutionInfo, cancellationToken) => service.GetSemanticClassificationsAsync(solutionInfo, document.Id, textSpan, cancellationToken), + callbackTarget: null, + cancellationToken).ConfigureAwait(false); + + // if the remote call fails do nothing (error has already been reported) + if (classifiedSpans.HasValue) + { + classifiedSpans.Value.Rehydrate(result); + } + } + else + { + using var _ = ArrayBuilder.GetInstance(out var temp); + await AddSemanticClassificationsInCurrentProcessAsync( + document, textSpan, temp, cancellationToken).ConfigureAwait(false); + AddRange(temp, result); + } } public static async Task AddSemanticClassificationsInCurrentProcessAsync(Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationCacheService.cs b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationCacheService.cs index 8365601411429..1fc45f2df836d 100644 --- a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationCacheService.cs +++ b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationCacheService.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.Classification /// internal interface IRemoteSemanticClassificationCacheService { - Task CacheSemanticClassificationsAsync( + ValueTask CacheSemanticClassificationsAsync( PinnedSolutionInfo solutionInfo, DocumentId documentId, bool isFullyLoaded, CancellationToken cancellationToken); /// @@ -27,7 +27,7 @@ Task CacheSemanticClassificationsAsync( /// /// Pass in . This will ensure that the cached /// classifications are only returned if they match the content the file currently has. - Task GetCachedSemanticClassificationsAsync( + ValueTask GetCachedSemanticClassificationsAsync( SerializableDocumentKey documentKey, TextSpan textSpan, Checksum checksum, diff --git a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs index 4a96e58bcaacb..062a5fac72e5d 100644 --- a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Classification { internal interface IRemoteSemanticClassificationService { - Task GetSemanticClassificationsAsync( + ValueTask GetSemanticClassificationsAsync( PinnedSolutionInfo solutionInfo, DocumentId documentId, TextSpan span, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/DesignerAttribute/IDesignerAttributeListener.cs b/src/Workspaces/Core/Portable/DesignerAttribute/IDesignerAttributeListener.cs index 17f9e31c9df1e..a2341af3cde8b 100644 --- a/src/Workspaces/Core/Portable/DesignerAttribute/IDesignerAttributeListener.cs +++ b/src/Workspaces/Core/Portable/DesignerAttribute/IDesignerAttributeListener.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.DesignerAttribute /// internal interface IDesignerAttributeListener { - Task OnProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken); - Task ReportDesignerAttributeDataAsync(ImmutableArray data, CancellationToken cancellationToken); + ValueTask OnProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken); + ValueTask ReportDesignerAttributeDataAsync(ImmutableArray data, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/DesignerAttribute/IRemoteDesignerAttributeService.cs b/src/Workspaces/Core/Portable/DesignerAttribute/IRemoteDesignerAttributeService.cs index fcc902b58d196..4f2565e061768 100644 --- a/src/Workspaces/Core/Portable/DesignerAttribute/IRemoteDesignerAttributeService.cs +++ b/src/Workspaces/Core/Portable/DesignerAttribute/IRemoteDesignerAttributeService.cs @@ -15,6 +15,6 @@ namespace Microsoft.CodeAnalysis.DesignerAttribute /// internal interface IRemoteDesignerAttributeService { - Task StartScanningForDesignerAttributesAsync(CancellationToken cancellation); + ValueTask StartScanningForDesignerAttributesAsync(CancellationToken cancellation); } } diff --git a/src/Workspaces/Core/Portable/ProjectTelemetry/IProjectTelemetryListener.cs b/src/Workspaces/Core/Portable/ProjectTelemetry/IProjectTelemetryListener.cs index df0f87f5cb0b4..963470a043496 100644 --- a/src/Workspaces/Core/Portable/ProjectTelemetry/IProjectTelemetryListener.cs +++ b/src/Workspaces/Core/Portable/ProjectTelemetry/IProjectTelemetryListener.cs @@ -15,6 +15,6 @@ namespace Microsoft.CodeAnalysis.ProjectTelemetry /// internal interface IProjectTelemetryListener { - Task ReportProjectTelemetryDataAsync(ProjectTelemetryData data, CancellationToken cancellationToken); + ValueTask ReportProjectTelemetryDataAsync(ProjectTelemetryData data, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/ProjectTelemetry/IRemoteProjectTelemetryService.cs b/src/Workspaces/Core/Portable/ProjectTelemetry/IRemoteProjectTelemetryService.cs index 2a16ef3cbedee..65ebdea33b289 100644 --- a/src/Workspaces/Core/Portable/ProjectTelemetry/IRemoteProjectTelemetryService.cs +++ b/src/Workspaces/Core/Portable/ProjectTelemetry/IRemoteProjectTelemetryService.cs @@ -15,6 +15,6 @@ namespace Microsoft.CodeAnalysis.ProjectTelemetry /// internal interface IRemoteProjectTelemetryService { - Task ComputeProjectTelemetryAsync(CancellationToken cancellation); + ValueTask ComputeProjectTelemetryAsync(CancellationToken cancellation); } } diff --git a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs index 25f51244ad319..3eaa1bd0125cc 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs @@ -23,14 +23,6 @@ internal abstract class RemoteHostClient : IDisposable { public event EventHandler? StatusChanged; - /// - /// Return an unique string per client. - /// - /// one can use this to distinguish different clients that are connected to different RemoteHosts including - /// cases where 2 external process finding each others - /// - public abstract string ClientId { get; } - protected void Started() { OnStatusChanged(started: true); @@ -66,8 +58,72 @@ private void OnStatusChanged(bool started) return service.TryGetRemoteHostClientAsync(cancellationToken); } + public abstract ValueTask> CreateConnectionAsync(object? callbackTarget, CancellationToken cancellationToken) + where T : class; + public abstract Task CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken); + // brokered services: + + public async ValueTask TryInvokeAsync( + Func invocation, + object? callbackTarget, + CancellationToken cancellationToken) + where TService : class + { + using var connection = await CreateConnectionAsync(callbackTarget, cancellationToken).ConfigureAwait(false); + return await connection.TryInvokeAsync(invocation, cancellationToken).ConfigureAwait(false); + } + + public async ValueTask> TryInvokeAsync( + Func> invocation, + object? callbackTarget, + CancellationToken cancellationToken) + where TService : class + { + using var connection = await CreateConnectionAsync(callbackTarget, cancellationToken).ConfigureAwait(false); + return await connection.TryInvokeAsync(invocation, cancellationToken).ConfigureAwait(false); + } + + public async ValueTask TryInvokeAsync( + Solution solution, + Func invocation, + object? callbackTarget, + CancellationToken cancellationToken) + where TService : class + { + using var connection = await CreateConnectionAsync(callbackTarget, cancellationToken).ConfigureAwait(false); + return await connection.TryInvokeAsync(solution, invocation, cancellationToken).ConfigureAwait(false); + } + + public async ValueTask> TryInvokeAsync( + Solution solution, + Func> invocation, + object? callbackTarget, + CancellationToken cancellationToken) + where TService : class + { + using var connection = await CreateConnectionAsync(callbackTarget, cancellationToken).ConfigureAwait(false); + return await connection.TryInvokeAsync(solution, invocation, cancellationToken).ConfigureAwait(false); + } + + /// + /// Invokes a remote API that streams results back to the caller. + /// + public async ValueTask> TryInvokeAsync( + Solution solution, + Func invocation, + Func> reader, + object? callbackTarget, + CancellationToken cancellationToken) + where TService : class + { + using var connection = await CreateConnectionAsync(callbackTarget, cancellationToken).ConfigureAwait(false); + return await connection.TryInvokeAsync(solution, invocation, reader, cancellationToken).ConfigureAwait(false); + } + + // legacy services: + public async Task RunRemoteAsync(RemoteServiceName serviceName, string targetName, Solution? solution, IReadOnlyList arguments, object? callbackTarget, CancellationToken cancellationToken) { using var connection = await CreateConnectionAsync(serviceName, callbackTarget, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs b/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs index 6517bad1bd762..5d94f0cbafd91 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs @@ -12,6 +12,9 @@ namespace Microsoft.CodeAnalysis.Remote { + /// + /// Abstracts a connection to a legacy remote service. + /// internal abstract class RemoteServiceConnection : IDisposable { public abstract void Dispose(); @@ -22,4 +25,43 @@ internal abstract class RemoteServiceConnection : IDisposable public Task RunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, CancellationToken cancellationToken) => RunRemoteAsync(targetName, solution, arguments, dataReader: null, cancellationToken); } + + /// + /// Abstracts a connection to a service implementing type . + /// + /// Remote interface type of the service. + internal abstract class RemoteServiceConnection : IDisposable + where TService : class + { + public abstract void Dispose(); + + public abstract ValueTask TryInvokeAsync( + Func invocation, + CancellationToken cancellationToken); + + public abstract ValueTask> TryInvokeAsync( + Func> invocation, + CancellationToken cancellationToken); + + public abstract ValueTask> TryInvokeAsync( + Func invocation, + Func> reader, + CancellationToken cancellationToken); + + public abstract ValueTask TryInvokeAsync( + Solution solution, + Func invocation, + CancellationToken cancellationToken); + + public abstract ValueTask> TryInvokeAsync( + Solution solution, + Func> invocation, + CancellationToken cancellationToken); + + public abstract ValueTask> TryInvokeAsync( + Solution solution, + Func invocation, + Func> reader, + CancellationToken cancellationToken); + } } diff --git a/src/Workspaces/Core/Portable/Remote/RemoteServiceName.cs b/src/Workspaces/Core/Portable/Remote/RemoteServiceName.cs index 86e3893df01ad..ca4092ad760e7 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteServiceName.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteServiceName.cs @@ -19,6 +19,7 @@ namespace Microsoft.CodeAnalysis.Remote internal readonly struct RemoteServiceName : IEquatable { internal const string Prefix = "roslyn"; + internal const string Suffix64 = "64"; internal const string IntelliCodeServiceName = "pythia"; internal const string RazorServiceName = "razorLanguageService"; internal const string UnitTestingAnalysisServiceName = "UnitTestingAnalysis"; @@ -45,8 +46,6 @@ public RemoteServiceName(string customServiceName) public string ToString(bool isRemoteHost64Bit) { - const string Suffix64 = "64"; - return CustomServiceName ?? (WellKnownService, isRemoteHost64Bit) switch { (WellKnownServiceHubService.RemoteHost, false) => Prefix + nameof(WellKnownServiceHubService.RemoteHost), @@ -55,12 +54,6 @@ public string ToString(bool isRemoteHost64Bit) (WellKnownServiceHubService.CodeAnalysis, true) => Prefix + nameof(WellKnownServiceHubService.CodeAnalysis) + Suffix64, (WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine, false) => Prefix + nameof(WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine), (WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine, true) => Prefix + nameof(WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine) + Suffix64, - (WellKnownServiceHubService.RemoteDesignerAttributeService, false) => Prefix + nameof(WellKnownServiceHubService.RemoteDesignerAttributeService), - (WellKnownServiceHubService.RemoteDesignerAttributeService, true) => Prefix + nameof(WellKnownServiceHubService.RemoteDesignerAttributeService) + Suffix64, - (WellKnownServiceHubService.RemoteProjectTelemetryService, false) => Prefix + nameof(WellKnownServiceHubService.RemoteProjectTelemetryService), - (WellKnownServiceHubService.RemoteProjectTelemetryService, true) => Prefix + nameof(WellKnownServiceHubService.RemoteProjectTelemetryService) + Suffix64, - (WellKnownServiceHubService.RemoteTodoCommentsService, false) => Prefix + nameof(WellKnownServiceHubService.RemoteTodoCommentsService), - (WellKnownServiceHubService.RemoteTodoCommentsService, true) => Prefix + nameof(WellKnownServiceHubService.RemoteTodoCommentsService) + Suffix64, (WellKnownServiceHubService.LanguageServer, false) => Prefix + nameof(WellKnownServiceHubService.LanguageServer), (WellKnownServiceHubService.LanguageServer, true) => Prefix + nameof(WellKnownServiceHubService.LanguageServer) + Suffix64, diff --git a/src/Workspaces/Core/Portable/Remote/WellKnownServiceHubService.cs b/src/Workspaces/Core/Portable/Remote/WellKnownServiceHubService.cs index c8634a328580e..787fd1e23f60d 100644 --- a/src/Workspaces/Core/Portable/Remote/WellKnownServiceHubService.cs +++ b/src/Workspaces/Core/Portable/Remote/WellKnownServiceHubService.cs @@ -8,20 +8,20 @@ namespace Microsoft.CodeAnalysis.Remote { internal enum WellKnownServiceHubService { - None, - RemoteHost, - CodeAnalysis, - RemoteSymbolSearchUpdateEngine, - RemoteDesignerAttributeService, - RemoteProjectTelemetryService, - RemoteTodoCommentsService, - LanguageServer, - IntelliCode, - Razor, + None = 0, + RemoteHost = 1, + CodeAnalysis = 2, + RemoteSymbolSearchUpdateEngine = 3, + // obsolete: RemoteDesignerAttributeService = 4, + // obsolete: RemoteProjectTelemetryService = 5, + // obsolete: RemoteTodoCommentsService = 6, + LanguageServer = 7, + IntelliCode = 8, + Razor = 9, // owned by Unit Testing team: - UnitTestingAnalysisService, - LiveUnitTestingBuildService, - UnitTestingSourceLookupService, + UnitTestingAnalysisService = 10, + LiveUnitTestingBuildService = 11, + UnitTestingSourceLookupService = 12, } } diff --git a/src/Workspaces/Core/Portable/TodoComments/IRemoteTodoCommentsService.cs b/src/Workspaces/Core/Portable/TodoComments/IRemoteTodoCommentsService.cs index 2123d1cbc6fc0..286a07b2b063c 100644 --- a/src/Workspaces/Core/Portable/TodoComments/IRemoteTodoCommentsService.cs +++ b/src/Workspaces/Core/Portable/TodoComments/IRemoteTodoCommentsService.cs @@ -15,6 +15,6 @@ namespace Microsoft.CodeAnalysis.TodoComments /// internal interface IRemoteTodoCommentsService { - Task ComputeTodoCommentsAsync(CancellationToken cancellation); + ValueTask ComputeTodoCommentsAsync(CancellationToken cancellation); } } diff --git a/src/Workspaces/Core/Portable/TodoComments/ITodoCommentsListener.cs b/src/Workspaces/Core/Portable/TodoComments/ITodoCommentsListener.cs index 9a4d718b5ffb2..3258d8077b12f 100644 --- a/src/Workspaces/Core/Portable/TodoComments/ITodoCommentsListener.cs +++ b/src/Workspaces/Core/Portable/TodoComments/ITodoCommentsListener.cs @@ -15,6 +15,6 @@ namespace Microsoft.CodeAnalysis.TodoComments /// internal interface ITodoCommentsListener { - Task ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken); + ValueTask ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index 3257d90fd2e78..b263854dc6324 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -8,33 +8,38 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Pipelines; using System.Runtime.Remoting; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Serialization; -using Nerdbank; +using Microsoft.CodeAnalysis.TodoComments; +using Microsoft.ServiceHub.Framework; +using Nerdbank.Streams; +using Roslyn.Test.Utilities; using Roslyn.Utilities; using StreamJsonRpc; +using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Remote.Testing { - internal sealed class InProcRemoteHostClient : RemoteHostClient, IRemoteHostServiceCallback + internal sealed partial class InProcRemoteHostClient : RemoteHostClient, IRemoteHostServiceCallback { - private readonly HostWorkspaceServices _services; + private readonly HostWorkspaceServices _workspaceServices; private readonly InProcRemoteServices _inprocServices; private readonly RemoteEndPoint _endPoint; private readonly TraceSource _logger; public static async Task CreateAsync(HostWorkspaceServices services, RemoteHostTestData testData) { - var inprocServices = new InProcRemoteServices(testData); + var inprocServices = new InProcRemoteServices(services, testData); var remoteHostStream = await inprocServices.RequestServiceAsync(WellKnownServiceHubService.RemoteHost).ConfigureAwait(false); - var clientId = $"InProc ({Guid.NewGuid()})"; - var instance = new InProcRemoteHostClient(clientId, services, inprocServices, remoteHostStream); + var instance = new InProcRemoteHostClient(services, inprocServices, remoteHostStream); // make sure connection is done right var uiCultureLCIDE = 0; @@ -52,13 +57,11 @@ await instance._endPoint.InvokeAsync( } private InProcRemoteHostClient( - string clientId, HostWorkspaceServices services, InProcRemoteServices inprocServices, Stream stream) { - ClientId = clientId; - _services = services; + _workspaceServices = services; _logger = new TraceSource("Default"); _inprocServices = inprocServices; @@ -86,24 +89,39 @@ public Task GetAssetsAsync(int scopeId, Checksum[] checksums, string pipeName, C pipeName, (scopeId, checksums), (writer, data, cancellationToken) => RemoteHostAssetSerialization.WriteDataAsync( - writer, _services.GetRequiredService().AssetStorage, _services.GetRequiredService(), data.scopeId, data.checksums, cancellationToken), + writer, _workspaceServices.GetRequiredService().AssetStorage, _workspaceServices.GetRequiredService(), data.scopeId, data.checksums, cancellationToken), cancellationToken); /// /// Remote API. /// public Task IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken) - => _services.GetRequiredService().IsExperimentEnabled(experimentName) ? SpecializedTasks.True : SpecializedTasks.False; + => _workspaceServices.GetRequiredService().IsExperimentEnabled(experimentName) ? SpecializedTasks.True : SpecializedTasks.False; public RemoteHostTestData TestData => _inprocServices.TestData; - public void RegisterService(RemoteServiceName serviceName, Func serviceCreator) + public void RegisterService(RemoteServiceName serviceName, Func serviceCreator) => _inprocServices.RegisterService(serviceName, serviceCreator); - public Task RequestServiceAsync(RemoteServiceName serviceName) - => _inprocServices.RequestServiceAsync(serviceName); + public override async ValueTask> CreateConnectionAsync(object? callbackTarget, CancellationToken cancellationToken) + { + var options = new ServiceActivationOptions(); + + if (callbackTarget is not null) + { + options.ClientRpcTarget = callbackTarget; + } + + var assetStorage = _workspaceServices.GetRequiredService().AssetStorage; + var descriptor = ServiceDescriptors.GetServiceDescriptor(typeof(T), isRemoteHost64Bit: IntPtr.Size == 8); + +#pragma warning disable ISB001 // Dispose of proxies - caller disposes + var proxy = await _inprocServices.ServiceBroker.GetProxyAsync(descriptor, options, cancellationToken).ConfigureAwait(false); +#pragma warning restore - public override string ClientId { get; } + Contract.ThrowIfNull(proxy); + return new BrokeredServiceConnection(proxy, assetStorage, errorReportingService: null); + } public override async Task CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken) { @@ -111,7 +129,7 @@ public override async Task CreateConnectionAsync(Remote // this is what consumer actually use to communicate information var serviceStream = await _inprocServices.RequestServiceAsync(serviceName).ConfigureAwait(false); - return new JsonRpcConnection(_services, _logger, callbackTarget, serviceStream, poolReclamation: null); + return new JsonRpcConnection(_workspaceServices, _logger, callbackTarget, serviceStream, poolReclamation: null); } public override void Dispose() @@ -153,41 +171,130 @@ public object GetService(Type serviceType) } } + private sealed class InProcServiceBroker : IServiceBroker + { + private readonly InProcRemoteServices _services; + + public InProcServiceBroker(InProcRemoteServices services) + { + _services = services; + } + + public event EventHandler? AvailabilityChanged { add { } remove { } } + + // This method is currently not needed for our IServiceBroker usage patterns. + public ValueTask GetPipeAsync(ServiceMoniker serviceMoniker, ServiceActivationOptions options, CancellationToken cancellationToken) + => throw ExceptionUtilities.Unreachable; + + public ValueTask GetProxyAsync(ServiceRpcDescriptor descriptor, ServiceActivationOptions options, CancellationToken cancellationToken) where T : class + { + var pipePair = FullDuplexStream.CreatePipePair(); + + var clientConnection = descriptor.ConstructRpcConnection(pipePair.Item2); + + Contract.ThrowIfFalse(options.ClientRpcTarget is null == descriptor.ClientInterface is null); + + if (descriptor.ClientInterface != null) + { + Contract.ThrowIfNull(options.ClientRpcTarget); + clientConnection.AddLocalRpcTarget(options.ClientRpcTarget); + } + + // Clear RPC target so that the server connection is forced to create a new proxy for the callback + // instead of just invoking the callback object directly (this emulates the product that does + // not serialize the callback object over). + options.ClientRpcTarget = null; + + // Creates service instance and connects it to the pipe. + // We don't need to store the instance anywhere. + _ = _services.CreateBrokeredService(descriptor, pipePair.Item1, options); + + clientConnection.StartListening(); + + return new ValueTask(clientConnection.ConstructRpcClient()); + } + } + private sealed class InProcRemoteServices { private readonly ServiceProvider _serviceProvider; - private readonly Dictionary> _creatorMap; + private readonly Dictionary> _inProcBrokeredServicesMap = new(); + private readonly Dictionary _remoteBrokeredServicesMap = new(); + private readonly Dictionary> _factoryMap = new(); + private readonly Dictionary _serviceNameMap = new(); + + public readonly IServiceBroker ServiceBroker; - public InProcRemoteServices(RemoteHostTestData testData) + public InProcRemoteServices(HostWorkspaceServices workspaceServices, RemoteHostTestData testData) { var remoteLogger = new TraceSource("inprocRemoteClient"); _serviceProvider = new ServiceProvider(remoteLogger, testData); - _creatorMap = new Dictionary>(); - - RegisterService(WellKnownServiceHubService.RemoteHost, (s, p) => new RemoteHostService(s, p)); - RegisterService(WellKnownServiceHubService.CodeAnalysis, (s, p) => new CodeAnalysisService(s, p)); - RegisterService(WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine, (s, p) => new RemoteSymbolSearchUpdateEngine(s, p)); - RegisterService(WellKnownServiceHubService.RemoteDesignerAttributeService, (s, p) => new RemoteDesignerAttributeService(s, p)); - RegisterService(WellKnownServiceHubService.RemoteProjectTelemetryService, (s, p) => new RemoteProjectTelemetryService(s, p)); - RegisterService(WellKnownServiceHubService.RemoteTodoCommentsService, (s, p) => new RemoteTodoCommentsService(s, p)); - RegisterService(WellKnownServiceHubService.LanguageServer, (s, p) => new LanguageServer(s, p)); + + ServiceBroker = new InProcServiceBroker(this); + + RegisterService(WellKnownServiceHubService.RemoteHost, (s, p, o) => new RemoteHostService(s, p)); + RegisterService(WellKnownServiceHubService.CodeAnalysis, (s, p, o) => new CodeAnalysisService(s, p)); + RegisterService(WellKnownServiceHubService.RemoteSymbolSearchUpdateEngine, (s, p, o) => new RemoteSymbolSearchUpdateEngine(s, p)); + RegisterInProcBrokeredService(SolutionAssetProvider.ServiceDescriptor, () => new SolutionAssetProvider(workspaceServices)); + RegisterRemoteBrokeredService(new RemoteDesignerAttributeService.Factory()); + RegisterRemoteBrokeredService(new RemoteProjectTelemetryService.Factory()); + RegisterRemoteBrokeredService(new RemoteTodoCommentsService.Factory()); + RegisterRemoteBrokeredService(new RemoteDiagnosticAnalyzerService.Factory()); + RegisterRemoteBrokeredService(new RemoteSemanticClassificationService.Factory()); + RegisterRemoteBrokeredService(new RemoteSemanticClassificationCacheService.Factory()); + RegisterService(WellKnownServiceHubService.LanguageServer, (s, p, o) => new LanguageServer(s, p)); } public RemoteHostTestData TestData => _serviceProvider.TestData; - public void RegisterService(RemoteServiceName name, Func serviceCreator) - => _creatorMap.Add(name, serviceCreator); + public void RegisterService(RemoteServiceName name, Func serviceFactory) + { + _factoryMap.Add(name, serviceFactory); + _serviceNameMap.Add(name.ToString(isRemoteHost64Bit: IntPtr.Size == 8), name.WellKnownService); + } public Task RequestServiceAsync(RemoteServiceName serviceName) { - if (_creatorMap.TryGetValue(serviceName, out var creator)) + var factory = _factoryMap[serviceName]; + var streams = FullDuplexStream.CreatePair(); + return Task.FromResult(new WrappedStream(factory(streams.Item1, _serviceProvider, default), streams.Item2)); + } + + public void RegisterInProcBrokeredService(ServiceDescriptor serviceDescriptor, Func serviceFactory) + { + _inProcBrokeredServicesMap.Add(serviceDescriptor.Moniker, serviceFactory); + } + + public void RegisterRemoteBrokeredService(BrokeredServiceBase.IFactory serviceFactory) + { + var moniker = ServiceDescriptors.GetServiceDescriptor(serviceFactory.ServiceType, isRemoteHost64Bit: IntPtr.Size == 8).Moniker; + _remoteBrokeredServicesMap.Add(moniker, serviceFactory); + } + + public object CreateBrokeredService(ServiceRpcDescriptor descriptor, IDuplexPipe pipe, ServiceActivationOptions options) + { + if (_inProcBrokeredServicesMap.TryGetValue(descriptor.Moniker, out var inProcFactory)) + { + // This code is similar to service creation implemented in BrokeredServiceBase.FactoryBase. + // Currently don't support callback creation as we don't have in-proc service with callbacks yet. + Contract.ThrowIfFalse(descriptor.ClientInterface == null); + + var serviceConnection = descriptor.ConstructRpcConnection(pipe); + var service = inProcFactory(); + + serviceConnection.AddLocalRpcTarget(service); + serviceConnection.StartListening(); + + return service; + } + + if (_remoteBrokeredServicesMap.TryGetValue(descriptor.Moniker, out var remoteFactory)) { - var tuple = FullDuplexStream.CreateStreams(); - return Task.FromResult(new WrappedStream(creator(tuple.Item1, _serviceProvider), tuple.Item2)); + return remoteFactory.Create(pipe, _serviceProvider, options, ServiceBroker); } - throw ExceptionUtilities.UnexpectedValue(serviceName); + throw ExceptionUtilities.UnexpectedValue(descriptor.Moniker); } private sealed class WrappedStream : Stream diff --git a/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj b/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj index d1d303914dde6..96db67ac6f827 100644 --- a/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj +++ b/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj @@ -34,7 +34,6 @@ - diff --git a/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs b/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs new file mode 100644 index 0000000000000..58787a154dccc --- /dev/null +++ b/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs @@ -0,0 +1,161 @@ +// 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.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Extensions; +using Nerdbank.Streams; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal sealed class BrokeredServiceConnection : RemoteServiceConnection + where TService : class + { + private readonly IErrorReportingService _errorReportingService; + private readonly SolutionAssetStorage _solutionAssetStorage; + private readonly TService _service; + + public BrokeredServiceConnection(TService service, SolutionAssetStorage solutionAssetStorage, IErrorReportingService errorReportingService) + { + _errorReportingService = errorReportingService; + _solutionAssetStorage = solutionAssetStorage; + _service = service; + } + + public override void Dispose() + => (_service as IDisposable)?.Dispose(); + + // without solution + + public override async ValueTask TryInvokeAsync(Func invocation, CancellationToken cancellationToken) + { + try + { + await invocation(_service, cancellationToken).ConfigureAwait(false); + return true; + } + catch (Exception exception) when (FatalError.ReportWithoutCrashUnlessCanceled(exception, cancellationToken)) + { + OnUnexpectedException(exception, cancellationToken); + return false; + } + } + + public override async ValueTask> TryInvokeAsync(Func> invocation, CancellationToken cancellationToken) + { + try + { + return await invocation(_service, cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) when (FatalError.ReportWithoutCrashUnlessCanceled(exception, cancellationToken)) + { + OnUnexpectedException(exception, cancellationToken); + return default; + } + } + + public override async ValueTask> TryInvokeAsync( + Func invocation, + Func> reader, + CancellationToken cancellationToken) + { + try + { + return await InvokeStreamingServiceAsync(_service, invocation, reader, cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) when (FatalError.ReportWithoutCrashUnlessCanceled(exception, cancellationToken)) + { + OnUnexpectedException(exception, cancellationToken); + return default; + } + } + + // with solution + + public override async ValueTask TryInvokeAsync(Solution solution, Func invocation, CancellationToken cancellationToken) + { + try + { + using var scope = await _solutionAssetStorage.StoreAssetsAsync(solution, cancellationToken).ConfigureAwait(false); + await invocation(_service, scope.SolutionInfo, cancellationToken).ConfigureAwait(false); + return true; + } + catch (Exception exception) when (FatalError.ReportWithoutCrashUnlessCanceled(exception, cancellationToken)) + { + OnUnexpectedException(exception, cancellationToken); + return false; + } + } + + public override async ValueTask> TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken) + { + try + { + using var scope = await _solutionAssetStorage.StoreAssetsAsync(solution, cancellationToken).ConfigureAwait(false); + return await invocation(_service, scope.SolutionInfo, cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) when (FatalError.ReportWithoutCrashUnlessCanceled(exception, cancellationToken)) + { + OnUnexpectedException(exception, cancellationToken); + return default; + } + } + + public override async ValueTask> TryInvokeAsync( + Solution solution, + Func invocation, + Func> reader, + CancellationToken cancellationToken) + { + try + { + using var scope = await _solutionAssetStorage.StoreAssetsAsync(solution, cancellationToken).ConfigureAwait(false); + return await InvokeStreamingServiceAsync( + _service, + (service, stream, cancellationToken) => invocation(service, scope.SolutionInfo, stream, cancellationToken), + reader, + cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) when (FatalError.ReportWithoutCrashUnlessCanceled(exception, cancellationToken)) + { + OnUnexpectedException(exception, cancellationToken); + return default; + } + } + + internal static async ValueTask InvokeStreamingServiceAsync( + TService service, + Func invocation, + Func> reader, + CancellationToken cancellationToken) + { + // The reader should close the client stream, the writer will close the server stream. + // See https://github.com/microsoft/vs-streamjsonrpc/blob/master/doc/oob_streams.md + var (clientStream, serverStream) = FullDuplexStream.CreatePair(); + + var writerTask = invocation(service, serverStream, cancellationToken).AsTask(); + var readerTask = reader(clientStream, cancellationToken).AsTask(); + await Task.WhenAll(writerTask, readerTask).ConfigureAwait(false); + + return readerTask.Result; + } + + private void OnUnexpectedException(Exception exception, CancellationToken cancellationToken) + { + // Remote call may fail with different exception even when our cancellation token is signaled + // (e.g. on shutdown if the connection is dropped): + cancellationToken.ThrowIfCancellationRequested(); + + // TODO: better message depending on the exception (https://github.com/dotnet/roslyn/issues/40476): + // "Feature xyz is currently unavailable due to network issues" (connection exceptions) + // "Feature xyz is currently unavailable due to an internal error [Details]" (exception is RemoteInvocationException, serialization issues) + // "Feature xyz is currently unavailable" (connection exceptions during shutdown cancellation when cancellationToken is not signalled) + + _errorReportingService?.ShowRemoteHostCrashedErrorInfo(exception); + } + } +} diff --git a/src/Workspaces/Remote/Core/IRemoteHostService.cs b/src/Workspaces/Remote/Core/IRemoteHostService.cs index 413436f9a25fa..f05329a4ef310 100644 --- a/src/Workspaces/Remote/Core/IRemoteHostService.cs +++ b/src/Workspaces/Remote/Core/IRemoteHostService.cs @@ -15,7 +15,7 @@ internal interface IRemoteHostService { void InitializeGlobalState(int uiCultureLCID, int cultureLCID, CancellationToken cancellationToken); - void InitializeTelemetrySession(string host, string serializedSession, CancellationToken cancellationToken); + void InitializeTelemetrySession(int hostProcessId, string serializedSession, CancellationToken cancellationToken); /// /// This is only for debugging diff --git a/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs b/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs new file mode 100644 index 0000000000000..2c08dcf910f60 --- /dev/null +++ b/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs @@ -0,0 +1,27 @@ +// 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.Collections.Immutable; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Remote +{ + /// + /// Brokered service. + /// + internal interface ISolutionAssetProvider + { + /// + /// Streams serialized assets into the given stream. + /// + ValueTask GetAssetsAsync(Stream outputStream, int scopeId, Checksum[] checksums, CancellationToken cancellationToken); + + // TODO: remove (https://github.com/dotnet/roslyn/issues/43477) + ValueTask IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/Remote/Core/RemoteCallback.cs b/src/Workspaces/Remote/Core/RemoteCallback.cs new file mode 100644 index 0000000000000..93a1569025f8b --- /dev/null +++ b/src/Workspaces/Remote/Core/RemoteCallback.cs @@ -0,0 +1,126 @@ +// 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.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; +using Nerdbank.Streams; +using Newtonsoft.Json; +using Roslyn.Utilities; +using StreamJsonRpc; + +namespace Microsoft.CodeAnalysis.Remote +{ + /// + /// Wraps calls from a remote brokered service back to the client or to an in-proc brokered service. + /// The purpose of this type is to handle exceptions thrown by the underlying remoting infrastructure + /// in manner that's compatible with our exception handling policies. + /// + /// TODO: This wrapper might not be needed once https://github.com/microsoft/vs-streamjsonrpc/issues/246 is fixed. + /// + internal readonly struct RemoteCallback + where T : class + { + private readonly T _callback; + + public readonly CancellationTokenSource ClientDisconnectedSource; + + public RemoteCallback(T callback, CancellationTokenSource clientDisconnectedSource) + { + _callback = callback; + ClientDisconnectedSource = clientDisconnectedSource; + } + + public async ValueTask InvokeAsync(Func invocation, CancellationToken cancellationToken) + { + try + { + await invocation(_callback, cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) when (ReportUnexpectedException(exception, cancellationToken)) + { + throw OnUnexpectedException(cancellationToken); + } + } + + public async ValueTask InvokeAsync(Func> invocation, CancellationToken cancellationToken) + { + try + { + return await invocation(_callback, cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) when (ReportUnexpectedException(exception, cancellationToken)) + { + throw OnUnexpectedException(cancellationToken); + } + } + + /// + /// Invokes a remote API that streams results back to the caller. + /// + public async ValueTask InvokeAsync( + Func invocation, + Func> reader, + CancellationToken cancellationToken) + { + try + { + return await BrokeredServiceConnection.InvokeStreamingServiceAsync(_callback, invocation, reader, cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) when (ReportUnexpectedException(exception, cancellationToken)) + { + throw OnUnexpectedException(cancellationToken); + } + } + + // TODO: https://github.com/microsoft/vs-streamjsonrpc/issues/246 + // + // We need to get to a state when remote calls can only throw 4 types of exceptions that correspond to + // 1) Connection issue (connection dropped for any reason) + // 2) Serialization issue - bug in serialization of arguments (types are not serializable, etc.) + // 3) Remote exception - an exception was thrown by the callee + // 4) Cancelation + // When a connection is dropped and CancelLocallyInvokedMethodsWhenConnectionIsClosed is set the connection dropped exception [1] should not be thrown. + // Instead a the cancellation token should be signaled and OperationCancelledException should be thrown ([4]). + // + // Until the above issue in JSON-RPC is fixed we do a best guess on what the issue is. + + private bool ReportUnexpectedException(Exception exception, CancellationToken cancellationToken) + { + if (exception is RemoteInvocationException or JsonException) + { + // indicates bug on client side or in serialization, propagate the exception + return FatalError.ReportWithoutCrashAndPropagate(exception); + } + + if (cancellationToken.IsCancellationRequested) + { + // If cancelation is requested and we see a different exception the handler will throw OperationCancelledException. + return exception is not OperationCanceledException; + } + + // We assume that any other exception indicates lost connection (it might not), + // cancel any ongoing work since the client can't receive the results. + // This should be handled by JSON-RPC but it's not guaranteed due to https://github.com/microsoft/vs-streamjsonrpc/issues/246. + ClientDisconnectedSource.Cancel(); + + // catch the exception, cancellation exception will be thrown by the handler. + return true; + } + + private static Exception OnUnexpectedException(CancellationToken cancellationToken) + { + // Remote call may fail with different exception even when our cancellation token is signaled + // (e.g. on shutdown if the connection is dropped): + cancellationToken.ThrowIfCancellationRequested(); + + // If this is hit the cancellation token passed to the service implementation did not use the correct token. + return ExceptionUtilities.Unreachable; + } + } +} diff --git a/src/Workspaces/Remote/Core/RemoteEndPoint.cs b/src/Workspaces/Remote/Core/RemoteEndPoint.cs index 2ecc51b7a1f7f..86ab1ec0f0d13 100644 --- a/src/Workspaces/Remote/Core/RemoteEndPoint.cs +++ b/src/Workspaces/Remote/Core/RemoteEndPoint.cs @@ -58,9 +58,6 @@ public RemoteEndPoint(Stream stream, TraceSource logger, object? incomingCallTar var jsonFormatter = new JsonMessageFormatter(); - // disable interpreting of strings as DateTime during deserialization: - jsonFormatter.JsonSerializer.DateParseHandling = DateParseHandling.None; - if (jsonConverters != null) { jsonFormatter.JsonSerializer.Converters.AddRange(jsonConverters); @@ -390,7 +387,7 @@ private void LogDisconnectInfo(JsonRpcDisconnectedEventArgs? e) { if (e != null) { - LogError($@"Stream disconnected unexpectedly: {e.Reason}, '{e.Description}', LastMessage: {e.LastMessage}, Exception: {e.Exception?.Message}"); + LogError($@"Stream disconnected unexpectedly: {e.Reason}, '{e.Description}', Exception: {e.Exception?.Message}"); } } diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 56f5205afcaa2..86bde237f2138 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -60,7 +60,7 @@ static void WriteAsset(ObjectWriter writer, ISerializerService serializer, Check } } - public static ImmutableArray<(Checksum, object)> ReadData(Stream stream, int scopeId, ISet checksums, ISerializerService serializerService, CancellationToken cancellationToken) + public static ValueTask> ReadDataAsync(Stream stream, int scopeId, ISet checksums, ISerializerService serializerService, CancellationToken cancellationToken) { using var _ = ArrayBuilder<(Checksum, object)>.GetInstance(out var results); @@ -89,7 +89,7 @@ static void WriteAsset(ObjectWriter writer, ISerializerService serializer, Check results.Add((responseChecksum, result)); } - return results.ToImmutable(); + return new(results.ToImmutable()); } } } diff --git a/src/Workspaces/Remote/Core/ServiceDescriptor.cs b/src/Workspaces/Remote/Core/ServiceDescriptor.cs new file mode 100644 index 0000000000000..c6f23e433e63f --- /dev/null +++ b/src/Workspaces/Remote/Core/ServiceDescriptor.cs @@ -0,0 +1,71 @@ +// 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 Microsoft.ServiceHub.Framework; +using Nerdbank.Streams; +using StreamJsonRpc; + +namespace Microsoft.CodeAnalysis.Remote +{ + /// + /// Describes Roslyn remote brokered service. + /// Adds Roslyn specific JSON converters and RPC settings to the default implementation. + /// + internal sealed class ServiceDescriptor : ServiceJsonRpcDescriptor + { + private static readonly JsonRpcTargetOptions s_jsonRpcTargetOptions = new JsonRpcTargetOptions() + { + // Do not allow JSON-RPC to automatically subscribe to events and remote their calls. + NotifyClientOfEvents = false, + + // Only allow public methods (may be on internal types) to be invoked remotely. + AllowNonPublicInvocation = false + }; + + // Enables remote APIs to pass Stream as parameter. + private static readonly MultiplexingStream.Options s_multiplexingStreamOptions = new MultiplexingStream.Options + { + ProtocolMajorVersion = 3 + }.GetFrozenCopy(); + + private ServiceDescriptor(ServiceMoniker serviceMoniker, Type? clientInterface) + : base(serviceMoniker, clientInterface, Formatters.UTF8, MessageDelimiters.HttpLikeHeaders, s_multiplexingStreamOptions) + { + } + + private ServiceDescriptor(ServiceDescriptor copyFrom) + : base(copyFrom) + { + } + + public static ServiceDescriptor CreateRemoteServiceDescriptor(string serviceName, Type? clientInterface) + => new ServiceDescriptor(new ServiceMoniker(serviceName), clientInterface); + + public static ServiceDescriptor CreateInProcServiceDescriptor(string serviceName) + => new ServiceDescriptor(new ServiceMoniker(serviceName), clientInterface: null); + + protected override ServiceRpcDescriptor Clone() + => new ServiceDescriptor(this); + + protected override IJsonRpcMessageFormatter CreateFormatter() + => ConfigureFormatter((JsonMessageFormatter)base.CreateFormatter()); + + internal static JsonMessageFormatter ConfigureFormatter(JsonMessageFormatter formatter) + { + formatter.JsonSerializer.Converters.Add(AggregateJsonConverter.Instance); + return formatter; + } + + protected override JsonRpcConnection CreateConnection(JsonRpc jsonRpc) + { + jsonRpc.CancelLocallyInvokedMethodsWhenConnectionIsClosed = true; + var connection = base.CreateConnection(jsonRpc); + connection.LocalRpcTargetOptions = s_jsonRpcTargetOptions; + return connection; + } + } +} diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs new file mode 100644 index 0000000000000..cb77027fec81c --- /dev/null +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -0,0 +1,69 @@ +// 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.Collections.Immutable; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.DesignerAttribute; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ProjectTelemetry; +using Microsoft.CodeAnalysis.TodoComments; +using Microsoft.ServiceHub.Framework; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote +{ + /// + /// Service descriptors of brokered Roslyn ServiceHub services. + /// + internal static class ServiceDescriptors + { + /// + /// Brokered services must be defined in Microsoft.VisualStudio service namespace in order to be considered first party. + /// + internal const string ServiceNamePrefix = "Microsoft.VisualStudio.LanguageServices."; + + private const string InterfaceNamePrefix = "IRemote"; + private const string InterfaceNameSuffix = "Service"; + + internal static readonly ImmutableDictionary Descriptors = ImmutableDictionary.CreateRange(new[] + { + CreateDescriptors(typeof(IRemoteTodoCommentsService), callbackInterface: typeof(ITodoCommentsListener)), + CreateDescriptors(typeof(IRemoteDesignerAttributeService), callbackInterface: typeof(IDesignerAttributeListener)), + CreateDescriptors(typeof(IRemoteProjectTelemetryService), callbackInterface: typeof(IProjectTelemetryListener)), + CreateDescriptors(typeof(IRemoteDiagnosticAnalyzerService)), + CreateDescriptors(typeof(IRemoteSemanticClassificationService)), + CreateDescriptors(typeof(IRemoteSemanticClassificationCacheService)), + }); + + private static string GetServiceName(Type serviceInterface) + { + Contract.ThrowIfFalse(serviceInterface.IsInterface); + var interfaceName = serviceInterface.Name; + Contract.ThrowIfFalse(interfaceName.StartsWith(InterfaceNamePrefix, StringComparison.Ordinal)); + Contract.ThrowIfFalse(interfaceName.EndsWith(InterfaceNameSuffix, StringComparison.Ordinal)); + + return ServiceNamePrefix + interfaceName.Substring(InterfaceNamePrefix.Length, interfaceName.Length - InterfaceNamePrefix.Length - InterfaceNameSuffix.Length); + } + + private static KeyValuePair CreateDescriptors(Type serviceInterface, Type? callbackInterface = null) + { + Contract.ThrowIfFalse(callbackInterface == null || callbackInterface.IsInterface); + + var serviceName = GetServiceName(serviceInterface); + var descriptor32 = ServiceDescriptor.CreateRemoteServiceDescriptor(serviceName, callbackInterface); + var descriptor64 = ServiceDescriptor.CreateRemoteServiceDescriptor(serviceName + RemoteServiceName.Suffix64, callbackInterface); + return new(serviceInterface, (descriptor32, descriptor64)); + } + + public static ServiceRpcDescriptor GetServiceDescriptor(Type serviceType, bool isRemoteHost64Bit) + { + var (descriptor32, descriptor64) = Descriptors[serviceType]; + return isRemoteHost64Bit ? descriptor64 : descriptor32; + } + } +} diff --git a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs index e3432ff785003..c6e5fd8ffd2f4 100644 --- a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs +++ b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.ServiceHub.Client; +using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using StreamJsonRpc; @@ -33,14 +34,17 @@ internal sealed partial class ServiceHubRemoteHostClient : RemoteHostClient, IRe private readonly ISerializerService _serializer; private readonly RemoteEndPoint _endPoint; private readonly HubClient _hubClient; - private readonly HostGroup _hostGroup; + private readonly IServiceBroker _serviceBroker; + private readonly ServiceBrokerClient _serviceBrokerClient; + private readonly IErrorReportingService? _errorReportingService; private readonly ConnectionPools? _connectionPools; private ServiceHubRemoteHostClient( HostWorkspaceServices services, + IServiceBroker serviceBroker, + ServiceBrokerClient serviceBrokerClient, HubClient hubClient, - HostGroup hostGroup, Stream stream) { _connectionPools = new ConnectionPools( @@ -51,8 +55,9 @@ private ServiceHubRemoteHostClient( services.GetService()?.RegisterUnexpectedExceptionLogger(hubClient.Logger); _services = services; + _serviceBroker = serviceBroker; + _serviceBrokerClient = serviceBrokerClient; _hubClient = hubClient; - _hostGroup = hostGroup; _endPoint = new RemoteEndPoint(stream, hubClient.Logger, incomingCallTarget: this); _endPoint.Disconnected += OnDisconnected; @@ -61,26 +66,28 @@ private ServiceHubRemoteHostClient( _assetStorage = services.GetRequiredService().AssetStorage; _serializer = services.GetRequiredService(); + _errorReportingService = services.GetService(); } private void OnUnexpectedExceptionThrown(Exception unexpectedException) - => _services.GetService()?.ShowRemoteHostCrashedErrorInfo(unexpectedException); + => _errorReportingService?.ShowRemoteHostCrashedErrorInfo(unexpectedException); - public static async Task CreateAsync(HostWorkspaceServices services, CancellationToken cancellationToken) + public static async Task CreateAsync(HostWorkspaceServices services, IServiceBroker serviceBroker, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.ServiceHubRemoteHostClient_CreateAsync, KeyValueLogMessage.NoProperty, cancellationToken)) { 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 = $"VS ({Process.GetCurrentProcess().Id}) ({Guid.NewGuid()})"; +#pragma warning disable ISB001 // Dispose of proxies +#pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed + var serviceBrokerClient = new ServiceBrokerClient(serviceBroker); +#pragma warning restore - var hostGroup = new HostGroup(clientId); var hubClient = new HubClient("ManagedLanguage.IDE.RemoteHostClient"); - var remoteHostStream = await RequestServiceAsync(services, hubClient, WellKnownServiceHubService.RemoteHost, hostGroup, cancellationToken).ConfigureAwait(false); + var remoteHostStream = await RequestServiceAsync(services, hubClient, WellKnownServiceHubService.RemoteHost, cancellationToken).ConfigureAwait(false); - var client = new ServiceHubRemoteHostClient(services, hubClient, hostGroup, remoteHostStream); + var client = new ServiceHubRemoteHostClient(services, serviceBroker, serviceBrokerClient, hubClient, remoteHostStream); var uiCultureLCID = CultureInfo.CurrentUICulture.LCID; var cultureLCID = CultureInfo.CurrentCulture.LCID; @@ -100,7 +107,6 @@ public static async Task RequestServiceAsync( HostWorkspaceServices services, HubClient client, RemoteServiceName serviceName, - HostGroup hostGroup, CancellationToken cancellationToken) { var is64bit = RemoteHostOptions.IsServiceHubProcess64Bit(services); @@ -108,7 +114,7 @@ public static async Task RequestServiceAsync( // 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 }; + var descriptor = new ServiceHub.Client.ServiceDescriptor(serviceName.ToString(is64bit)); try { return await client.RequestServiceAsync(descriptor, cancellationToken).ConfigureAwait(false); @@ -142,9 +148,32 @@ static bool ReportNonFatalWatson(Exception e, CancellationToken cancellationToke } } - public HostGroup HostGroup => _hostGroup; + public override async ValueTask> CreateConnectionAsync(object? callbackTarget, CancellationToken cancellationToken) + { + try + { + var options = default(ServiceActivationOptions); + + if (callbackTarget is not null) + { + options.ClientRpcTarget = callbackTarget; + } + + var descriptor = ServiceDescriptors.GetServiceDescriptor(typeof(T), RemoteHostOptions.IsServiceHubProcess64Bit(_services)); + +#pragma warning disable ISB001 // Dispose of proxies - BrokeredServiceConnection takes care of disposal + var proxy = await _serviceBroker.GetProxyAsync(descriptor, options, cancellationToken).ConfigureAwait(false); +#pragma warning restore - public override string ClientId => _hostGroup.Id; + Contract.ThrowIfNull(proxy, $"Brokered service not found: {descriptor.Moniker.Name}"); + + return new BrokeredServiceConnection(proxy, _assetStorage, _errorReportingService); + } + catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceledAndPropagate(e)) + { + throw ExceptionUtilities.Unreachable; + } + } public override Task CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken) { @@ -162,7 +191,7 @@ public override Task CreateConnectionAsync(RemoteServic private async Task CreateConnectionImplAsync(RemoteServiceName serviceName, object? callbackTarget, IPooledConnectionReclamation? poolReclamation, CancellationToken cancellationToken) { - var serviceStream = await RequestServiceAsync(_services, _hubClient, serviceName, _hostGroup, cancellationToken).ConfigureAwait(false); + var serviceStream = await RequestServiceAsync(_services, _hubClient, serviceName, cancellationToken).ConfigureAwait(false); return new JsonRpcConnection(_services, _hubClient.Logger, callbackTarget, serviceStream, poolReclamation); } @@ -177,6 +206,8 @@ public override void Dispose() _services.GetService()?.UnregisterUnexpectedExceptionLogger(_hubClient.Logger); _hubClient.Dispose(); + _serviceBrokerClient.Dispose(); + base.Dispose(); } diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs new file mode 100644 index 0000000000000..2677eca2d6262 --- /dev/null +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -0,0 +1,51 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Experiments; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Serialization; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal sealed class SolutionAssetProvider : ISolutionAssetProvider + { + public const string ServiceName = ServiceDescriptors.ServiceNamePrefix + "SolutionAssetProvider"; + + internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceName); + + private readonly HostWorkspaceServices _services; + + public SolutionAssetProvider(HostWorkspaceServices services) + { + _services = services; + } + + public ValueTask GetAssetsAsync(Stream outputStream, int scopeId, Checksum[] checksums, CancellationToken cancellationToken) + { + // Complete RPC right away so the client can start reading from the stream. + // The fire-and forget task starts writing to the output stream and the client will read it until it reads all expected data. + _ = Task.Run(async () => + { + using var writer = new ObjectWriter(outputStream, leaveOpen: false, cancellationToken); + + var assetStorage = _services.GetRequiredService().AssetStorage; + var serializer = _services.GetRequiredService(); + await RemoteHostAssetSerialization.WriteDataAsync(writer, assetStorage, serializer, scopeId, checksums, cancellationToken).ConfigureAwait(false); + }, cancellationToken); + + return default; + } + + public ValueTask IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken) + => new ValueTask(_services.GetRequiredService().IsExperimentEnabled(experimentName)); + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs index 332d1e24dda11..6401edc33c6d9 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs @@ -17,9 +17,9 @@ namespace Microsoft.CodeAnalysis.Remote /// internal interface IAssetSource { - Task> GetAssetsAsync(int scopeId, ISet checksums, ISerializerService serializerService, CancellationToken cancellationToken); + ValueTask> GetAssetsAsync(int scopeId, ISet checksums, ISerializerService serializerService, CancellationToken cancellationToken); // TODO: remove (https://github.com/dotnet/roslyn/issues/43477) - Task IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken); + ValueTask IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs index ac77e32196c14..9be9579d5d119 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Immutable; using System.Reflection; -using System.Threading; using Microsoft.CodeAnalysis.Host.Mef; using Roslyn.Utilities; diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs new file mode 100644 index 0000000000000..9efb43d94450e --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -0,0 +1,50 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Serialization; +using Microsoft.ServiceHub.Framework; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal sealed class SolutionAssetSource : IAssetSource + { + private readonly ServiceBrokerClient _client; + private readonly CancellationTokenSource _clientDisconnectedSource; + + public SolutionAssetSource(ServiceBrokerClient client, CancellationTokenSource clientDisconnectedSource) + { + _client = client; + _clientDisconnectedSource = clientDisconnectedSource; + } + + public async ValueTask> GetAssetsAsync(int scopeId, ISet checksums, ISerializerService serializerService, CancellationToken cancellationToken) + { + using var provider = await _client.GetProxyAsync(SolutionAssetProvider.ServiceDescriptor, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(provider.Proxy); + + return await new RemoteCallback(provider.Proxy, _clientDisconnectedSource).InvokeAsync( + (proxy, stream, cancellationToken) => proxy.GetAssetsAsync(stream, scopeId, checksums.ToArray(), cancellationToken), + (stream, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(stream, scopeId, checksums, serializerService, cancellationToken), + cancellationToken).ConfigureAwait(false); + } + + public async ValueTask IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken) + { + using var provider = await _client.GetProxyAsync(SolutionAssetProvider.ServiceDescriptor, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(provider.Proxy); + + return await new RemoteCallback(provider.Proxy, _clientDisconnectedSource).InvokeAsync( + (self, cancellationToken) => provider.Proxy.IsExperimentEnabledAsync(experimentName, cancellationToken), + cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs new file mode 100644 index 0000000000000..7db97dd45a5ed --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs @@ -0,0 +1,101 @@ +// 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.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.ServiceHub.Framework; +using Microsoft.ServiceHub.Framework.Services; +using Nerdbank.Streams; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal abstract partial class BrokeredServiceBase + { + internal interface IFactory + { + object Create(IDuplexPipe pipe, IServiceProvider hostProvidedServices, ServiceActivationOptions serviceActivationOptions, IServiceBroker serviceBroker); + Type ServiceType { get; } + } + + internal abstract class FactoryBase : IServiceHubServiceFactory, IFactory + where TService : class + { + protected abstract TService CreateService(in ServiceConstructionArguments arguments); + + protected virtual TService CreateService( + in ServiceConstructionArguments arguments, + ServiceRpcDescriptor descriptor, + ServiceRpcDescriptor.RpcConnection serverConnection, + object? clientRpcTarget) + => CreateService(arguments); + + public Task CreateAsync( + Stream stream, + IServiceProvider hostProvidedServices, + ServiceActivationOptions serviceActivationOptions, + IServiceBroker serviceBroker, + AuthorizationServiceClient? authorizationServiceClient) + { + // Dispose the AuthorizationServiceClient since we won't be using it + authorizationServiceClient?.Dispose(); + + return Task.FromResult((object)Create( + stream.UsePipe(), + hostProvidedServices, + serviceActivationOptions, + serviceBroker)); + } + + object IFactory.Create(IDuplexPipe pipe, IServiceProvider hostProvidedServices, ServiceActivationOptions serviceActivationOptions, IServiceBroker serviceBroker) + => Create(pipe, hostProvidedServices, serviceActivationOptions, serviceBroker); + + Type IFactory.ServiceType => typeof(TService); + + internal TService Create( + IDuplexPipe pipe, + IServiceProvider hostProvidedServices, + ServiceActivationOptions serviceActivationOptions, + IServiceBroker serviceBroker) + { + var descriptor = ServiceDescriptors.GetServiceDescriptor(typeof(TService), isRemoteHost64Bit: IntPtr.Size == 8); + var serverConnection = descriptor.ConstructRpcConnection(pipe); + + var args = new ServiceConstructionArguments(hostProvidedServices, serviceBroker, new CancellationTokenSource()); + var service = CreateService(args, descriptor, serverConnection, serviceActivationOptions.ClientRpcTarget); + + serverConnection.AddLocalRpcTarget(service); + serverConnection.StartListening(); + + return service; + } + } + + internal abstract class FactoryBase : FactoryBase + where TService : class + where TCallback : class + { + protected abstract TService CreateService(in ServiceConstructionArguments arguments, RemoteCallback callback); + + protected sealed override TService CreateService(in ServiceConstructionArguments arguments) + => throw ExceptionUtilities.Unreachable; + + protected sealed override TService CreateService( + in ServiceConstructionArguments arguments, + ServiceRpcDescriptor descriptor, + ServiceRpcDescriptor.RpcConnection serverConnection, + object? clientRpcTarget) + { + Contract.ThrowIfNull(descriptor.ClientInterface); + var callback = (TCallback)(clientRpcTarget ?? serverConnection.ConstructRpcClient(descriptor.ClientInterface)); + return CreateService(arguments, new RemoteCallback(callback, arguments.ClientDisconnectedSource)); + } + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.ServiceConstructionArguments.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.ServiceConstructionArguments.cs new file mode 100644 index 0000000000000..3569393b81742 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.ServiceConstructionArguments.cs @@ -0,0 +1,29 @@ +// 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.Threading; +using Microsoft.ServiceHub.Framework; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal abstract partial class BrokeredServiceBase + { + internal readonly struct ServiceConstructionArguments + { + public readonly IServiceProvider ServiceProvider; + public readonly IServiceBroker ServiceBroker; + public readonly CancellationTokenSource ClientDisconnectedSource; + + public ServiceConstructionArguments(IServiceProvider serviceProvider, IServiceBroker serviceBroker, CancellationTokenSource clientDisconnectedSource) + { + ServiceProvider = serviceProvider; + ServiceBroker = serviceBroker; + ClientDisconnectedSource = clientDisconnectedSource; + } + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs new file mode 100644 index 0000000000000..782eab6164252 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs @@ -0,0 +1,110 @@ +// 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.Diagnostics; +using System.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.ServiceHub.Framework; +using Microsoft.ServiceHub.Framework.Services; +using Nerdbank.Streams; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote +{ + /// + /// Base type for Roslyn brokered services hosted in ServiceHub. + /// + internal abstract partial class BrokeredServiceBase : IDisposable + { + private readonly TraceSource _logger; + protected readonly RemoteWorkspaceManager WorkspaceManager; + + protected readonly SolutionAssetSource SolutionAssetSource; + protected readonly CancellationTokenSource ClientDisconnectedSource; + protected readonly ServiceBrokerClient ServiceBrokerClient; + + // test data are only available when running tests: + internal readonly RemoteHostTestData? TestData; + + static BrokeredServiceBase() + { + // Use a TraceListener hook to intercept assertion failures and report them through FatalError. + WatsonTraceListener.Install(); + } + + protected BrokeredServiceBase(in ServiceConstructionArguments arguments) + { + _logger = (TraceSource)arguments.ServiceProvider.GetService(typeof(TraceSource)); + + TestData = (RemoteHostTestData?)arguments.ServiceProvider.GetService(typeof(RemoteHostTestData)); + WorkspaceManager = TestData?.WorkspaceManager ?? RemoteWorkspaceManager.Default; + +#pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed + ServiceBrokerClient = new ServiceBrokerClient(arguments.ServiceBroker); +#pragma warning restore + + SolutionAssetSource = new SolutionAssetSource(ServiceBrokerClient, arguments.ClientDisconnectedSource); + ClientDisconnectedSource = arguments.ClientDisconnectedSource; + } + + public void Dispose() + => ServiceBrokerClient.Dispose(); + + public RemoteWorkspace GetWorkspace() + => WorkspaceManager.GetWorkspace(); + + protected void Log(TraceEventType errorType, string message) + => _logger.TraceEvent(errorType, 0, $"{GetType()}: {message}"); + + protected Task GetSolutionAsync(PinnedSolutionInfo solutionInfo, CancellationToken cancellationToken) + { + var workspace = GetWorkspace(); + var assetProvider = workspace.CreateAssetProvider(solutionInfo, WorkspaceManager.SolutionAssetCache, SolutionAssetSource); + return workspace.GetSolutionAsync(assetProvider, solutionInfo.SolutionChecksum, solutionInfo.FromPrimaryBranch, solutionInfo.WorkspaceVersion, cancellationToken); + } + + protected async ValueTask RunServiceAsync(Func> implementation, CancellationToken cancellationToken) + { + WorkspaceManager.SolutionAssetCache.UpdateLastActivityTime(); + using var _ = LinkToken(ref cancellationToken); + + try + { + return await implementation(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (FatalError.ReportWithoutCrashUnlessCanceledAndPropagate(ex, cancellationToken)) + { + throw ExceptionUtilities.Unreachable; + } + } + + protected async ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) + { + WorkspaceManager.SolutionAssetCache.UpdateLastActivityTime(); + using var _ = LinkToken(ref cancellationToken); + + try + { + await implementation(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (FatalError.ReportWithoutCrashUnlessCanceledAndPropagate(ex, cancellationToken)) + { + throw ExceptionUtilities.Unreachable; + } + } + + private CancellationTokenSource? LinkToken(ref CancellationToken cancellationToken) + { + var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ClientDisconnectedSource.Token); + cancellationToken = source.Token; + return source; + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService.cs b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService.cs index 600a70a4c4a35..07b304b534255 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService.cs @@ -4,20 +4,19 @@ using System; using System.IO; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.ServiceHub.Framework; +using Microsoft.ServiceHub.Framework.Services; namespace Microsoft.CodeAnalysis.Remote { // root level service for all Roslyn services internal partial class CodeAnalysisService : ServiceBase { - private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache; - public CodeAnalysisService(Stream stream, IServiceProvider serviceProvider) : base(serviceProvider, stream) { - _analyzerInfoCache = new DiagnosticAnalyzerInfoCache(); - StartService(); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_GlobalNotifications.cs b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_GlobalNotifications.cs index 962126e2eaca3..49b801e4d9b6d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_GlobalNotifications.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_GlobalNotifications.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Remote { - internal partial class CodeAnalysisService : ServiceBase, IRemoteGlobalNotificationDeliveryService + internal partial class CodeAnalysisService : IRemoteGlobalNotificationDeliveryService { /// /// Remote API. diff --git a/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeIncrementalAnalyzer.cs b/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeIncrementalAnalyzer.cs index f98a5fde47d91..195418a5d442c 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeIncrementalAnalyzer.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeIncrementalAnalyzer.cs @@ -5,6 +5,7 @@ #nullable enable using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.DesignerAttribute; @@ -16,28 +17,32 @@ internal sealed class RemoteDesignerAttributeIncrementalAnalyzer : AbstractDesig /// /// Channel back to VS to inform it of the designer attributes we discover. /// - private readonly RemoteEndPoint _endPoint; + private readonly RemoteCallback _callback; - public RemoteDesignerAttributeIncrementalAnalyzer(Workspace workspace, RemoteEndPoint endPoint) + public RemoteDesignerAttributeIncrementalAnalyzer(Workspace workspace, RemoteCallback callback) : base(workspace) { - _endPoint = endPoint; + _callback = callback; } - protected override Task ReportProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken) + protected override async ValueTask ReportProjectRemovedAsync(ProjectId projectId, CancellationToken cancellationToken) { - return _endPoint.InvokeAsync( - nameof(IDesignerAttributeListener.OnProjectRemovedAsync), - new object[] { projectId }, - cancellationToken); + // cancel whenever the analyzer runner cancels or the client disconnects and the request is canceled: + using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _callback.ClientDisconnectedSource.Token); + + await _callback.InvokeAsync( + (callback, cancellationToken) => callback.OnProjectRemovedAsync(projectId, cancellationToken), + linkedSource.Token).ConfigureAwait(false); } - protected override Task ReportDesignerAttributeDataAsync(List data, CancellationToken cancellationToken) + protected override async ValueTask ReportDesignerAttributeDataAsync(List data, CancellationToken cancellationToken) { - return _endPoint.InvokeAsync( - nameof(IDesignerAttributeListener.ReportDesignerAttributeDataAsync), - new object[] { data }, - cancellationToken); + // cancel whenever the analyzer runner cancels or the client disconnects and the request is canceled: + using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _callback.ClientDisconnectedSource.Token); + + await _callback.InvokeAsync( + (callback, cancellationToken) => callback.ReportDesignerAttributeDataAsync(data.ToImmutableArray(), cancellationToken), + linkedSource.Token).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeIncrementalAnalyzerProvider.cs b/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeIncrementalAnalyzerProvider.cs index b64be61337b1a..a20b96d5c77ea 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeIncrementalAnalyzerProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeIncrementalAnalyzerProvider.cs @@ -4,6 +4,7 @@ #nullable enable +using Microsoft.CodeAnalysis.DesignerAttribute; using Microsoft.CodeAnalysis.SolutionCrawler; namespace Microsoft.CodeAnalysis.Remote @@ -15,14 +16,14 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class RemoteDesignerAttributeIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider { - private readonly RemoteEndPoint _endPoint; + private readonly RemoteCallback _callback; - public RemoteDesignerAttributeIncrementalAnalyzerProvider(RemoteEndPoint endPoint) + public RemoteDesignerAttributeIncrementalAnalyzerProvider(RemoteCallback callback) { - _endPoint = endPoint; + _callback = callback; } public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) - => new RemoteDesignerAttributeIncrementalAnalyzer(workspace, _endPoint); + => new RemoteDesignerAttributeIncrementalAnalyzer(workspace, _callback); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeService.cs b/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeService.cs index 493281e2b4d7b..c5cadee93feb3 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DesignerAttribute/RemoteDesignerAttributeService.cs @@ -4,8 +4,6 @@ #nullable enable -using System; -using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.DesignerAttribute; @@ -13,21 +11,28 @@ namespace Microsoft.CodeAnalysis.Remote { - internal sealed class RemoteDesignerAttributeService : ServiceBase, IRemoteDesignerAttributeService + internal sealed class RemoteDesignerAttributeService : BrokeredServiceBase, IRemoteDesignerAttributeService { - public RemoteDesignerAttributeService( - Stream stream, IServiceProvider serviceProvider) - : base(serviceProvider, stream) + internal sealed class Factory : FactoryBase { - StartService(); + protected override IRemoteDesignerAttributeService CreateService(in ServiceConstructionArguments arguments, RemoteCallback callback) + => new RemoteDesignerAttributeService(arguments, callback); } - public Task StartScanningForDesignerAttributesAsync(CancellationToken cancellation) + private readonly RemoteCallback _callback; + + public RemoteDesignerAttributeService(in ServiceConstructionArguments arguments, RemoteCallback callback) + : base(arguments) + { + _callback = callback; + } + + public ValueTask StartScanningForDesignerAttributesAsync(CancellationToken cancellationToken) { - return RunServiceAsync(() => + return RunServiceAsync(cancellationToken => { var registrationService = GetWorkspace().Services.GetRequiredService(); - var analyzerProvider = new RemoteDesignerAttributeIncrementalAnalyzerProvider(this.EndPoint); + var analyzerProvider = new RemoteDesignerAttributeIncrementalAnalyzerProvider(_callback); registrationService.AddAnalyzerProvider( analyzerProvider, @@ -36,8 +41,8 @@ public Task StartScanningForDesignerAttributesAsync(CancellationToken cancellati highPriorityForActiveFile: false, workspaceKinds: WorkspaceKind.RemoteWorkspace)); - return Task.CompletedTask; - }, cancellation); + return default; + }, cancellationToken); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_Diagnostics.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs similarity index 59% rename from src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_Diagnostics.cs rename to src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs index 2da0f2207e544..7b7fae029c22a 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_Diagnostics.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs @@ -2,28 +2,45 @@ // 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.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Remote.Diagnostics; +using Roslyn.Utilities; using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger; namespace Microsoft.CodeAnalysis.Remote { - // root level service for all Roslyn services - internal partial class CodeAnalysisService : IRemoteDiagnosticAnalyzerService + internal sealed class RemoteDiagnosticAnalyzerService : BrokeredServiceBase, IRemoteDiagnosticAnalyzerService { + internal sealed class Factory : FactoryBase + { + protected override IRemoteDiagnosticAnalyzerService CreateService(in ServiceConstructionArguments arguments) + => new RemoteDiagnosticAnalyzerService(arguments); + } + + private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache = new(); + + public RemoteDiagnosticAnalyzerService(in ServiceConstructionArguments arguments) + : base(arguments) + { + } + /// /// Calculate dignostics. this works differently than other ones such as todo comments or designer attribute scanner /// since in proc and out of proc runs quite differently due to concurrency and due to possible amount of data /// that needs to pass through between processes /// - public Task CalculateDiagnosticsAsync(PinnedSolutionInfo solutionInfo, DiagnosticArguments arguments, string pipeName, CancellationToken cancellationToken) + public ValueTask CalculateDiagnosticsAsync(PinnedSolutionInfo solutionInfo, DiagnosticArguments arguments, Stream outputStream, CancellationToken cancellationToken) { - return RunServiceAsync(async () => + // Complete RPC right away so the client can start reading from the stream. + // The fire-and forget task starts writing to the output stream and the client will read it until it reads all expected data. + + _ = RunServiceAsync(async cancellationToken => { using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_CalculateDiagnosticsAsync, arguments.ProjectId.DebugName, cancellationToken)) using (arguments.IsHighPriority ? UserOperationBooster.Boost() : default) @@ -44,22 +61,20 @@ public Task CalculateDiagnosticsAsync(PinnedSolutionInfo solutionInfo, Diagnosti getTelemetryInfo: arguments.GetTelemetryInfo, cancellationToken).ConfigureAwait(false); - await RemoteEndPoint.WriteDataToNamedPipeAsync(pipeName, (result, documentAnalysisKind), (writer, data, cancellationToken) => - { - var (diagnostics, telemetry) = DiagnosticResultSerializer.WriteDiagnosticAnalysisResults(writer, data.documentAnalysisKind, data.result, cancellationToken); - - // save log for debugging - Log(TraceEventType.Information, $"diagnostics: {diagnostics}, telemetry: {telemetry}"); + using var writer = new ObjectWriter(outputStream, leaveOpen: false, cancellationToken); + var (diagnostics, telemetry) = DiagnosticResultSerializer.WriteDiagnosticAnalysisResults(writer, documentAnalysisKind, result, cancellationToken); - return Task.CompletedTask; - }, cancellationToken).ConfigureAwait(false); + // save log for debugging + Log(TraceEventType.Information, $"diagnostics: {diagnostics}, telemetry: {telemetry}"); } }, cancellationToken); + + return default; } - public void ReportAnalyzerPerformance(List snapshot, int unitCount, CancellationToken cancellationToken) + public ValueTask ReportAnalyzerPerformanceAsync(ImmutableArray snapshot, int unitCount, CancellationToken cancellationToken) { - RunService(() => + return RunServiceAsync(cancellationToken => { using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_ReportAnalyzerPerformance, cancellationToken)) { @@ -68,11 +83,13 @@ public void ReportAnalyzerPerformance(List snapshot, in var service = GetWorkspace().Services.GetService(); if (service == null) { - return; + return default; } service.AddSnapshot(snapshot, unitCount); } + + return default; }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs index e79f348454dd8..0c09f2234809b 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Remote { - internal partial class RemoteHostService : ServiceBase, IRemoteHostService + internal partial class RemoteHostService { /// /// Track when last time report has sent and send new report if there is update after given internal diff --git a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.cs b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.cs index 7577d7cd826bd..1a5965c45d3c8 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.cs @@ -94,7 +94,7 @@ public void InitializeGlobalState(int uiCultureLCID, int cultureLCID, Cancellati /// /// Remote API. Initializes ServiceHub process global state. /// - public void InitializeTelemetrySession(string host, string serializedSession, CancellationToken cancellationToken) + public void InitializeTelemetrySession(int hostProcessId, string serializedSession, CancellationToken cancellationToken) { RunService(() => { @@ -110,7 +110,7 @@ public void InitializeTelemetrySession(string host, string serializedSession, Ca // log telemetry that service hub started RoslynLogger.Log(FunctionId.RemoteHost_Connect, KeyValueLogMessage.Create(m => { - m["Host"] = host; + m["Host"] = hostProcessId; m["InstanceId"] = InstanceId; })); @@ -124,25 +124,25 @@ public void InitializeTelemetrySession(string host, string serializedSession, Ca }, cancellationToken); } - Task> IAssetSource.GetAssetsAsync(int scopeId, ISet checksums, ISerializerService serializerService, CancellationToken cancellationToken) + async ValueTask> IAssetSource.GetAssetsAsync(int scopeId, ISet checksums, ISerializerService serializerService, CancellationToken cancellationToken) { - return RunServiceAsync(() => + return await RunServiceAsync(() => { using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_GetAssetsAsync, (serviceId, checksums) => $"{serviceId} - {Checksum.GetChecksumsLogInfo(checksums)}", scopeId, checksums, cancellationToken)) { return EndPoint.InvokeAsync( nameof(IRemoteHostServiceCallback.GetAssetsAsync), new object[] { scopeId, checksums.ToArray() }, - (stream, cancellationToken) => Task.FromResult(RemoteHostAssetSerialization.ReadData(stream, scopeId, checksums, serializerService, cancellationToken)), + (stream, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(stream, scopeId, checksums, serializerService, cancellationToken).AsTask(), cancellationToken); } - }, cancellationToken); + }, cancellationToken).ConfigureAwait(false); } // TODO: remove (https://github.com/dotnet/roslyn/issues/43477) - Task IAssetSource.IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken) + async ValueTask IAssetSource.IsExperimentEnabledAsync(string experimentName, CancellationToken cancellationToken) { - return RunServiceAsync(() => + return await RunServiceAsync(() => { using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_IsExperimentEnabledAsync, experimentName, cancellationToken)) { @@ -151,7 +151,7 @@ Task IAssetSource.IsExperimentEnabledAsync(string experimentName, Cancella new object[] { experimentName }, cancellationToken); } - }, cancellationToken); + }, cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryIncrementalAnalyzer.cs b/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryIncrementalAnalyzer.cs index 08a28348e07a6..87aac48c8ec9a 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryIncrementalAnalyzer.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryIncrementalAnalyzer.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ProjectTelemetry; using Microsoft.CodeAnalysis.SolutionCrawler; +using StreamJsonRpc; namespace Microsoft.CodeAnalysis.Remote { @@ -17,13 +18,13 @@ internal class RemoteProjectTelemetryIncrementalAnalyzer : IncrementalAnalyzerBa /// /// Channel back to VS to inform it of the designer attributes we discover. /// - private readonly RemoteEndPoint _endPoint; + private readonly RemoteCallback _callback; private readonly object _gate = new object(); private readonly Dictionary _projectToData = new Dictionary(); - public RemoteProjectTelemetryIncrementalAnalyzer(RemoteEndPoint endPoint) - => _endPoint = endPoint; + public RemoteProjectTelemetryIncrementalAnalyzer(RemoteCallback callback) + => _callback = callback; /// /// Collects data from and reports it to the telemetry service. @@ -68,10 +69,12 @@ public override async Task AnalyzeProjectAsync(Project project, bool semanticsCh _projectToData[projectId] = info; } - await _endPoint.InvokeAsync( - nameof(IProjectTelemetryListener.ReportProjectTelemetryDataAsync), - new object[] { info }, - cancellationToken).ConfigureAwait(false); + // cancel whenever the analyzer runner cancels or the client disconnects and the request is canceled: + using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _callback.ClientDisconnectedSource.Token); + + await _callback.InvokeAsync( + (callback, cancellationToken) => callback.ReportProjectTelemetryDataAsync(info, cancellationToken), + linkedSource.Token).ConfigureAwait(false); } public override Task RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken) diff --git a/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryIncrementalAnalyzerProvider.cs b/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryIncrementalAnalyzerProvider.cs index 6af5bfe3de007..aad1c69e8c0e2 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryIncrementalAnalyzerProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryIncrementalAnalyzerProvider.cs @@ -4,6 +4,7 @@ #nullable enable +using Microsoft.CodeAnalysis.ProjectTelemetry; using Microsoft.CodeAnalysis.SolutionCrawler; namespace Microsoft.CodeAnalysis.Remote @@ -15,14 +16,14 @@ namespace Microsoft.CodeAnalysis.Remote /// internal class RemoteProjectTelemetryIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider { - private readonly RemoteEndPoint _endPoint; + private readonly RemoteCallback _callback; - public RemoteProjectTelemetryIncrementalAnalyzerProvider(RemoteEndPoint endPoint) + public RemoteProjectTelemetryIncrementalAnalyzerProvider(RemoteCallback callback) { - _endPoint = endPoint; + _callback = callback; } public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) - => new RemoteProjectTelemetryIncrementalAnalyzer(_endPoint); + => new RemoteProjectTelemetryIncrementalAnalyzer(_callback); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryService.cs b/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryService.cs index 5ce459e9292b9..b40eca22e2fbc 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ProjectTelemetry/RemoteProjectTelemetryService.cs @@ -10,26 +10,33 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ProjectTelemetry; using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote { - internal partial class RemoteProjectTelemetryService : ServiceBase, IRemoteProjectTelemetryService + internal partial class RemoteProjectTelemetryService : BrokeredServiceBase, IRemoteProjectTelemetryService { - public RemoteProjectTelemetryService( - Stream stream, IServiceProvider serviceProvider) - : base(serviceProvider, stream) + internal sealed class Factory : FactoryBase { - StartService(); + protected override IRemoteProjectTelemetryService CreateService(in ServiceConstructionArguments arguments, RemoteCallback callback) + => new RemoteProjectTelemetryService(arguments, callback); } - public Task ComputeProjectTelemetryAsync(CancellationToken cancellation) + private readonly RemoteCallback _callback; + + public RemoteProjectTelemetryService(in ServiceConstructionArguments arguments, RemoteCallback callback) + : base(arguments) + { + _callback = callback; + } + + public ValueTask ComputeProjectTelemetryAsync(CancellationToken cancellationToken) { - return RunServiceAsync(() => + return RunServiceAsync(cancellationToken => { var workspace = GetWorkspace(); - var endpoint = this.EndPoint; var registrationService = workspace.Services.GetRequiredService(); - var analyzerProvider = new RemoteProjectTelemetryIncrementalAnalyzerProvider(endpoint); + var analyzerProvider = new RemoteProjectTelemetryIncrementalAnalyzerProvider(_callback); registrationService.AddAnalyzerProvider( analyzerProvider, @@ -38,8 +45,8 @@ public Task ComputeProjectTelemetryAsync(CancellationToken cancellation) highPriorityForActiveFile: false, workspaceKinds: WorkspaceKind.RemoteWorkspace)); - return Task.CompletedTask; - }, cancellation); + return default; + }, cancellationToken); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_SemanticClassification.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs similarity index 62% rename from src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_SemanticClassification.cs rename to src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs index 6f5edba308677..4997b5190348f 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_SemanticClassification.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs @@ -2,21 +2,34 @@ // 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.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote { - internal partial class CodeAnalysisService : IRemoteSemanticClassificationService + internal sealed class RemoteSemanticClassificationService : BrokeredServiceBase, IRemoteSemanticClassificationService { - public Task GetSemanticClassificationsAsync( + internal sealed class Factory : FactoryBase + { + protected override IRemoteSemanticClassificationService CreateService(in ServiceConstructionArguments arguments) + => new RemoteSemanticClassificationService(arguments); + } + + public RemoteSemanticClassificationService(in ServiceConstructionArguments arguments) + : base(arguments) + { + } + + public ValueTask GetSemanticClassificationsAsync( PinnedSolutionInfo solutionInfo, DocumentId documentId, TextSpan span, CancellationToken cancellationToken) { - return RunServiceAsync(async () => + return RunServiceAsync(async cancellationToken => { using (UserOperationBooster.Boost()) { diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_SemanticClassificationCache.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs similarity index 93% rename from src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_SemanticClassificationCache.cs rename to src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs index e0dd94a55f170..a5e119710ee40 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysis/CodeAnalysisService_SemanticClassificationCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs @@ -4,6 +4,7 @@ #nullable enable +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -17,12 +18,24 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.ServiceHub.Framework; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote { - internal partial class CodeAnalysisService : IRemoteSemanticClassificationCacheService + internal sealed class RemoteSemanticClassificationCacheService : BrokeredServiceBase, IRemoteSemanticClassificationCacheService { + internal sealed class Factory : FactoryBase + { + protected override IRemoteSemanticClassificationCacheService CreateService(in ServiceConstructionArguments arguments) + => new RemoteSemanticClassificationCacheService(arguments); + } + + public RemoteSemanticClassificationCacheService(in ServiceConstructionArguments arguments) + : base(arguments) + { + } + /// /// Key we use to look this up in the persistence store for a particular document. /// @@ -57,13 +70,13 @@ private static async Task GetChecksumAsync(Document document, Cancella return textChecksum; } - public Task CacheSemanticClassificationsAsync( + public ValueTask CacheSemanticClassificationsAsync( PinnedSolutionInfo solutionInfo, DocumentId documentId, bool isFullyLoaded, CancellationToken cancellationToken) { - return RunServiceAsync(async () => + return RunServiceAsync(async cancellationToken => { // Once fully loaded, we can clear any of the cached information we stored during load. if (isFullyLoaded) @@ -167,10 +180,10 @@ private static void WriteTo(List classifiedSpans, ObjectWriter w } } - public Task GetCachedSemanticClassificationsAsync( + public ValueTask GetCachedSemanticClassificationsAsync( SerializableDocumentKey documentKey, TextSpan textSpan, Checksum checksum, CancellationToken cancellationToken) { - return RunServiceAsync(async () => + return RunServiceAsync(async cancellationToken => { var classifiedSpans = await TryGetOrReadCachedSemanticClassificationsAsync( documentKey.Rehydrate(), checksum, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsIncrementalAnalyzer.cs b/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsIncrementalAnalyzer.cs index 3b5caf13be6b6..9d44a17fe9832 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsIncrementalAnalyzer.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsIncrementalAnalyzer.cs @@ -16,17 +16,19 @@ internal sealed class RemoteTodoCommentsIncrementalAnalyzer : AbstractTodoCommen /// /// Channel back to VS to inform it of the designer attributes we discover. /// - private readonly RemoteEndPoint _endPoint; + private readonly RemoteCallback _callback; - public RemoteTodoCommentsIncrementalAnalyzer(RemoteEndPoint endPoint) - => _endPoint = endPoint; + public RemoteTodoCommentsIncrementalAnalyzer(RemoteCallback callback) + => _callback = callback; - protected override Task ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken) + protected override async ValueTask ReportTodoCommentDataAsync(DocumentId documentId, ImmutableArray data, CancellationToken cancellationToken) { - return _endPoint.InvokeAsync( - nameof(ITodoCommentsListener.ReportTodoCommentDataAsync), - new object[] { documentId, data }, - cancellationToken); + // cancel whenever the analyzer runner cancels or the client disconnects and the request is canceled: + using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _callback.ClientDisconnectedSource.Token); + + await _callback.InvokeAsync( + (callback, cancellationToken) => callback.ReportTodoCommentDataAsync(documentId, data, cancellationToken), + linkedSource.Token).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsIncrementalAnalyzerProvider.cs b/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsIncrementalAnalyzerProvider.cs index c75616fb64c01..fd1b99663ed4c 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsIncrementalAnalyzerProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsIncrementalAnalyzerProvider.cs @@ -5,6 +5,8 @@ #nullable enable using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.TodoComments; +using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote { @@ -15,14 +17,14 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class RemoteTodoCommentsIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider { - private readonly RemoteEndPoint _endPoint; + private readonly RemoteCallback _callback; - public RemoteTodoCommentsIncrementalAnalyzerProvider(RemoteEndPoint endPoint) + public RemoteTodoCommentsIncrementalAnalyzerProvider(RemoteCallback callback) { - _endPoint = endPoint; + _callback = callback; } public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) - => new RemoteTodoCommentsIncrementalAnalyzer(_endPoint); + => new RemoteTodoCommentsIncrementalAnalyzer(_callback); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsService.cs b/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsService.cs index f1fe97ba4a1fc..2dd83884f2eda 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/TodoComments/RemoteTodoCommentsService.cs @@ -4,8 +4,6 @@ #nullable enable -using System; -using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -13,23 +11,29 @@ namespace Microsoft.CodeAnalysis.Remote { - internal partial class RemoteTodoCommentsService : ServiceBase, IRemoteTodoCommentsService + internal partial class RemoteTodoCommentsService : BrokeredServiceBase, IRemoteTodoCommentsService { - public RemoteTodoCommentsService( - Stream stream, IServiceProvider serviceProvider) - : base(serviceProvider, stream) + internal sealed class Factory : FactoryBase { - StartService(); + protected override IRemoteTodoCommentsService CreateService(in ServiceConstructionArguments arguments, RemoteCallback callback) + => new RemoteTodoCommentsService(arguments, callback); } - public Task ComputeTodoCommentsAsync(CancellationToken cancellation) + private readonly RemoteCallback _callback; + + public RemoteTodoCommentsService(in ServiceConstructionArguments arguments, RemoteCallback callback) + : base(arguments) + { + _callback = callback; + } + + public ValueTask ComputeTodoCommentsAsync(CancellationToken cancellationToken) { - return RunServiceAsync(() => + return RunServiceAsync(cancellationToken => { var workspace = GetWorkspace(); - var endpoint = this.EndPoint; var registrationService = workspace.Services.GetRequiredService(); - var analyzerProvider = new RemoteTodoCommentsIncrementalAnalyzerProvider(endpoint); + var analyzerProvider = new RemoteTodoCommentsIncrementalAnalyzerProvider(_callback); registrationService.AddAnalyzerProvider( analyzerProvider, @@ -38,8 +42,8 @@ public Task ComputeTodoCommentsAsync(CancellationToken cancellation) highPriorityForActiveFile: false, workspaceKinds: WorkspaceKind.RemoteWorkspace)); - return Task.CompletedTask; - }, cancellation); + return default; + }, cancellationToken); } } }