File: AssemblyDependency\Node\OutOfProcRarNodeEndpoint.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
 
namespace Microsoft.Build.Tasks.AssemblyDependency
{
    /// <summary>
    /// Implements a single instance of a pipe server which executes the ResolveAssemblyReference task.
    /// </summary>
    internal sealed class OutOfProcRarNodeEndpoint : IDisposable
    {
        private readonly int _endpointId;
        private readonly NodePipeServer _pipeServer;
        private readonly RarNodeBuildEngine _buildEngine = new();
 
        internal OutOfProcRarNodeEndpoint(int endpointId, SharedConfig config)
        {
            _endpointId = endpointId;
            _pipeServer = new NodePipeServer(config.PipeName, config.Handshake, config.MaxNumberOfServerInstances);
            _pipeServer.RegisterPacketFactory(config.PacketFactory);
        }
 
        public void Dispose() => _pipeServer.Dispose();
 
        internal static SharedConfig CreateConfig(int maxNumberOfServerInstances)
        {
            ServerNodeHandshake handshake = new(HandshakeOptions.None);
            NodePacketFactory packetFactory = new();
            packetFactory.RegisterPacketHandler(NodePacketType.RarNodeExecuteRequest, static t => new RarNodeExecuteRequest(t), null);
 
            return new SharedConfig(
                pipeName: NamedPipeUtil.GetRarNodeEndpointPipeName(handshake),
                handshake,
                packetFactory,
                maxNumberOfServerInstances);
        }
 
        internal async Task RunAsync(CancellationToken cancellationToken = default)
        {
            CommunicationsUtilities.Trace("({0}) Starting RAR endpoint.", _endpointId);
 
            try
            {
                await RunInternalAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                // Swallow cancellation excpetions for now. We're using this as a simple way to gracefully shutdown the
                // endpoint, instead of having to implement separate Start / Stop methods and deferring to the caller.
                // Can reevaluate if we need more granular control over cancellation vs shutdown.
                CommunicationsUtilities.Trace("({0}) RAR endpoint stopped due to cancellation.", _endpointId);
            }
        }
 
        private async Task RunInternalAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                if (!_pipeServer.IsConnected)
                {
                    LinkStatus linkStatus = await _pipeServer.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false);
 
                    if (linkStatus != LinkStatus.Active)
                    {
                        continue;
                    }
                }
 
                try
                {
                    INodePacket packet = await _pipeServer.ReadPacketAsync(cancellationToken).ConfigureAwait(false);
                    NodePacketType packetType = packet.Type;
 
                    CommunicationsUtilities.Trace("({0}) Received request.", _endpointId);
 
                    switch (packet.Type)
                    {
                        case NodePacketType.RarNodeEndpointConfiguration:
                            // TODO: Pass in client state such as immutable directories, environment variables, ect.
                            break;
                        case NodePacketType.RarNodeExecuteRequest:
                            CommunicationsUtilities.Trace("({0}) Executing RAR...", _endpointId);
 
                            RarNodeExecuteRequest request = (RarNodeExecuteRequest)packet;
                            ResolveAssemblyReference rarTask = new();
                            request.SetTaskInputs(rarTask, _buildEngine);
 
                            bool success = rarTask.Execute();
 
                            await _pipeServer.WritePacketAsync(new RarNodeExecuteResponse(rarTask, success), cancellationToken).ConfigureAwait(false);
 
                            CommunicationsUtilities.Trace("({0}) Completed RAR request.", _endpointId);
                            break;
                        case NodePacketType.NodeShutdown:
                            // Although the client has already disconnected, it is still necessary to Disconnect() so the
                            // pipe can transition into PipeState.Disconnected, which is treated as an intentional pipe break.
                            // Otherwise, all future operations on the pipe will throw an exception.
                            CommunicationsUtilities.Trace("({0}) RAR client disconnected.", _endpointId);
                            _pipeServer.Disconnect();
                            break;
                        default:
                            ErrorUtilities.ThrowInternalError($"Received unexpected packet type {packetType}");
                            break;
                    }
                }
                catch (Exception e) when (e is not OperationCanceledException)
                {
                    CommunicationsUtilities.Trace("({0}) Exception while executing RAR request: {1}", _endpointId, e);
                }
            }
 
            _pipeServer.Disconnect();
        }
 
        /// <summary>
        /// Configuration to reuse for all endpoints in a given RAR node process.
        /// </summary>
        internal readonly struct SharedConfig(
            string pipeName,
            ServerNodeHandshake handshake,
            NodePacketFactory packetFactory,
            int maxNumberOfServerInstances)
        {
            public string PipeName { get; } = pipeName;
 
            public ServerNodeHandshake Handshake { get; } = handshake;
 
            public NodePacketFactory PacketFactory { get; } = packetFactory;
 
            public int MaxNumberOfServerInstances { get; } = maxNumberOfServerInstances;
        }
    }
}