| File: CoordinatorServer.Connection.cs | Web Access |
| Project: src\msbuild\src\MSBuild.Coordinator\MSBuild.Coordinator.csproj (MSBuild.Coordinator) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; using System.IO; using System.IO.Pipes; using System.Text; using Microsoft.Build.Framework.Coordinator; namespace Microsoft.Build.Coordinator; internal sealed partial class CoordinatorServer { /// <summary> /// Owns a newly-connected client pipe during server-side handshake and request negotiation. /// </summary> private sealed class Connection : IDisposable { private NamedPipeServerStream? _pipeStream; private BinaryReader? _reader; private BinaryWriter? _writer; /// <summary> /// Gets the unique identifier received during the client handshake. /// </summary> public Guid Id { get; private set; } /// <summary> /// Gets the process ID received during the client handshake. /// </summary> public int ProcessId { get; private set; } /// <summary> /// Gets the capabilities advertised by the client during handshake. /// </summary> public ImmutableArray<string> ClientCapabilities { get; private set; } private Connection(NamedPipeServerStream pipeStream) { _pipeStream = pipeStream; _reader = new BinaryReader(pipeStream, Encoding.UTF8, leaveOpen: true); _writer = new BinaryWriter(pipeStream, Encoding.UTF8, leaveOpen: true); } /// <summary> /// Creates a handshaken server connection over an already-connected pipe. /// </summary> /// <returns> /// A connection that owns the pipe, reader, and writer; or <see langword="null"/> if the handshake failed. /// </returns> public static Connection? TryCreate(NamedPipeServerStream pipeStream, ICoordinatorDebugOutput output) { var connection = new Connection(pipeStream); if (connection.TryHandshake(output)) { return connection; } connection.Dispose(); return null; } private bool TryHandshake(ICoordinatorDebugOutput output) { try { ClientMessage clientMessage = ReadClientMessage(); if (clientMessage is not ClientHandshakeMessage handshake) { output.WriteLine($"CoordinatorServer: Rejected client — first message was {clientMessage.GetType().Name}"); WriteServerMessage(new ErrorMessage("First message must be Handshake")); return false; } Id = handshake.ConnectionId; ProcessId = handshake.ProcessId; ClientCapabilities = handshake.Capabilities; output.WriteLine($"CoordinatorServer: Handshake received (ConnectionId {Id}, PID {ProcessId}, Capabilities: [{string.Join(", ", ClientCapabilities)}])"); if (ProcessId <= 0) { output.WriteLine($"CoordinatorServer: Rejected client — invalid handshake (PID={ProcessId})"); WriteServerMessage(new ErrorMessage("Invalid handshake: ProcessId must be > 0")); return false; } WriteServerMessage(new ServerHandshakeMessage([Capabilities.NestedGrants])); return true; } catch (Exception ex) { output.WriteLine($"CoordinatorServer: Exception during handshake: {ex}"); return false; } } /// <summary> /// Transfers ownership of the pipe, reader, and writer to the caller. /// </summary> public (NamedPipeServerStream PipeStream, BinaryReader Reader, BinaryWriter Writer) TransferOwnership() { Assumed.NotNull(_pipeStream); Assumed.NotNull(_reader); Assumed.NotNull(_writer); var result = (_pipeStream, _reader, _writer); _pipeStream = null; _reader = null; _writer = null; return result; } public void Dispose() { try { _writer?.Dispose(); } catch (IOException) { // The pipe may already be broken if the client disconnected. } _reader?.Dispose(); _pipeStream?.Dispose(); } /// <summary> /// Reads the next client message from the coordinator pipe. /// </summary> public ClientMessage ReadClientMessage() { Assumed.NotNull(_reader); return _reader.ReadClientMessage(); } /// <summary> /// Writes a server message to the coordinator pipe. /// </summary> public void WriteServerMessage(ServerMessage message) { Assumed.NotNull(_writer); _writer.Write(message); } } }