File: Services\BrokeredServiceBase.cs
Web Access
Project: ..\..\..\src\Workspaces\Remote\ServiceHub\Microsoft.CodeAnalysis.Remote.ServiceHub.csproj (Microsoft.CodeAnalysis.Remote.ServiceHub)
// 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.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.ServiceHub.Framework;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Remote
{
    /// <summary>
    /// Base type for Roslyn brokered services hosted in ServiceHub.
    /// </summary>
    internal abstract partial class BrokeredServiceBase : IDisposable
    {
        protected readonly TraceSource TraceLogger;
        protected readonly RemoteWorkspaceManager WorkspaceManager;
 
        protected readonly SolutionAssetSource SolutionAssetSource;
        protected readonly ServiceBrokerClient ServiceBrokerClient;
 
        // test data are only available when running tests:
        internal readonly RemoteHostTestData? TestData;
 
        static BrokeredServiceBase()
        {
            if (GCSettings.IsServerGC)
            {
                // Server GC runs processor-affinitized threads with high priority. To avoid interfering with other
                // applications while still allowing efficient out-of-process execution, slightly reduce the process
                // priority when using server GC.
                Process.GetCurrentProcess().TrySetPriorityClass(ProcessPriorityClass.BelowNormal);
            }
 
            // Make encodings that is by default present in desktop framework but not in corefx available to runtime.
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
 
#if DEBUG
            // Make sure debug assertions in ServiceHub result in exceptions instead of the assertion UI
            Trace.Listeners.Clear();
            Trace.Listeners.Add(new ThrowingTraceListener());
#endif
 
            SetNativeDllSearchDirectories();
        }
 
        protected BrokeredServiceBase(in ServiceConstructionArguments arguments)
        {
            var traceSource = (TraceSource?)arguments.ServiceProvider.GetService(typeof(TraceSource));
            Contract.ThrowIfNull(traceSource);
            TraceLogger = 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);
        }
 
        public virtual void Dispose()
            => ServiceBrokerClient.Dispose();
 
        public RemoteWorkspace GetWorkspace()
            => WorkspaceManager.GetWorkspace();
 
        public SolutionServices GetWorkspaceServices()
            => GetWorkspace().Services.SolutionServices;
 
        protected void Log(TraceEventType errorType, string message)
            => TraceLogger.TraceEvent(errorType, 0, $"{GetType()}: {message}");
 
        protected async ValueTask<T> RunWithSolutionAsync<T>(
            Checksum solutionChecksum,
            Func<Solution, ValueTask<T>> implementation,
            CancellationToken cancellationToken)
        {
            var workspace = GetWorkspace();
            var assetProvider = workspace.CreateAssetProvider(solutionChecksum, WorkspaceManager.SolutionAssetCache, SolutionAssetSource);
            var (_, result) = await workspace.RunWithSolutionAsync(
                assetProvider,
                solutionChecksum,
                implementation,
                cancellationToken).ConfigureAwait(false);
 
            return result;
        }
 
        protected ValueTask<T> RunServiceAsync<T>(Func<CancellationToken, ValueTask<T>> implementation, CancellationToken cancellationToken)
        {
            WorkspaceManager.SolutionAssetCache.UpdateLastActivityTime();
            return RunServiceImplAsync(implementation, cancellationToken);
        }
 
        protected ValueTask<T> RunServiceAsync<T>(
            Checksum solutionChecksum, Func<Solution, ValueTask<T>> implementation, CancellationToken cancellationToken)
        {
            return RunServiceAsync(
                c => RunWithSolutionAsync(solutionChecksum, implementation, c), cancellationToken);
        }
 
        internal static async ValueTask<T> RunServiceImplAsync<T>(Func<CancellationToken, ValueTask<T>> implementation, CancellationToken cancellationToken)
        {
            try
            {
                return await implementation(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        protected ValueTask RunServiceAsync(Func<CancellationToken, ValueTask> implementation, CancellationToken cancellationToken)
        {
            WorkspaceManager.SolutionAssetCache.UpdateLastActivityTime();
            return RunServiceImplAsync(implementation, cancellationToken);
        }
 
        protected ValueTask RunServiceAsync(
            Checksum solutionChecksum, Func<Solution, ValueTask> implementation, CancellationToken cancellationToken)
        {
            return RunServiceAsync(
                async c =>
                {
                    await RunWithSolutionAsync(
                        solutionChecksum,
                        async s =>
                        {
                            await implementation(s).ConfigureAwait(false);
                            // bridge this void 'implementation' callback to the non-void type the underlying api needs.
                            return false;
                        }, c).ConfigureAwait(false);
                }, cancellationToken);
        }
 
        protected ValueTask RunServiceAsync(
            Checksum solutionChecksum1,
            Checksum solutionChecksum2,
            Func<Solution, Solution, ValueTask> implementation,
            CancellationToken cancellationToken)
        {
            return RunServiceAsync(
                solutionChecksum1,
                s1 => RunServiceAsync(
                    solutionChecksum2,
                    s2 => implementation(s1, s2),
                    cancellationToken),
                cancellationToken);
        }
 
        internal static async ValueTask RunServiceImplAsync(Func<CancellationToken, ValueTask> implementation, CancellationToken cancellationToken)
        {
            try
            {
                await implementation(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
#if TODO // https://github.com/microsoft/vs-streamjsonrpc/issues/789
        internal static async ValueTask<TOptions> GetClientOptionsAsync<TOptions, TCallbackInterface>(
            RemoteCallback<TCallbackInterface> callback,
            RemoteServiceCallbackId callbackId,
            HostLanguageServices languageServices,
            CancellationToken cancellationToken)
            where TCallbackInterface : class, IRemoteOptionsCallback<TOptions>
        {
            var cache = ImmutableDictionary<string, AsyncLazy<TOptions>>.Empty;
            var lazyOptions = ImmutableInterlocked.GetOrAdd(ref cache, languageServices.Language, _ => new AsyncLazy<TOptions>(GetRemoteOptions, cacheResult: true));
            return await lazyOptions.GetValueAsync(cancellationToken).ConfigureAwait(false);
 
            Task<TOptions> GetRemoteOptions(CancellationToken cancellationToken)
                => callback.InvokeAsync((callback, cancellationToken) => callback.GetOptionsAsync(callbackId, languageServices.Language, cancellationToken), cancellationToken).AsTask();
        }
#endif
 
        private static void SetNativeDllSearchDirectories()
        {
            if (PlatformInformation.IsWindows)
            {
                // Set LoadLibrary search directory to %VSINSTALLDIR%\Common7\IDE so that the compiler
                // can P/Invoke to Microsoft.DiaSymReader.Native when emitting Windows PDBs.
                //
                // The AppDomain base directory is specified in VisualStudio\Setup\codeAnalysisService.servicehub.service.json
                // to be the directory where devenv.exe is -- which is exactly the directory we need to add to the search paths:
                //
                //   "appBasePath": "%VSAPPIDDIR%"
                //
 
                var loadDir = AppDomain.CurrentDomain.BaseDirectory!;
 
                try
                {
                    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
                    static extern IntPtr AddDllDirectory(string directory);
 
                    if (AddDllDirectory(loadDir) == IntPtr.Zero)
                    {
                        throw new Win32Exception();
                    }
                }
                catch (EntryPointNotFoundException)
                {
                    // AddDllDirectory API might not be available on Windows 7.
                    Environment.SetEnvironmentVariable("MICROSOFT_DIASYMREADER_NATIVE_ALT_LOAD_PATH", loadDir);
                }
            }
        }
    }
}