File: NamedPipeClientConnection.cs
Web Access
Project: ..\..\..\src\Compilers\Server\VBCSCompiler\AnyCpu\VBCSCompiler.csproj (VBCSCompiler)
// 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 Roslyn.Utilities;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Runtime.CompilerServices;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CommandLine;
using System.Security.AccessControl;
namespace Microsoft.CodeAnalysis.CompilerServer
{
    internal sealed class NamedPipeClientConnection : IClientConnection
    {
        private CancellationTokenSource DisconnectCancellationTokenSource { get; } = new CancellationTokenSource();
        private TaskCompletionSource<object> DisconnectTaskCompletionSource { get; } = new TaskCompletionSource<object>();
 
        public NamedPipeServerStream Stream { get; }
        public ICompilerServerLogger Logger { get; }
        public bool IsDisposed { get; private set; }
 
        public Task DisconnectTask => DisconnectTaskCompletionSource.Task;
 
        internal NamedPipeClientConnection(NamedPipeServerStream stream, ICompilerServerLogger logger)
        {
            Stream = stream;
            Logger = logger;
        }
 
        public void Dispose()
        {
            if (!IsDisposed)
            {
                try
                {
                    DisconnectTaskCompletionSource.TrySetResult(new object());
                    DisconnectCancellationTokenSource.Cancel();
                    Stream.Close();
                }
                catch (Exception ex)
                {
                    Logger.LogException(ex, $"Error closing client connection");
                }
 
                IsDisposed = true;
            }
        }
 
        public async Task<BuildRequest> ReadBuildRequestAsync(CancellationToken cancellationToken)
        {
            var request = await BuildRequest.ReadAsync(Stream, cancellationToken).ConfigureAwait(false);
 
            // Now that we've read data from the stream we can validate the identity.
            if (!NamedPipeUtil.CheckClientElevationMatches(Stream))
            {
                throw new Exception("Client identity does not match server identity.");
            }
 
            // The result is deliberately discarded here. The idea is to kick off the monitor code and 
            // when it completes it will trigger the task. Don't want to block on that here.
            _ = MonitorDisconnect();
 
            return request;
 
            async Task MonitorDisconnect()
            {
                try
                {
                    await BuildServerConnection.MonitorDisconnectAsync(Stream, request.RequestId, Logger, DisconnectCancellationTokenSource.Token).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    Logger.LogException(ex, $"Error monitoring disconnect {request.RequestId}");
                }
                finally
                {
                    DisconnectTaskCompletionSource.TrySetResult(this);
                }
            }
        }
 
        public Task WriteBuildResponseAsync(BuildResponse response, CancellationToken cancellationToken) => response.WriteAsync(Stream, cancellationToken);
    }
}