| File: BackEnd\Components\Communications\NodeProviderOutOfProc.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// 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.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.Build.Exceptions; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; #nullable disable namespace Microsoft.Build.BackEnd { /// <summary> /// The provider for out-of-proc nodes. This manages the lifetime of external MSBuild.exe processes /// which act as child nodes for the build system. /// </summary> internal class NodeProviderOutOfProc : NodeProviderOutOfProcBase, INodeProvider { /// <summary> /// A mapping of all the nodes managed by this provider. /// </summary> private ConcurrentDictionary<int, NodeContext> _nodeContexts; /// <summary> /// Constructor. /// </summary> private NodeProviderOutOfProc() { } #region INodeProvider Members /// <summary> /// Returns the node provider type. /// </summary> public NodeProviderType ProviderType { [DebuggerStepThrough] get { return NodeProviderType.OutOfProc; } } /// <summary> /// Returns the number of available nodes. /// </summary> public int AvailableNodes { get { return ComponentHost.BuildParameters.MaxNodeCount - _nodeContexts.Count; } } /// <summary> /// Magic number sent by the host to the client during the handshake. /// Derived from the binary timestamp to avoid mixing binary versions, /// Is64BitProcess to avoid mixing bitness, and enableNodeReuse to /// ensure that a /nr:false build doesn't reuse clients left over from /// a prior /nr:true build. The enableLowPriority flag is to ensure that /// a build with /low:false doesn't reuse clients left over for a prior /// /low:true build. /// </summary> /// <param name="enableNodeReuse">Is reuse of build nodes allowed?</param> /// <param name="enableLowPriority">Is the build running at low priority?</param> internal static Handshake GetHandshake(bool enableNodeReuse, bool enableLowPriority) { CommunicationsUtilities.Trace($"""MSBUILDNODEHANDSHAKESALT="{Traits.MSBuildNodeHandshakeSalt}", msbuildDirectory="{BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32}", enableNodeReuse={enableNodeReuse}, enableLowPriority={enableLowPriority}"""); return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, taskHostParameters: TaskHostParameters.Empty, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture(), nodeReuse: enableNodeReuse, lowPriority: enableLowPriority)); } /// <summary> /// Instantiates a new MSBuild processes acting as a child nodes or connect to existing ones. /// </summary> public IList<NodeInfo> CreateNodes(int nextNodeId, INodePacketFactory factory, Func<NodeInfo, NodeConfiguration> configurationFactory, int numberOfNodesToCreate) { ArgumentNullException.ThrowIfNull(factory); // This can run concurrently. To be properly detect internal bug when we create more nodes than allowed // we add into _nodeContexts premise of future node and verify that it will not cross limits. if (_nodeContexts.Count + numberOfNodesToCreate > ComponentHost.BuildParameters.MaxNodeCount) { return InternalError.Throw<IList<NodeInfo>>($"Exceeded max node count of '{ComponentHost.BuildParameters.MaxNodeCount}', current count is '{_nodeContexts.Count}' "); } ConcurrentBag<NodeInfo> nodes = new(); Handshake hostHandshake = new(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, taskHostParameters: TaskHostParameters.Empty, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture(), nodeReuse: ComponentHost.BuildParameters.EnableNodeReuse, lowPriority: ComponentHost.BuildParameters.LowPriority)); // Start the new process. We pass in a node mode with a node number of 1, to indicate that we // want to start up just a standard MSBuild out-of-proc node. // Note: We need to always pass /nodeReuse to ensure the value for /nodeReuse from msbuild.rsp // (next to msbuild.exe) is ignored. NodeLaunchData nodeLaunchData = new( MSBuildLocation: null, CommandLineArgs: $"/noautoresponse /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcNode)} /nodeReuse:{ComponentHost.BuildParameters.EnableNodeReuse.ToString().ToLower()} /low:{ComponentHost.BuildParameters.LowPriority.ToString().ToLower()}", Handshake: hostHandshake, EnvironmentOverrides: DotnetHostEnvironmentHelper.CreateDotnetRootEnvironmentOverrides()); CommunicationsUtilities.Trace($"Starting to acquire {numberOfNodesToCreate} new or existing node(s) to establish nodes from ID {nextNodeId} to {nextNodeId + numberOfNodesToCreate - 1}..."); IList<NodeContext> nodeContexts = GetNodes(nodeLaunchData, nextNodeId, factory, NodeContextCreated, NodeContextTerminated, numberOfNodesToCreate); if (nodeContexts.Count > 0) { return nodeContexts .Select(nc => new NodeInfo(nc.NodeId, ProviderType)) .ToList(); } throw new BuildAbortedException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("CouldNotConnectToMSBuildExe", ComponentHost.BuildParameters.NodeExeLocation)); void NodeContextCreated(NodeContext context) { NodeInfo nodeInfo = new NodeInfo(context.NodeId, ProviderType); _nodeContexts[context.NodeId] = context; // Start the asynchronous read. context.BeginAsyncPacketRead(); // Configure the node. context.SendData(configurationFactory(nodeInfo)); } } /// <summary> /// Sends data to the specified node. /// </summary> /// <param name="nodeId">The node to which data shall be sent.</param> /// <param name="packet">The packet to send.</param> public void SendData(int nodeId, INodePacket packet) { Assumed.True(_nodeContexts.ContainsKey(nodeId), $"Invalid node id specified: {nodeId}."); SendData(_nodeContexts[nodeId], packet); } /// <summary> /// Shuts down all of the connected managed nodes. /// </summary> /// <param name="enableReuse">Flag indicating if nodes should prepare for reuse.</param> public void ShutdownConnectedNodes(bool enableReuse) { // Send the build completion message to the nodes, causing them to shutdown or reset. var contextsToShutDown = new List<NodeContext>(_nodeContexts.Values); ShutdownConnectedNodes(contextsToShutDown, enableReuse); } /// <summary> /// Shuts down all of the managed nodes permanently. /// </summary> public void ShutdownAllNodes() { // If no BuildParameters were specified for this build, // we must be trying to shut down idle nodes from some // other, completed build. If they're still around, // they must have been started with node reuse. bool nodeReuse = ComponentHost.BuildParameters?.EnableNodeReuse ?? true; // To avoid issues with mismatched priorities not shutting // down all the nodes on exit, we will attempt to shutdown // all matching nodes with and without the priority bit set. // This means we need both versions of the handshake. ShutdownAllNodes(nodeReuse, NodeContextTerminated); } #endregion #region IBuildComponent Members /// <summary> /// Initializes the component. /// </summary> /// <param name="host">The component host.</param> public void InitializeComponent(IBuildComponentHost host) { this.ComponentHost = host; _nodeContexts = new ConcurrentDictionary<int, NodeContext>(); } /// <summary> /// Shuts down the component /// </summary> public void ShutdownComponent() { } #endregion /// <summary> /// Static factory for component creation. /// </summary> internal static IBuildComponent CreateComponent(BuildComponentType componentType) { Assumed.Equal(componentType, BuildComponentType.OutOfProcNodeProvider, $"Factory cannot create components of type {componentType}"); return new NodeProviderOutOfProc(); } /// <summary> /// Method called when a context terminates. /// </summary> private void NodeContextTerminated(int nodeId) { _nodeContexts.TryRemove(nodeId, out _); } public IEnumerable<Process> GetProcesses() { return _nodeContexts.Values.Select(context => context.Process); } } }