diff --git a/eng/targets/GeneratePkgDef.targets b/eng/targets/GeneratePkgDef.targets index 54877ced329be..9d8bf69b1d090 100644 --- a/eng/targets/GeneratePkgDef.targets +++ b/eng/targets/GeneratePkgDef.targets @@ -35,6 +35,7 @@ Metadata: - ItemSpec: service name + - Audience ('Local'|'Process'): Microsoft.VisualStudio.Shell.ServiceBroker.ServiceAudience enum value - ProfferingPackageId (guid, optional): GUID of the proffering package or empty for ServiceHub services 4) PkgDefBindingRedirect @@ -87,6 +88,19 @@ + + + + + + <_Audience>dword:00000003 + <_Audience Condition="'%(PkgDefBrokeredService.Audience)' == 'Process'">dword:00000001 + + + + diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index ff5cc5f18ef13..85aaccdf515a9 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Tools/ExternalAccess/Razor/RazorRemoteHostClient.cs b/src/Tools/ExternalAccess/Razor/RazorRemoteHostClient.cs deleted file mode 100644 index 506c8d57ee843..0000000000000 --- a/src/Tools/ExternalAccess/Razor/RazorRemoteHostClient.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -#nullable enable annotations - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Experiments; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Remote; - -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor -{ - internal sealed class RazorRemoteHostClient - { - private readonly RemoteHostClient _client; - - internal RazorRemoteHostClient(RemoteHostClient client) - { - _client = client; - } - - public static async Task CreateAsync(Workspace workspace, CancellationToken cancellationToken = default) - { - var client = await RemoteHostClient.TryGetClientAsync(workspace.Services, cancellationToken).ConfigureAwait(false); - return client == null ? null : new RazorRemoteHostClient(client); - } - - public async Task> TryRunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, CancellationToken cancellationToken) - => await _client.RunRemoteAsync(WellKnownServiceHubService.Razor, targetName, solution, arguments, callbackTarget: null, cancellationToken).ConfigureAwait(false); - } -} diff --git a/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteCallbackWrapper.cs b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteCallbackWrapper.cs new file mode 100644 index 0000000000000..a6bd5ca5b5fe6 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteCallbackWrapper.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +{ + internal readonly struct RazorRemoteCallbackWrapper + where T : class + { + internal readonly RemoteCallback UnderlyingObject; + + public RazorRemoteCallbackWrapper(T callback) + => UnderlyingObject = new RemoteCallback(callback); + + public ValueTask InvokeAsync(Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.InvokeAsync(invocation, cancellationToken); + + public ValueTask InvokeAsync(Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.InvokeAsync(invocation, cancellationToken); + } +} diff --git a/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteHostClient.cs b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteHostClient.cs new file mode 100644 index 0000000000000..3a10bb12571cb --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteHostClient.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using MessagePack; +using MessagePack.Formatters; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Remote; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +{ + internal readonly struct RazorRemoteHostClient + { + private readonly ServiceHubRemoteHostClient _client; + private readonly RazorServiceDescriptorsWrapper _serviceDescriptors; + private readonly RazorRemoteServiceCallbackDispatcherRegistry _callbackDispatchers; + + internal RazorRemoteHostClient(ServiceHubRemoteHostClient client, RazorServiceDescriptorsWrapper serviceDescriptors, RazorRemoteServiceCallbackDispatcherRegistry callbackDispatchers) + { + _client = client; + _serviceDescriptors = serviceDescriptors; + _callbackDispatchers = callbackDispatchers; + } + + [Obsolete] + public static async Task CreateAsync(Workspace workspace, CancellationToken cancellationToken = default) + { + var client = await RemoteHostClient.TryGetClientAsync(workspace.Services, cancellationToken).ConfigureAwait(false); + var descriptors = new RazorServiceDescriptorsWrapper("dummy", _ => throw ExceptionUtilities.Unreachable, ImmutableArray.Empty, ImmutableArray.Empty, Array.Empty<(Type, Type?)>()); + return client == null ? null : new RazorRemoteHostClient((ServiceHubRemoteHostClient)client, descriptors, RazorRemoteServiceCallbackDispatcherRegistry.Empty); + } + + [Obsolete] + public async Task> TryRunRemoteAsync(string targetName, Solution? solution, IReadOnlyList arguments, CancellationToken cancellationToken) + => await _client.RunRemoteAsync(WellKnownServiceHubService.Razor, targetName, solution, arguments, callbackTarget: null, cancellationToken).ConfigureAwait(false); + + public static async Task TryGetClientAsync(HostWorkspaceServices services, RazorServiceDescriptorsWrapper serviceDescriptors, RazorRemoteServiceCallbackDispatcherRegistry callbackDispatchers, CancellationToken cancellationToken = default) + { + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); + if (client is null) + return null; + + return new RazorRemoteHostClient((ServiceHubRemoteHostClient)client, serviceDescriptors, callbackDispatchers); + } + + public RazorRemoteServiceConnectionWrapper CreateConnection(object? callbackTarget) where TService : class + => new(_client.CreateConnection(_serviceDescriptors.UnderlyingObject, _callbackDispatchers, callbackTarget)); + + // no solution, no callback: + + public ValueTask TryInvokeAsync(Func invocation, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync(invocation, cancellationToken); + + public ValueTask> TryInvokeAsync(Func> invocation, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync(invocation, cancellationToken); + + // no solution, callback: + + public ValueTask TryInvokeAsync(Func invocation, object callbackTarget, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync( + (service, callbackId, cancellationToken) => invocation(service, new RazorRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + callbackTarget, + cancellationToken); + + public ValueTask> TryInvokeAsync(Func> invocation, object callbackTarget, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync( + (service, callbackId, cancellationToken) => invocation(service, new RazorRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + callbackTarget, + cancellationToken); + + // solution, no callback: + + public ValueTask TryInvokeAsync(Solution solution, Func invocation, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync(solution, invocation, cancellationToken); + + public ValueTask> TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync(solution, invocation, cancellationToken); + + // solution, callback: + + public ValueTask TryInvokeAsync(Solution solution, Func invocation, object callbackTarget, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => invocation(service, solutionInfo, new RazorRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + callbackTarget, + cancellationToken); + + public ValueTask> TryInvokeAsync(Solution solution, Func> invocation, object callbackTarget, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => invocation(service, solutionInfo, new RazorRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + callbackTarget, + cancellationToken); + } +} diff --git a/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackDispatcher.cs b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackDispatcher.cs new file mode 100644 index 0000000000000..f7599e517a219 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackDispatcher.cs @@ -0,0 +1,19 @@ +// 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 Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +{ + internal abstract class RazorRemoteServiceCallbackDispatcher : IRemoteServiceCallbackDispatcher + { + private readonly RemoteServiceCallbackDispatcher _dispatcher = new(); + + public object GetCallback(RazorRemoteServiceCallbackIdWrapper callbackId) + => _dispatcher.GetCallback(callbackId.UnderlyingObject); + + RemoteServiceCallbackDispatcher.Handle IRemoteServiceCallbackDispatcher.CreateHandle(object? instance) + => _dispatcher.CreateHandle(instance); + } +} diff --git a/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackDispatcherRegistry.cs b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackDispatcherRegistry.cs new file mode 100644 index 0000000000000..7ebafece4dc33 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackDispatcherRegistry.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +{ + internal sealed class RazorRemoteServiceCallbackDispatcherRegistry : IRemoteServiceCallbackDispatcherProvider + { + public static readonly RazorRemoteServiceCallbackDispatcherRegistry Empty = new(Array.Empty<(Type, RazorRemoteServiceCallbackDispatcher)>()); + + private readonly ImmutableDictionary _lazyDispatchers; + + public RazorRemoteServiceCallbackDispatcherRegistry(IEnumerable<(Type serviceType, RazorRemoteServiceCallbackDispatcher dispatcher)> lazyDispatchers) + { + _lazyDispatchers = lazyDispatchers.ToImmutableDictionary(e => e.serviceType, e => e.dispatcher); + } + + IRemoteServiceCallbackDispatcher IRemoteServiceCallbackDispatcherProvider.GetDispatcher(Type serviceType) + => _lazyDispatchers[serviceType]; + } +} diff --git a/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackIdWrapper.cs b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackIdWrapper.cs new file mode 100644 index 0000000000000..5be8a32acbf9c --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceCallbackIdWrapper.cs @@ -0,0 +1,20 @@ +// 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.Runtime.Serialization; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +{ + [DataContract] + internal readonly struct RazorRemoteServiceCallbackIdWrapper + { + [DataMember(Order = 0)] + internal RemoteServiceCallbackId UnderlyingObject { get; } + + public RazorRemoteServiceCallbackIdWrapper(RemoteServiceCallbackId underlyingObject) + => UnderlyingObject = underlyingObject; + } +} diff --git a/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceConnectionWrapper.cs b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceConnectionWrapper.cs new file mode 100644 index 0000000000000..234016a19a10f --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Remote/RazorRemoteServiceConnectionWrapper.cs @@ -0,0 +1,65 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +{ + internal readonly struct RazorRemoteServiceConnectionWrapper : IDisposable + where TService : class + { + internal RemoteServiceConnection UnderlyingObject { get; } + + internal RazorRemoteServiceConnectionWrapper(RemoteServiceConnection underlyingObject) + => UnderlyingObject = underlyingObject; + + public void Dispose() + => UnderlyingObject.Dispose(); + + // no solution, no callback + + public ValueTask TryInvokeAsync(Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync(invocation, cancellationToken); + + public ValueTask> TryInvokeAsync(Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync(invocation, cancellationToken); + + // no solution, callback + + public ValueTask TryInvokeAsync(Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync( + (service, callbackId, cancellationToken) => invocation(service, new RazorRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + cancellationToken); + + public ValueTask> TryInvokeAsync(Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync( + (service, callbackId, cancellationToken) => invocation(service, new RazorRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + cancellationToken); + + // solution, no callback + + public ValueTask TryInvokeAsync(Solution solution, Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync(solution, invocation, cancellationToken); + + public ValueTask> TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync(solution, invocation, cancellationToken); + + // solution, callback + + public ValueTask TryInvokeAsync(Solution solution, Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => invocation(service, solutionInfo, new RazorRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + cancellationToken); + + public ValueTask> TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => invocation(service, solutionInfo, new RazorRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + cancellationToken); + } +} diff --git a/src/Tools/ExternalAccess/Razor/Remote/RazorServiceDescriptorsWrapper.cs b/src/Tools/ExternalAccess/Razor/Remote/RazorServiceDescriptorsWrapper.cs new file mode 100644 index 0000000000000..0be303ac1aec4 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Remote/RazorServiceDescriptorsWrapper.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Runtime; +using MessagePack; +using MessagePack.Formatters; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.ServiceHub.Framework; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +{ + internal readonly struct RazorServiceDescriptorsWrapper + { + internal readonly ServiceDescriptors UnderlyingObject; + + public RazorServiceDescriptorsWrapper( + string componentLevelPrefix, + Func featureDisplayNameProvider, + ImmutableArray additionalFormatters, + ImmutableArray additionalResolvers, + IEnumerable<(Type serviceInterface, Type? callbackInterface)> interfaces) + => UnderlyingObject = new ServiceDescriptors(componentLevelPrefix, featureDisplayNameProvider, additionalFormatters, additionalResolvers, interfaces); + + /// + /// To be called from a service factory in OOP. + /// + public ServiceJsonRpcDescriptor GetDescriptorForServiceFactory(Type serviceInterface) + => UnderlyingObject.GetServiceDescriptorForServiceFactory(serviceInterface); + + public MessagePackSerializerOptions MessagePackOptions + => UnderlyingObject.Options; + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/Watson/WatsonReporter.cs b/src/VisualStudio/Core/Def/Implementation/Watson/WatsonReporter.cs index 75a9b182f3d25..369697d2f1f2f 100644 --- a/src/VisualStudio/Core/Def/Implementation/Watson/WatsonReporter.cs +++ b/src/VisualStudio/Core/Def/Implementation/Watson/WatsonReporter.cs @@ -194,7 +194,7 @@ private static List CollectServiceHubLogFilePaths() // name our services more consistently to simplify filtering // filter logs that are not relevant to Roslyn investigation - if (!name.Contains("-" + ServiceDescriptors.ServiceNamePrefix) && + if (!name.Contains("-" + ServiceDescriptors.ServiceNameTopLevelPrefix) && !name.Contains("-" + RemoteServiceName.Prefix) && !name.Contains("-" + RemoteServiceName.IntelliCodeServiceName) && !name.Contains("-" + RemoteServiceName.RazorServiceName) && diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClientWrapper.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClientWrapper.cs index 519e5d7b495b9..f52b518fccb39 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClientWrapper.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClientWrapper.cs @@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api { + [Obsolete] internal readonly struct UnitTestingRemoteHostClientWrapper { internal UnitTestingRemoteHostClientWrapper(RemoteHostClient underlyingObject) diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceConnectionWrapper.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceConnectionWrapper.cs index 1d9eaa05dfc0b..e1be7bd82033e 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceConnectionWrapper.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceConnectionWrapper.cs @@ -10,6 +10,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api { + [Obsolete] internal readonly struct UnitTestingRemoteServiceConnectionWrapper : IDisposable { internal RemoteServiceConnection UnderlyingObject { get; } diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingServiceHubService.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingServiceHubService.cs index 7b8a965fe51bb..dd3af1edb00d2 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingServiceHubService.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingServiceHubService.cs @@ -2,10 +2,12 @@ // 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 Microsoft.CodeAnalysis.Remote; namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api { + [Obsolete] internal enum UnitTestingServiceHubService { UnitTestingAnalysisService = WellKnownServiceHubService.UnitTestingAnalysisService, diff --git a/src/Workspaces/Core/Portable/Remote/IRemoteServiceCallbackDispatcherProvider.cs b/src/Workspaces/Core/Portable/Remote/IRemoteServiceCallbackDispatcherProvider.cs new file mode 100644 index 0000000000000..759cfe8f66992 --- /dev/null +++ b/src/Workspaces/Core/Portable/Remote/IRemoteServiceCallbackDispatcherProvider.cs @@ -0,0 +1,13 @@ +// 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; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal interface IRemoteServiceCallbackDispatcherProvider + { + IRemoteServiceCallbackDispatcher GetDispatcher(Type serviceType); + } +} diff --git a/src/Workspaces/Core/Portable/Remote/RemoteServiceCallbackDispatcher.cs b/src/Workspaces/Core/Portable/Remote/RemoteServiceCallbackDispatcher.cs index 24c8c354acc93..4ed51833c319c 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteServiceCallbackDispatcher.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteServiceCallbackDispatcher.cs @@ -14,7 +14,7 @@ internal interface IRemoteServiceCallbackDispatcher RemoteServiceCallbackDispatcher.Handle CreateHandle(object? instance); } - internal abstract class RemoteServiceCallbackDispatcher : IRemoteServiceCallbackDispatcher + internal class RemoteServiceCallbackDispatcher : IRemoteServiceCallbackDispatcher { internal readonly struct Handle : IDisposable { diff --git a/src/Workspaces/Core/Portable/Remote/RemoteServiceCallbackDispatchers.cs b/src/Workspaces/Core/Portable/Remote/RemoteServiceCallbackDispatchers.cs index 71abffc784c6d..47e8fb1ccaeff 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteServiceCallbackDispatchers.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteServiceCallbackDispatchers.cs @@ -9,13 +9,15 @@ namespace Microsoft.CodeAnalysis.Remote { - internal readonly struct RemoteServiceCallbackDispatcherRegistry - + internal sealed class RemoteServiceCallbackDispatcherRegistry : IRemoteServiceCallbackDispatcherProvider { public sealed class ExportMetadata { public Type ServiceInterface { get; } + public ExportMetadata(Type serviceInterface) + => ServiceInterface = serviceInterface; + public ExportMetadata(IDictionary data) { var serviceInterface = data.GetValueOrDefault(nameof(ExportRemoteServiceCallbackDispatcherAttribute.ServiceInterface)); diff --git a/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs b/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs index 7ffa3b8458ac0..e963f875d7c52 100644 --- a/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs +++ b/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs @@ -26,14 +26,15 @@ namespace Microsoft.CodeAnalysis.Remote.UnitTests [UseExportProvider] public class ServiceDescriptorTests { - public static IEnumerable ServiceTypes - => ServiceDescriptors.Descriptors.Select(descriptor => new object[] { descriptor.Key }); + public static IEnumerable AllServiceDescriptors + => ServiceDescriptors.Instance.GetTestAccessor().Descriptors + .Select(descriptor => new object[] { descriptor.Key, descriptor.Value.descriptor32, descriptor.Value.descriptor64, descriptor.Value.descriptor64ServerGC }); private static Dictionary GetAllParameterTypesOfRemoteApis() { var interfaces = new List(); - foreach (var (serviceType, (descriptor, _, _)) in ServiceDescriptors.Descriptors) + foreach (var (serviceType, (descriptor, _, _)) in ServiceDescriptors.Instance.GetTestAccessor().Descriptors) { interfaces.Add(serviceType); if (descriptor.ClientInterface != null) @@ -128,7 +129,7 @@ void AddTypeRecursive(Type type, MemberInfo declaringMember) public void TypesUsedInRemoteApisMustBeMessagePackSerializable() { var types = GetAllParameterTypesOfRemoteApis(); - var resolver = ServiceDescriptor.TestAccessor.Options.Resolver; + var resolver = MessagePackFormatters.DefaultResolver; var errors = new List(); @@ -163,10 +164,16 @@ public void TypesUsedInRemoteApisMustBeMessagePackSerializable() } [Theory] - [MemberData(nameof(ServiceTypes))] - public void GetFeatureName(Type serviceType) + [MemberData(nameof(AllServiceDescriptors))] + internal void GetFeatureDisplayName(Type serviceInterface, ServiceDescriptor descriptor32, ServiceDescriptor descriptor64, ServiceDescriptor descriptor64ServerGC) { - Assert.NotEmpty(ServiceDescriptors.GetFeatureName(serviceType)); + Assert.NotNull(serviceInterface); + + var expectedName = descriptor32.GetFeatureDisplayName(); + Assert.NotEmpty(expectedName); + + Assert.Equal(expectedName, descriptor64.GetFeatureDisplayName()); + Assert.Equal(expectedName, descriptor64ServerGC.GetFeatureDisplayName()); } [Fact] @@ -175,7 +182,9 @@ public void CallbackDispatchers() var hostServices = FeaturesTestCompositions.Features.WithTestHostParts(Testing.TestHost.OutOfProcess).GetHostServices(); var callbackDispatchers = ((IMefHostExportProvider)hostServices).GetExports(); - var descriptorsWithCallbackServiceTypes = ServiceDescriptors.Descriptors.Where(d => d.Value.descriptor32.ClientInterface != null).Select(d => d.Key); + var descriptorsWithCallbackServiceTypes = ServiceDescriptors.Instance.GetTestAccessor().Descriptors + .Where(d => d.Value.descriptor32.ClientInterface != null).Select(d => d.Key); + var callbackDispatcherServiceTypes = callbackDispatchers.Select(d => d.Metadata.ServiceInterface); AssertEx.SetEqual(descriptorsWithCallbackServiceTypes, callbackDispatcherServiceTypes); } diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index 103d6ad1db6dd..0debdb3b40b9f 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -109,15 +109,19 @@ public void RegisterService(RemoteServiceName serviceName, Func _inprocServices.RegisterService(serviceName, serviceCreator); public override RemoteServiceConnection CreateConnection(object? callbackTarget) where T : class - => new BrokeredServiceConnection( + { + var descriptor = ServiceDescriptors.Instance.GetServiceDescriptor(typeof(T), isRemoteHost64Bit: IntPtr.Size == 8, isRemoteHostServerGC: GCSettings.IsServerGC); + var callbackDispatcher = (descriptor.ClientInterface != null) ? _callbackDispatchers.GetDispatcher(typeof(T)) : null; + + return new BrokeredServiceConnection( + descriptor, callbackTarget, - _callbackDispatchers, + callbackDispatcher, _inprocServices.ServiceBrokerClient, _workspaceServices.GetRequiredService().AssetStorage, _workspaceServices.GetRequiredService(), - shutdownCancellationService: null, - isRemoteHost64Bit: IntPtr.Size == 8, - isRemoteHostServerGC: GCSettings.IsServerGC); + shutdownCancellationService: null); + } public override async Task CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken) { @@ -298,7 +302,7 @@ public void RegisterInProcBrokeredService(ServiceDescriptor serviceDescriptor, F public void RegisterRemoteBrokeredService(BrokeredServiceBase.IFactory serviceFactory) { - var moniker = ServiceDescriptors.GetServiceDescriptor(serviceFactory.ServiceType, isRemoteHost64Bit: IntPtr.Size == 8, isRemoteHostServerGC: GCSettings.IsServerGC).Moniker; + var moniker = ServiceDescriptors.Instance.GetServiceDescriptorForServiceFactory(serviceFactory.ServiceType).Moniker; _remoteBrokeredServicesMap.Add(moniker, serviceFactory); } diff --git a/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs b/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs index 73827298bdf0f..01ff3847f911e 100644 --- a/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs +++ b/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs @@ -50,27 +50,23 @@ public void Dispose() private readonly IRemoteServiceCallbackDispatcher? _callbackDispatcher; public BrokeredServiceConnection( + ServiceDescriptor serviceDescriptor, object? callbackTarget, - RemoteServiceCallbackDispatcherRegistry callbackDispatchers, + IRemoteServiceCallbackDispatcher? callbackDispatcher, ServiceBrokerClient serviceBrokerClient, SolutionAssetStorage solutionAssetStorage, IErrorReportingService? errorReportingService, - IRemoteHostClientShutdownCancellationService? shutdownCancellationService, - bool isRemoteHost64Bit, - bool isRemoteHostServerGC) + IRemoteHostClientShutdownCancellationService? shutdownCancellationService) { + Contract.ThrowIfFalse((callbackDispatcher == null) == (serviceDescriptor.ClientInterface == null)); + + _serviceDescriptor = serviceDescriptor; _serviceBrokerClient = serviceBrokerClient; _solutionAssetStorage = solutionAssetStorage; _errorReportingService = errorReportingService; _shutdownCancellationService = shutdownCancellationService; - - _serviceDescriptor = ServiceDescriptors.GetServiceDescriptor(typeof(TService), isRemoteHost64Bit, isRemoteHostServerGC); - - if (_serviceDescriptor.ClientInterface != null) - { - _callbackDispatcher = callbackDispatchers.GetDispatcher(typeof(TService)); - _callbackHandle = _callbackDispatcher.CreateHandle(callbackTarget); - } + _callbackDispatcher = callbackDispatcher; + _callbackHandle = callbackDispatcher?.CreateHandle(callbackTarget) ?? default; } public override void Dispose() @@ -347,7 +343,7 @@ private bool ReportUnexpectedException(Exception exception, CancellationToken ca } // report telemetry event: - Logger.Log(FunctionId.FeatureNotAvailable, $"{ServiceDescriptors.GetServiceName(typeof(TService))}: {exception.GetType()}: {exception.Message}"); + Logger.Log(FunctionId.FeatureNotAvailable, $"{_serviceDescriptor.Moniker}: {exception.GetType()}: {exception.Message}"); return FatalError.ReportAndCatch(exception); } @@ -385,7 +381,7 @@ private void OnUnexpectedException(Exception exception, CancellationToken cancel string message; Exception? internalException = null; - var featureName = ServiceDescriptors.GetFeatureName(typeof(TService)); + var featureName = _serviceDescriptor.GetFeatureDisplayName(); if (IsRemoteIOException(exception)) { diff --git a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteCallbackWrapper.cs b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteCallbackWrapper.cs new file mode 100644 index 0000000000000..c7f70f7d46bc9 --- /dev/null +++ b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteCallbackWrapper.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api +{ + internal readonly struct UnitTestingRemoteCallbackWrapper + where T : class + { + internal readonly RemoteCallback UnderlyingObject; + + public UnitTestingRemoteCallbackWrapper(T callback) + => UnderlyingObject = new RemoteCallback(callback); + + public ValueTask InvokeAsync(Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.InvokeAsync(invocation, cancellationToken); + + public ValueTask InvokeAsync(Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.InvokeAsync(invocation, cancellationToken); + } +} diff --git a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClient.cs b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClient.cs new file mode 100644 index 0000000000000..092c694e5bc66 --- /dev/null +++ b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClient.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Remote; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api +{ + internal readonly struct UnitTestingRemoteHostClient + { + private readonly ServiceHubRemoteHostClient _client; + private readonly UnitTestingServiceDescriptorsWrapper _serviceDescriptors; + private readonly UnitTestingRemoteServiceCallbackDispatcherRegistry _callbackDispatchers; + + internal UnitTestingRemoteHostClient(ServiceHubRemoteHostClient client, UnitTestingServiceDescriptorsWrapper serviceDescriptors, UnitTestingRemoteServiceCallbackDispatcherRegistry callbackDispatchers) + { + _client = client; + _serviceDescriptors = serviceDescriptors; + _callbackDispatchers = callbackDispatchers; + } + + public static async Task TryGetClientAsync(HostWorkspaceServices services, UnitTestingServiceDescriptorsWrapper serviceDescriptors, UnitTestingRemoteServiceCallbackDispatcherRegistry callbackDispatchers, CancellationToken cancellationToken = default) + { + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); + if (client is null) + return null; + + return new UnitTestingRemoteHostClient((ServiceHubRemoteHostClient)client, serviceDescriptors, callbackDispatchers); + } + + public UnitTestingRemoteServiceConnectionWrapper CreateConnection(object? callbackTarget) where TService : class + => new(_client.CreateConnection(_serviceDescriptors.UnderlyingObject, _callbackDispatchers, callbackTarget)); + + // no solution, no callback: + + public ValueTask TryInvokeAsync(Func invocation, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync(invocation, cancellationToken); + + public ValueTask> TryInvokeAsync(Func> invocation, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync(invocation, cancellationToken); + + // no solution, callback: + + public ValueTask TryInvokeAsync(Func invocation, object callbackTarget, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync( + (service, callbackId, cancellationToken) => invocation(service, new UnitTestingRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + callbackTarget, + cancellationToken); + + public ValueTask> TryInvokeAsync(Func> invocation, object callbackTarget, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync( + (service, callbackId, cancellationToken) => invocation(service, new UnitTestingRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + callbackTarget, + cancellationToken); + + // solution, no callback: + + public ValueTask TryInvokeAsync(Solution solution, Func invocation, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync(solution, invocation, cancellationToken); + + public ValueTask> TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync(solution, invocation, cancellationToken); + + // solution, callback: + + public ValueTask TryInvokeAsync(Solution solution, Func invocation, object callbackTarget, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => invocation(service, solutionInfo, new UnitTestingRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + callbackTarget, + cancellationToken); + + public ValueTask> TryInvokeAsync(Solution solution, Func> invocation, object callbackTarget, CancellationToken cancellationToken) where TService : class + => _client.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => invocation(service, solutionInfo, new UnitTestingRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + callbackTarget, + cancellationToken); + } +} diff --git a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackDispatcher.cs b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackDispatcher.cs new file mode 100644 index 0000000000000..9fc35b9ff6d7b --- /dev/null +++ b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackDispatcher.cs @@ -0,0 +1,19 @@ +// 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 Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api +{ + internal abstract class UnitTestingRemoteServiceCallbackDispatcher : IRemoteServiceCallbackDispatcher + { + private readonly RemoteServiceCallbackDispatcher _dispatcher = new(); + + public object GetCallback(UnitTestingRemoteServiceCallbackIdWrapper callbackId) + => _dispatcher.GetCallback(callbackId.UnderlyingObject); + + RemoteServiceCallbackDispatcher.Handle IRemoteServiceCallbackDispatcher.CreateHandle(object? instance) + => _dispatcher.CreateHandle(instance); + } +} diff --git a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackDispatcherRegistry.cs b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackDispatcherRegistry.cs new file mode 100644 index 0000000000000..e5443f9094706 --- /dev/null +++ b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackDispatcherRegistry.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api +{ + internal sealed class UnitTestingRemoteServiceCallbackDispatcherRegistry : IRemoteServiceCallbackDispatcherProvider + { + public static readonly UnitTestingRemoteServiceCallbackDispatcherRegistry Empty = new(Array.Empty<(Type, UnitTestingRemoteServiceCallbackDispatcher)>()); + + private readonly ImmutableDictionary _lazyDispatchers; + + public UnitTestingRemoteServiceCallbackDispatcherRegistry(IEnumerable<(Type serviceType, UnitTestingRemoteServiceCallbackDispatcher dispatcher)> lazyDispatchers) + { + _lazyDispatchers = lazyDispatchers.ToImmutableDictionary(e => e.serviceType, e => e.dispatcher); + } + + IRemoteServiceCallbackDispatcher IRemoteServiceCallbackDispatcherProvider.GetDispatcher(Type serviceType) + => _lazyDispatchers[serviceType]; + } +} diff --git a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackIdWrapper.cs b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackIdWrapper.cs new file mode 100644 index 0000000000000..476c9d61de75d --- /dev/null +++ b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceCallbackIdWrapper.cs @@ -0,0 +1,20 @@ +// 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.Runtime.Serialization; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api +{ + [DataContract] + internal readonly struct UnitTestingRemoteServiceCallbackIdWrapper + { + [DataMember(Order = 0)] + internal RemoteServiceCallbackId UnderlyingObject { get; } + + public UnitTestingRemoteServiceCallbackIdWrapper(RemoteServiceCallbackId underlyingObject) + => UnderlyingObject = underlyingObject; + } +} diff --git a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceConnectionWrapper.cs b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceConnectionWrapper.cs new file mode 100644 index 0000000000000..33ad63273a323 --- /dev/null +++ b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteServiceConnectionWrapper.cs @@ -0,0 +1,65 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api +{ + internal readonly struct UnitTestingRemoteServiceConnectionWrapper : IDisposable + where TService : class + { + internal RemoteServiceConnection UnderlyingObject { get; } + + internal UnitTestingRemoteServiceConnectionWrapper(RemoteServiceConnection underlyingObject) + => UnderlyingObject = underlyingObject; + + public void Dispose() + => UnderlyingObject.Dispose(); + + // no solution, no callback + + public ValueTask TryInvokeAsync(Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync(invocation, cancellationToken); + + public ValueTask> TryInvokeAsync(Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync(invocation, cancellationToken); + + // no solution, callback + + public ValueTask TryInvokeAsync(Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync( + (service, callbackId, cancellationToken) => invocation(service, new UnitTestingRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + cancellationToken); + + public ValueTask> TryInvokeAsync(Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync( + (service, callbackId, cancellationToken) => invocation(service, new UnitTestingRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + cancellationToken); + + // solution, no callback + + public ValueTask TryInvokeAsync(Solution solution, Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync(solution, invocation, cancellationToken); + + public ValueTask> TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync(solution, invocation, cancellationToken); + + // solution, callback + + public ValueTask TryInvokeAsync(Solution solution, Func invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => invocation(service, solutionInfo, new UnitTestingRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + cancellationToken); + + public ValueTask> TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken) + => UnderlyingObject.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => invocation(service, solutionInfo, new UnitTestingRemoteServiceCallbackIdWrapper(callbackId), cancellationToken), + cancellationToken); + } +} diff --git a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingServiceDescriptorsWrapper.cs b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingServiceDescriptorsWrapper.cs new file mode 100644 index 0000000000000..64b56075cc82f --- /dev/null +++ b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingServiceDescriptorsWrapper.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Runtime; +using MessagePack; +using MessagePack.Formatters; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.ServiceHub.Framework; + +namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api +{ + internal readonly struct UnitTestingServiceDescriptorsWrapper + { + internal readonly ServiceDescriptors UnderlyingObject; + + public UnitTestingServiceDescriptorsWrapper( + string componentLevelPrefix, + Func featureDisplayNameProvider, + ImmutableArray additionalFormatters, + ImmutableArray additionalResolvers, + IEnumerable<(Type serviceInterface, Type? callbackInterface)> interfaces) + => UnderlyingObject = new ServiceDescriptors(componentLevelPrefix, featureDisplayNameProvider, additionalFormatters, additionalResolvers, interfaces); + + /// + /// To be called from a service factory in OOP. + /// + public ServiceJsonRpcDescriptor GetDescriptorForServiceFactory(Type serviceInterface) + => UnderlyingObject.GetServiceDescriptorForServiceFactory(serviceInterface); + + public MessagePackSerializerOptions MessagePackOptions + => UnderlyingObject.Options; + } +} diff --git a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj index 5c0aac1945947..16cd31984b6e3 100644 --- a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj +++ b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj @@ -57,6 +57,7 @@ + diff --git a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx index b14a8684de181..faab9f229e886 100644 --- a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx +++ b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx @@ -189,4 +189,7 @@ Asset synchronization + + Asset provider + \ No newline at end of file diff --git a/src/Workspaces/Remote/Core/Serialization/MessagePackFormatters.cs b/src/Workspaces/Remote/Core/Serialization/MessagePackFormatters.cs index 92f9849cb0a95..026f1605efb43 100644 --- a/src/Workspaces/Remote/Core/Serialization/MessagePackFormatters.cs +++ b/src/Workspaces/Remote/Core/Serialization/MessagePackFormatters.cs @@ -8,6 +8,7 @@ using System.Runtime.Serialization; using MessagePack; using MessagePack.Formatters; +using MessagePack.Resolvers; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.ConvertTupleToStruct; @@ -26,7 +27,7 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class MessagePackFormatters { - public static ImmutableArray GetFormatters() => ImmutableArray.Create( + private static readonly ImmutableArray s_formatters = ImmutableArray.Create( SolutionIdFormatter.Instance, ProjectIdFormatter.Instance, DocumentIdFormatter.Instance, @@ -48,6 +49,15 @@ public static ImmutableArray GetFormatters() => Immutable EnumFormatters.CodeActionPriority, EnumFormatters.DependentTypesKind); + private static readonly ImmutableArray s_resolvers = ImmutableArray.Create( + ImmutableCollectionMessagePackResolver.Instance, + StandardResolverAllowPrivate.Instance); + + internal static readonly IFormatterResolver DefaultResolver = CompositeResolver.Create(s_formatters, s_resolvers); + + internal static IFormatterResolver CreateResolver(ImmutableArray additionalFormatters, ImmutableArray additionalResolvers) + => (additionalFormatters.IsEmpty && additionalResolvers.IsEmpty) ? DefaultResolver : CompositeResolver.Create(s_formatters.AddRange(additionalFormatters), s_resolvers.AddRange(additionalResolvers)); + // TODO: remove https://github.com/neuecc/MessagePack-CSharp/issues/1025 internal static class EnumFormatters { diff --git a/src/Workspaces/Remote/Core/ServiceDescriptor.cs b/src/Workspaces/Remote/Core/ServiceDescriptor.cs index fab36a6c1851a..f8f860b43ea5c 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptor.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptor.cs @@ -3,8 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.IO.Pipelines; using MessagePack; +using MessagePack.Formatters; using MessagePack.Resolvers; using Microsoft.ServiceHub.Framework; using Nerdbank.Streams; @@ -33,21 +35,32 @@ internal sealed class ServiceDescriptor : ServiceJsonRpcDescriptor ProtocolMajorVersion = 3 }.GetFrozenCopy(); - private ServiceDescriptor(ServiceMoniker serviceMoniker, Type? clientInterface) + internal static readonly MessagePackSerializerOptions DefaultOptions = StandardResolverAllowPrivate.Options + .WithSecurity(MessagePackSecurity.UntrustedData.WithHashCollisionResistant(false)) + .WithResolver(MessagePackFormatters.DefaultResolver); + + private readonly Func _featureDisplayNameProvider; + private readonly MessagePackSerializerOptions _options; + + private ServiceDescriptor(ServiceMoniker serviceMoniker, MessagePackSerializerOptions options, Func displayNameProvider, Type? clientInterface) : base(serviceMoniker, clientInterface, Formatters.MessagePack, MessageDelimiters.BigEndianInt32LengthHeader, s_multiplexingStreamOptions) { + _featureDisplayNameProvider = displayNameProvider; + _options = options; } private ServiceDescriptor(ServiceDescriptor copyFrom) : base(copyFrom) { + _featureDisplayNameProvider = copyFrom._featureDisplayNameProvider; + _options = copyFrom._options; } - public static ServiceDescriptor CreateRemoteServiceDescriptor(string serviceName, Type? clientInterface) - => new ServiceDescriptor(new ServiceMoniker(serviceName), clientInterface); + public static ServiceDescriptor CreateRemoteServiceDescriptor(string serviceName, MessagePackSerializerOptions options, Func featureDisplayNameProvider, Type? clientInterface) + => new ServiceDescriptor(new ServiceMoniker(serviceName), options, featureDisplayNameProvider, clientInterface); - public static ServiceDescriptor CreateInProcServiceDescriptor(string serviceName) - => new ServiceDescriptor(new ServiceMoniker(serviceName), clientInterface: null); + public static ServiceDescriptor CreateInProcServiceDescriptor(string serviceName, Func featureDisplayNameProvider) + => new ServiceDescriptor(new ServiceMoniker(serviceName), DefaultOptions, featureDisplayNameProvider, clientInterface: null); protected override ServiceRpcDescriptor Clone() => new ServiceDescriptor(this); @@ -55,16 +68,10 @@ protected override ServiceRpcDescriptor Clone() protected override IJsonRpcMessageFormatter CreateFormatter() => ConfigureFormatter((MessagePackFormatter)base.CreateFormatter()); - private static readonly MessagePackSerializerOptions s_options = StandardResolverAllowPrivate.Options - .WithSecurity(MessagePackSecurity.UntrustedData.WithHashCollisionResistant(false)) - .WithResolver(CompositeResolver.Create( - MessagePackFormatters.GetFormatters(), - new IFormatterResolver[] { ImmutableCollectionMessagePackResolver.Instance, StandardResolverAllowPrivate.Instance })); - - private static MessagePackFormatter ConfigureFormatter(MessagePackFormatter formatter) + private MessagePackFormatter ConfigureFormatter(MessagePackFormatter formatter) { // See https://github.com/neuecc/messagepack-csharp. - formatter.SetMessagePackSerializerOptions(s_options); + formatter.SetMessagePackSerializerOptions(_options); return formatter; } @@ -76,9 +83,7 @@ protected override JsonRpcConnection CreateConnection(JsonRpc jsonRpc) return connection; } - internal static class TestAccessor - { - public static MessagePackSerializerOptions Options => s_options; - } + internal string GetFeatureDisplayName() + => _featureDisplayNameProvider(Moniker.Name); } } diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index 726d1356be1b3..88e49645856a8 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -5,6 +5,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Resources; +using System.Runtime; +using MessagePack; +using MessagePack.Formatters; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.CodeLens; @@ -29,41 +33,61 @@ namespace Microsoft.CodeAnalysis.Remote /// /// Service descriptors of brokered Roslyn ServiceHub services. /// - internal static class ServiceDescriptors + internal sealed 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."; + internal const string ServiceNameTopLevelPrefix = "Microsoft.VisualStudio."; + + internal const string ServiceNameComponentLevelPrefix = "LanguageServices."; private const string InterfaceNamePrefix = "IRemote"; private const string InterfaceNameSuffix = "Service"; - internal static readonly ImmutableDictionary Descriptors = ImmutableDictionary.CreateRange(new[] + public static readonly ServiceDescriptors Instance = new(ServiceNameComponentLevelPrefix, GetFeatureDisplayName, ImmutableArray.Empty, ImmutableArray.Empty, new (Type, Type?)[] { - CreateDescriptors(typeof(IRemoteAssetSynchronizationService)), - CreateDescriptors(typeof(IRemoteAsynchronousOperationListenerService)), - CreateDescriptors(typeof(IRemoteTodoCommentsDiscoveryService), callbackInterface: typeof(IRemoteTodoCommentsDiscoveryService.ICallback)), - CreateDescriptors(typeof(IRemoteDesignerAttributeDiscoveryService), callbackInterface: typeof(IRemoteDesignerAttributeDiscoveryService.ICallback)), - CreateDescriptors(typeof(IRemoteProjectTelemetryService), callbackInterface: typeof(IRemoteProjectTelemetryService.ICallback)), - CreateDescriptors(typeof(IRemoteDiagnosticAnalyzerService)), - CreateDescriptors(typeof(IRemoteSemanticClassificationService)), - CreateDescriptors(typeof(IRemoteSemanticClassificationCacheService)), - CreateDescriptors(typeof(IRemoteDocumentHighlightsService)), - CreateDescriptors(typeof(IRemoteEncapsulateFieldService)), - CreateDescriptors(typeof(IRemoteRenamerService)), - CreateDescriptors(typeof(IRemoteConvertTupleToStructCodeRefactoringService)), - CreateDescriptors(typeof(IRemoteSymbolFinderService), callbackInterface: typeof(IRemoteSymbolFinderService.ICallback)), - CreateDescriptors(typeof(IRemoteFindUsagesService), callbackInterface: typeof(IRemoteFindUsagesService.ICallback)), - CreateDescriptors(typeof(IRemoteNavigateToSearchService)), - CreateDescriptors(typeof(IRemoteMissingImportDiscoveryService), callbackInterface: typeof(IRemoteMissingImportDiscoveryService.ICallback)), - CreateDescriptors(typeof(IRemoteSymbolSearchUpdateService), callbackInterface: typeof(IRemoteSymbolSearchUpdateService.ICallback)), - CreateDescriptors(typeof(IRemoteExtensionMethodImportCompletionService)), - CreateDescriptors(typeof(IRemoteDependentTypeFinderService)), - CreateDescriptors(typeof(IRemoteGlobalNotificationDeliveryService)), - CreateDescriptors(typeof(IRemoteCodeLensReferencesService)), + (typeof(IRemoteAssetSynchronizationService), null), + (typeof(IRemoteAsynchronousOperationListenerService), null), + (typeof(IRemoteTodoCommentsDiscoveryService), typeof(IRemoteTodoCommentsDiscoveryService.ICallback)), + (typeof(IRemoteDesignerAttributeDiscoveryService), typeof(IRemoteDesignerAttributeDiscoveryService.ICallback)), + (typeof(IRemoteProjectTelemetryService), typeof(IRemoteProjectTelemetryService.ICallback)), + (typeof(IRemoteDiagnosticAnalyzerService), null), + (typeof(IRemoteSemanticClassificationService), null), + (typeof(IRemoteSemanticClassificationCacheService), null), + (typeof(IRemoteDocumentHighlightsService), null), + (typeof(IRemoteEncapsulateFieldService), null), + (typeof(IRemoteRenamerService), null), + (typeof(IRemoteConvertTupleToStructCodeRefactoringService), null), + (typeof(IRemoteSymbolFinderService), typeof(IRemoteSymbolFinderService.ICallback)), + (typeof(IRemoteFindUsagesService), typeof(IRemoteFindUsagesService.ICallback)), + (typeof(IRemoteNavigateToSearchService), null), + (typeof(IRemoteMissingImportDiscoveryService), typeof(IRemoteMissingImportDiscoveryService.ICallback)), + (typeof(IRemoteSymbolSearchUpdateService), typeof(IRemoteSymbolSearchUpdateService.ICallback)), + (typeof(IRemoteExtensionMethodImportCompletionService), null), + (typeof(IRemoteDependentTypeFinderService), null), + (typeof(IRemoteGlobalNotificationDeliveryService), null), + (typeof(IRemoteCodeLensReferencesService), null), }); + internal readonly MessagePackSerializerOptions Options; + private readonly ImmutableDictionary _descriptors; + private readonly string _componentLevelPrefix; + private readonly Func _featureDisplayNameProvider; + + public ServiceDescriptors( + string componentLevelPrefix, + Func featureDisplayNameProvider, + ImmutableArray additionalFormatters, + ImmutableArray additionalResolvers, + IEnumerable<(Type serviceInterface, Type? callbackInterface)> interfaces) + { + Options = ServiceDescriptor.DefaultOptions.WithResolver(MessagePackFormatters.CreateResolver(additionalFormatters, additionalResolvers)); + _componentLevelPrefix = componentLevelPrefix; + _featureDisplayNameProvider = featureDisplayNameProvider; + _descriptors = interfaces.ToImmutableDictionary(i => i.serviceInterface, i => CreateDescriptors(i.serviceInterface, i.callbackInterface)); + } + internal static string GetServiceName(Type serviceInterface) { Contract.ThrowIfFalse(serviceInterface.IsInterface); @@ -74,23 +98,26 @@ internal static string GetServiceName(Type serviceInterface) return interfaceName.Substring(InterfaceNamePrefix.Length, interfaceName.Length - InterfaceNamePrefix.Length - InterfaceNameSuffix.Length); } - internal static string GetQualifiedServiceName(Type serviceInterface) - => ServiceNamePrefix + GetServiceName(serviceInterface); + internal string GetQualifiedServiceName(Type serviceInterface) + => ServiceNameTopLevelPrefix + _componentLevelPrefix + GetServiceName(serviceInterface); - private static KeyValuePair CreateDescriptors(Type serviceInterface, Type? callbackInterface = null) + private (ServiceDescriptor, ServiceDescriptor, ServiceDescriptor) CreateDescriptors(Type serviceInterface, Type? callbackInterface) { Contract.ThrowIfFalse(callbackInterface == null || callbackInterface.IsInterface); - var serviceName = GetQualifiedServiceName(serviceInterface); - var descriptor32 = ServiceDescriptor.CreateRemoteServiceDescriptor(serviceName, callbackInterface); - var descriptor64 = ServiceDescriptor.CreateRemoteServiceDescriptor(serviceName + RemoteServiceName.Suffix64, callbackInterface); - var descriptor64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(serviceName + RemoteServiceName.Suffix64 + RemoteServiceName.SuffixServerGC, callbackInterface); - return new(serviceInterface, (descriptor32, descriptor64, descriptor64ServerGC)); + var qualifiedServiceName = GetQualifiedServiceName(serviceInterface); + var descriptor32 = ServiceDescriptor.CreateRemoteServiceDescriptor(qualifiedServiceName, Options, _featureDisplayNameProvider, callbackInterface); + var descriptor64 = ServiceDescriptor.CreateRemoteServiceDescriptor(qualifiedServiceName + RemoteServiceName.Suffix64, Options, _featureDisplayNameProvider, callbackInterface); + var descriptor64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(qualifiedServiceName + RemoteServiceName.Suffix64 + RemoteServiceName.SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); + return (descriptor32, descriptor64, descriptor64ServerGC); } - public static ServiceDescriptor GetServiceDescriptor(Type serviceType, bool isRemoteHost64Bit, bool isRemoteHostServerGC) + public ServiceDescriptor GetServiceDescriptorForServiceFactory(Type serviceType) + => GetServiceDescriptor(serviceType, isRemoteHost64Bit: IntPtr.Size == 8, isRemoteHostServerGC: GCSettings.IsServerGC); + + public ServiceDescriptor GetServiceDescriptor(Type serviceType, bool isRemoteHost64Bit, bool isRemoteHostServerGC) { - var (descriptor32, descriptor64, descriptor64ServerGC) = Descriptors[serviceType]; + var (descriptor32, descriptor64, descriptor64ServerGC) = _descriptors[serviceType]; return (isRemoteHost64Bit, isRemoteHostServerGC) switch { (true, false) => descriptor64, @@ -99,7 +126,31 @@ public static ServiceDescriptor GetServiceDescriptor(Type serviceType, bool isRe }; } - internal static string GetFeatureName(Type serviceInterface) - => RemoteWorkspacesResources.GetResourceString("FeatureName_" + GetServiceName(serviceInterface)); + internal static string GetFeatureDisplayName(string qualifiedServiceName) + { + var prefixLength = qualifiedServiceName.LastIndexOf('.') + 1; + Contract.ThrowIfFalse(prefixLength > 0); + + var suffixLength = qualifiedServiceName.EndsWith(RemoteServiceName.Suffix64, StringComparison.Ordinal) + ? RemoteServiceName.Suffix64.Length + : qualifiedServiceName.EndsWith(RemoteServiceName.Suffix64 + RemoteServiceName.SuffixServerGC, StringComparison.Ordinal) ? RemoteServiceName.Suffix64.Length + RemoteServiceName.SuffixServerGC.Length : 0; + var shortName = qualifiedServiceName.Substring(prefixLength, qualifiedServiceName.Length - prefixLength - suffixLength); + + return RemoteWorkspacesResources.GetResourceString("FeatureName_" + shortName); + } + + internal TestAccessor GetTestAccessor() + => new(this); + + internal readonly struct TestAccessor + { + private readonly ServiceDescriptors _serviceDescriptors; + + internal TestAccessor(ServiceDescriptors serviceDescriptors) + => _serviceDescriptors = serviceDescriptors; + + public ImmutableDictionary Descriptors + => _serviceDescriptors._descriptors; + } } } diff --git a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs index 6a55bfb90dca6..44df16de0fe40 100644 --- a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs +++ b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs @@ -37,9 +37,11 @@ internal sealed partial class ServiceHubRemoteHostClient : RemoteHostClient, IRe private readonly ServiceBrokerClient _serviceBrokerClient; private readonly IErrorReportingService? _errorReportingService; private readonly IRemoteHostClientShutdownCancellationService? _shutdownCancellationService; - private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers; + private readonly IRemoteServiceCallbackDispatcherProvider _callbackDispatcherProvider; private readonly ConnectionPools? _connectionPools; + private readonly bool _isRemoteHost64Bit; + private readonly bool _isRemoteHostServerGC; private ServiceHubRemoteHostClient( HostWorkspaceServices services, @@ -47,7 +49,7 @@ private ServiceHubRemoteHostClient( ServiceBrokerClient serviceBrokerClient, HubClient hubClient, Stream stream, - RemoteServiceCallbackDispatcherRegistry callbackDispatchers) + IRemoteServiceCallbackDispatcherProvider callbackDispatcherProvider) { _connectionPools = new ConnectionPools( connectionFactory: (serviceName, pool, cancellationToken) => CreateConnectionImplAsync(serviceName, callbackTarget: null, pool, cancellationToken), @@ -60,7 +62,7 @@ private ServiceHubRemoteHostClient( _serviceBroker = serviceBroker; _serviceBrokerClient = serviceBrokerClient; _hubClient = hubClient; - _callbackDispatchers = callbackDispatchers; + _callbackDispatcherProvider = callbackDispatcherProvider; _endPoint = new RemoteEndPoint(stream, hubClient.Logger, incomingCallTarget: this); _endPoint.Disconnected += OnDisconnected; _endPoint.UnexpectedExceptionThrown += OnUnexpectedExceptionThrown; @@ -70,6 +72,8 @@ private ServiceHubRemoteHostClient( _serializer = services.GetRequiredService(); _errorReportingService = services.GetService(); _shutdownCancellationService = services.GetService(); + _isRemoteHost64Bit = RemoteHostOptions.IsServiceHubProcess64Bit(services); + _isRemoteHostServerGC = RemoteHostOptions.IsServiceHubProcessServerGC(services); } private void OnUnexpectedExceptionThrown(Exception unexpectedException) @@ -182,16 +186,29 @@ static bool ReportNonFatalWatson(Exception e, CancellationToken cancellationToke } } + /// + /// Creates connection to built-in remote service. + /// public override RemoteServiceConnection CreateConnection(object? callbackTarget) - => new BrokeredServiceConnection( + => CreateConnection(ServiceDescriptors.Instance, _callbackDispatcherProvider, callbackTarget); + + /// + /// This overload is meant to be used by partner teams from their External Access layer. + /// + internal RemoteServiceConnection CreateConnection(ServiceDescriptors descriptors, IRemoteServiceCallbackDispatcherProvider callbackDispatcherProvider, object? callbackTarget) where T : class + { + var descriptor = descriptors.GetServiceDescriptor(typeof(T), _isRemoteHost64Bit, _isRemoteHostServerGC); + var callbackDispatcher = (descriptor.ClientInterface != null) ? callbackDispatcherProvider.GetDispatcher(typeof(T)) : null; + + return new BrokeredServiceConnection( + descriptor, callbackTarget, - _callbackDispatchers, + callbackDispatcher, _serviceBrokerClient, _assetStorage, _errorReportingService, - _shutdownCancellationService, - isRemoteHost64Bit: RemoteHostOptions.IsServiceHubProcess64Bit(_services), - isRemoteHostServerGC: RemoteHostOptions.IsServiceHubProcessServerGC(_services)); + _shutdownCancellationService); + } public override Task CreateConnectionAsync(RemoteServiceName serviceName, object? callbackTarget, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 9c4f9480edb7a..540269ef8e86f 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -22,9 +22,9 @@ namespace Microsoft.CodeAnalysis.Remote { internal sealed class SolutionAssetProvider : ISolutionAssetProvider { - public const string ServiceName = ServiceDescriptors.ServiceNamePrefix + "SolutionAssetProvider"; + public const string ServiceName = ServiceDescriptors.ServiceNameTopLevelPrefix + ServiceDescriptors.ServiceNameComponentLevelPrefix + "SolutionAssetProvider"; - internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceName); + internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceName, ServiceDescriptors.GetFeatureDisplayName); private readonly HostWorkspaceServices _services; diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf index f97f1c5e7a0d0..c7dac4d65d346 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf index ecf572c20f713..09bfcfea811cf 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf index 228f6f6ee0f22..cf070718cdddd 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf index 5ab63d18071e3..e9a6e18a209c8 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf index 6cf061a51baa3..57fe69b8a16e4 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf index fd073a307c2f6..ad90d9ea7072b 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf index 6b0ab62192bd6..bb7f592a3bb50 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf index 50f04aa2353a4..c3b88eefd3ced 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf index 073c643469e6c..34e6c6840b7d9 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf index bf5f449da7c79..eed1e24dadf30 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf index 9ed36a939581e..70196a17b930a 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf index aeb735a275ee5..b3d3f59356af1 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf index fb7640ae97918..292c057d1f281 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf @@ -92,6 +92,11 @@ Semantic classification cache + + Asset provider + Asset provider + + Symbol finder Symbol finder diff --git a/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingBrokeredServiceImplementation.cs b/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingBrokeredServiceImplementation.cs new file mode 100644 index 0000000000000..bd1c5f8fb17d9 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingBrokeredServiceImplementation.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.ServiceHub.Framework; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api +{ + internal static class UnitTestingBrokeredServiceImplementation + { + public static ValueTask RunServiceAsync(Func> implementation, CancellationToken cancellationToken) + => BrokeredServiceBase.RunServiceImplAsync(implementation, cancellationToken); + + public static ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) + => BrokeredServiceBase.RunServiceImplAsync(implementation, cancellationToken); + + public static async ValueTask TryRegisterAnalyzerProviderAsync( + ServiceBrokerClient client, + string analyzerName, + IUnitTestingIncrementalAnalyzerProviderImplementation provider, + CancellationToken cancellationToken) + { + using var rental = await client.GetProxyAsync(RemoteWorkspaceSolutionProviderService.ServiceDescriptor, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(rental.Proxy); + var workspace = await rental.Proxy.GetWorkspaceAsync(WorkspaceKind.RemoteWorkspace, cancellationToken).ConfigureAwait(false); + return UnitTestingIncrementalAnalyzerProvider.TryRegister(workspace, analyzerName, provider); + } + + public static async ValueTask GetSolutionAsync(ServiceBrokerClient client, object solutionInfo, CancellationToken cancellationToken) + { + using var rental = await client.GetProxyAsync(RemoteWorkspaceSolutionProviderService.ServiceDescriptor, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(rental.Proxy); + return await rental.Proxy.GetSolutionAsync((PinnedSolutionInfo)solutionInfo, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingServiceBase.cs b/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingServiceBase.cs index 43b7bc668f294..cfe601440d88f 100644 --- a/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingServiceBase.cs +++ b/src/Workspaces/Remote/ServiceHub/ExternalAccess/UnitTesting/Api/UnitTestingServiceBase.cs @@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api { + [Obsolete] internal abstract class UnitTestingServiceBase : ServiceBase { protected UnitTestingServiceBase( diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs index bc35296e05565..e6abd6cb72e64 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs @@ -68,7 +68,7 @@ internal TService Create( ServiceActivationOptions serviceActivationOptions, IServiceBroker serviceBroker) { - var descriptor = ServiceDescriptors.GetServiceDescriptor(typeof(TService), isRemoteHost64Bit: IntPtr.Size == 8, isRemoteHostServerGC: GCSettings.IsServerGC); + var descriptor = ServiceDescriptors.Instance.GetServiceDescriptorForServiceFactory(typeof(TService)); var serviceHubTraceSource = (TraceSource)hostProvidedServices.GetService(typeof(TraceSource)); var serverConnection = descriptor.WithTraceSource(serviceHubTraceSource).ConstructRpcConnection(pipe); diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs index ab613fdc3017c..6107be2a3462c 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs @@ -60,10 +60,14 @@ protected Task GetSolutionAsync(PinnedSolutionInfo solutionInfo, Cance return workspace.GetSolutionAsync(assetProvider, solutionInfo.SolutionChecksum, solutionInfo.FromPrimaryBranch, solutionInfo.WorkspaceVersion, cancellationToken); } - protected async ValueTask RunServiceAsync(Func> implementation, CancellationToken cancellationToken) + protected ValueTask RunServiceAsync(Func> implementation, CancellationToken cancellationToken) { WorkspaceManager.SolutionAssetCache.UpdateLastActivityTime(); + return RunServiceImplAsync(implementation, cancellationToken); + } + internal static async ValueTask RunServiceImplAsync(Func> implementation, CancellationToken cancellationToken) + { try { return await implementation(cancellationToken).ConfigureAwait(false); @@ -74,10 +78,14 @@ protected async ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) + protected ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) { WorkspaceManager.SolutionAssetCache.UpdateLastActivityTime(); + return RunServiceImplAsync(implementation, cancellationToken); + } + internal static async ValueTask RunServiceImplAsync(Func implementation, CancellationToken cancellationToken) + { try { await implementation(cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Services/WorkspaceSolutionProvider/IRemoteWorkspaceSolutionProviderService.cs b/src/Workspaces/Remote/ServiceHub/Services/WorkspaceSolutionProvider/IRemoteWorkspaceSolutionProviderService.cs new file mode 100644 index 0000000000000..556d3cce12e11 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/WorkspaceSolutionProvider/IRemoteWorkspaceSolutionProviderService.cs @@ -0,0 +1,18 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Remote +{ + /// + /// Brokered service available only in ServiceHub process. + /// + internal interface IRemoteWorkspaceSolutionProviderService + { + ValueTask GetWorkspaceAsync(string workspaceKind, CancellationToken cancellationToken); + ValueTask GetSolutionAsync(PinnedSolutionInfo solutionInfo, CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/WorkspaceSolutionProvider/RemoteWorkspaceSolutionProviderService.cs b/src/Workspaces/Remote/ServiceHub/Services/WorkspaceSolutionProvider/RemoteWorkspaceSolutionProviderService.cs new file mode 100644 index 0000000000000..2fcb84f8b1ede --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/WorkspaceSolutionProvider/RemoteWorkspaceSolutionProviderService.cs @@ -0,0 +1,38 @@ +// 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.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal sealed class RemoteWorkspaceSolutionProviderService : BrokeredServiceBase, IRemoteWorkspaceSolutionProviderService + { + internal sealed class Factory : FactoryBase + { + protected override IRemoteWorkspaceSolutionProviderService CreateService(in ServiceConstructionArguments arguments) + => new RemoteWorkspaceSolutionProviderService(arguments); + } + + public const string ServiceName = ServiceDescriptors.ServiceNameTopLevelPrefix + ServiceDescriptors.ServiceNameComponentLevelPrefix + "WorkspaceSolutionProvider"; + + internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceName, ServiceDescriptors.GetFeatureDisplayName); + + public RemoteWorkspaceSolutionProviderService(in ServiceConstructionArguments arguments) + : base(arguments) + { + } + + public new async ValueTask GetSolutionAsync(PinnedSolutionInfo solutionInfo, CancellationToken cancellationToken) + => await base.GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); + + public ValueTask GetWorkspaceAsync(string workspaceKind, CancellationToken cancellationToken) + { + // other workspace kinds are currently not supported: + Contract.ThrowIfFalse(workspaceKind == WorkspaceKind.RemoteWorkspace); + return new(GetWorkspace()); + } + } +}