File: Remote\VisualStudioRemoteHostClientProvider.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_stpoydsm_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Shell.ServiceBroker;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
using VSThreading = Microsoft.VisualStudio.Threading;
 
namespace Microsoft.VisualStudio.LanguageServices.Remote;
 
internal sealed class VisualStudioRemoteHostClientProvider : IRemoteHostClientProvider
{
    [ExportWorkspaceServiceFactory(typeof(IRemoteHostClientProvider), [WorkspaceKind.Host, WorkspaceKind.Preview]), Shared]
    internal sealed class Factory : IWorkspaceServiceFactory
    {
        private readonly VisualStudioWorkspace _vsWorkspace;
        private readonly IVsService<IBrokeredServiceContainer> _brokeredServiceContainer;
        private readonly AsynchronousOperationListenerProvider _listenerProvider;
        private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers;
        private readonly IGlobalOptionService _globalOptions;
        private readonly IThreadingContext _threadingContext;
 
        private readonly object _gate = new();
        private VisualStudioRemoteHostClientProvider? _cachedVSInstance;
 
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public Factory(
            VisualStudioWorkspace vsWorkspace,
            IVsService<SVsBrokeredServiceContainer, IBrokeredServiceContainer> brokeredServiceContainer,
            AsynchronousOperationListenerProvider listenerProvider,
            IGlobalOptionService globalOptions,
            IThreadingContext threadingContext,
            [ImportMany] IEnumerable<Lazy<IRemoteServiceCallbackDispatcher, RemoteServiceCallbackDispatcherRegistry.ExportMetadata>> callbackDispatchers)
        {
            _globalOptions = globalOptions;
            _vsWorkspace = vsWorkspace;
            _brokeredServiceContainer = brokeredServiceContainer;
            _listenerProvider = listenerProvider;
            _threadingContext = threadingContext;
            _callbackDispatchers = new RemoteServiceCallbackDispatcherRegistry(callbackDispatchers);
        }
 
        [Obsolete(MefConstruction.FactoryMethodMessage, error: true)]
        public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
        {
            Debug.Assert(workspaceServices.Workspace is VisualStudioWorkspace or PreviewWorkspace);
 
            // We don't want to bring up the OOP process in a VS cloud environment client instance
            // Avoids proffering brokered services on the client instance.
            if (!_globalOptions.GetOption(RemoteHostOptionsStorage.OOP64Bit) ||
                // If the host services are different, then we can't use the cached VS instance, fall back to in-proc.
                // This can happen for preview workspace in Tools|Options.
                workspaceServices.SolutionServices.WorkspaceServices.HostServices != _vsWorkspace.Services.HostServices ||
                workspaceServices.GetRequiredService<IWorkspaceContextService>().IsCloudEnvironmentClient())
            {
                // Run code in the current process
                return new DefaultRemoteHostClientProvider();
            }
 
            lock (_gate)
            {
                // If we have a cached vs instance, then we can return that instance since we know they have the same host services.
                // Otherwise, create and cache an instance based on vs workspace for future callers with same services.
                if (_cachedVSInstance is null)
                    _cachedVSInstance = new VisualStudioRemoteHostClientProvider(_vsWorkspace.Services.SolutionServices, _globalOptions, _brokeredServiceContainer, _threadingContext, _listenerProvider, _callbackDispatchers);
 
                return _cachedVSInstance;
            }
        }
    }
 
    public readonly SolutionServices Services;
    private readonly IGlobalOptionService _globalOptions;
    private readonly VSThreading.AsyncLazy<RemoteHostClient?> _lazyClient;
    private readonly IVsService<IBrokeredServiceContainer> _brokeredServiceContainer;
    private readonly AsynchronousOperationListenerProvider _listenerProvider;
    private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers;
    private readonly TaskCompletionSource<bool> _clientCreationSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
 
    private VisualStudioRemoteHostClientProvider(
        SolutionServices services,
        IGlobalOptionService globalOptions,
        IVsService<IBrokeredServiceContainer> brokeredServiceContainer,
        IThreadingContext threadingContext,
        AsynchronousOperationListenerProvider listenerProvider,
        RemoteServiceCallbackDispatcherRegistry callbackDispatchers)
    {
        Services = services;
        _globalOptions = globalOptions;
        _brokeredServiceContainer = brokeredServiceContainer;
        _listenerProvider = listenerProvider;
        _callbackDispatchers = callbackDispatchers;
 
        // using VS AsyncLazy here since Roslyn's is not compatible with JTF. 
        // Our ServiceBroker services may be invoked by other VS components under JTF.
        _lazyClient = new VSThreading.AsyncLazy<RemoteHostClient?>(CreateHostClientAsync, threadingContext.JoinableTaskFactory);
    }
 
    private async Task<RemoteHostClient?> CreateHostClientAsync()
    {
        try
        {
            var brokeredServiceContainer = await _brokeredServiceContainer.GetValueAsync().ConfigureAwait(false);
            var serviceBroker = brokeredServiceContainer.GetFullAccessServiceBroker();
 
            var configuration =
                _globalOptions.GetOption(RemoteHostOptionsStorage.OOPServerGCFeatureFlag) ? RemoteProcessConfiguration.ServerGC : 0;
 
            // VS AsyncLazy does not currently support cancellation:
            var client = await ServiceHubRemoteHostClient.CreateAsync(Services, configuration, _listenerProvider, serviceBroker, _callbackDispatchers, CancellationToken.None).ConfigureAwait(false);
 
            // proffer in-proc brokered services:
            _ = brokeredServiceContainer.Proffer(SolutionAssetProvider.ServiceDescriptor, (_, _, _, _) => ValueTaskFactory.FromResult<object?>(new SolutionAssetProvider(Services)));
 
            return client;
        }
        catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e))
        {
            return null;
        }
        finally
        {
            _clientCreationSource.SetResult(true);
        }
    }
 
    public Task<RemoteHostClient?> TryGetRemoteHostClientAsync(CancellationToken cancellationToken)
        => _lazyClient.GetValueAsync(cancellationToken);
 
    public Task WaitForClientCreationAsync(CancellationToken cancellationToken)
        => _clientCreationSource.Task.WithCancellation(cancellationToken);
}