File: Engine\NodeManager.cs
Web Access
Project: ..\..\..\src\Deprecated\Engine\Microsoft.Build.Engine.csproj (Microsoft.Build.Engine)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE
// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL
// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED.
 
using System;
using System.Collections.Generic;
using Microsoft.Build.BuildEngine.Shared;
using System.Threading;
 
namespace Microsoft.Build.BuildEngine
{
    /// <summary>
    /// This class is resposible for managing the node providers - starting, stopping and sharing them.
    /// Although child nodes have a NodeManager, theirs do nothing as they never has any NodeProviders registered with them.
    /// </summary>
    internal class NodeManager
    {
        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        internal NodeManager(int cpuCount, bool childMode, Engine parentEngine)
        {
            nodeList = new List<ProvidersNodeInformation>();
            nodeProviders = new List<INodeProvider>();
 
            this.parentEngine = parentEngine;
 
            this.statusMessageReceived = new ManualResetEvent(false);
 
            // Create the inproc node, this means that there will always be one node, node 0
            if (taskExecutionModule == null)
            {
                taskExecutionModule = new TaskExecutionModule(parentEngine.EngineCallback,
                    cpuCount == 1 && !childMode ? TaskExecutionModule.TaskExecutionModuleMode.SingleProcMode :
                                                   TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode, parentEngine.ProfileBuild);
            }
        }
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Register an instantiated INodeProvider with the node manager. The node manager will query the nodeprovider
        /// for a list of its node descriptions, and add these nodes to a master list of nodes which can be used
        /// by the scheduler. QUESTION: Do we allow duplicate Node Providers?
        /// </summary>
        /// <param name="providerToRegister"></param>
        /// <returns></returns>
        internal bool RegisterNodeProvider(INodeProvider nodeProviderToRegister)
        {
            ErrorUtilities.VerifyThrowArgumentNull(nodeProviderToRegister, nameof(nodeProviderToRegister));
 
            INodeDescription[] nodeDescriptions = nodeProviderToRegister.QueryNodeDescriptions();
 
            int[] nodeIds = new int[nodeDescriptions.Length];
            for (int i = 0; i < nodeIds.Length; i++)
            {
                nodeIds[i] = parentEngine.GetNextNodeId();
            }
            nodeProviderToRegister.AssignNodeIdentifiers(nodeIds);
 
            // Go through all of the nodes as described by nodeDescriptions and add them to out list of nodes
            for (int i = 0; i < nodeDescriptions.Length; i++)
            {
                ProvidersNodeInformation nodeToAddFromProvider =
                    new ProvidersNodeInformation(i, nodeIds[i], nodeDescriptions[i], nodeProviderToRegister);
                nodeList.Add(nodeToAddFromProvider);
            }
 
            nodeProviders.Add(nodeProviderToRegister);
 
            return true;
        }
 
        /// <summary>
        /// Provide an array of INodeDescriptionsof the node provided by the node provider for the node. The index of the description
        /// is the node index to which the description matches
        /// </summary>
        /// <returns></returns>
        internal INodeDescription[] GetNodeDescriptions()
        {
            // The number of node descriptions is the number of nodes from all of the node providers, plus one for the default "0" node
            INodeDescription[] nodeDescription = new INodeDescription[nodeList.Count + 1];
            nodeDescription[0] = null;
            for (int nodeListIndex = 0; nodeListIndex < nodeList.Count; nodeListIndex++)
            {
                ProvidersNodeInformation nodeInfo = nodeList[nodeListIndex];
                // +1 because the node description already has the 0 element set to null
                nodeDescription[nodeListIndex + 1] = nodeInfo.Description;
            }
            return nodeDescription;
        }
 
        /// <summary>
        /// Register node logger with all currently available providers
        /// </summary>
        /// <param name="loggerDescription"></param>
        internal void RegisterNodeLogger(LoggerDescription loggerDescription)
        {
            foreach (INodeProvider nodeProvider in nodeProviders)
            {
                nodeProvider.RegisterNodeLogger(loggerDescription);
            }
        }
 
        /// <summary>
        /// Request status from all nodes in the system
        /// </summary>
        /// <param name="responseTimeout"></param>
        /// <returns></returns>
        internal NodeStatus[] RequestStatusForNodes(int responseTimeout)
        {
            int requestId = 0;
 
            statusForNodes = new NodeStatus[nodeList.Count];
            statusReplyCount = 0;
            statusMessageReceived.Reset();
 
            // Request status from all registered nodes
            for (int i = 0; i < nodeList.Count; i++)
            {
                nodeList[i].NodeProvider.RequestNodeStatus(nodeList[i].NodeIndex, requestId);
            }
 
            long startTime = DateTime.Now.Ticks;
 
            while (statusReplyCount < nodeList.Count)
            {
                if (statusMessageReceived.WaitOne(responseTimeout, false))
                {
                    // We received another reply
                    statusMessageReceived.Reset();
                    // Calculate the time remaining and only continue if there is time left
                    TimeSpan timeSpent = new TimeSpan(DateTime.Now.Ticks - startTime);
                    startTime = DateTime.Now.Ticks;
                    responseTimeout -= (int)timeSpent.TotalMilliseconds;
                    if (responseTimeout <= 0)
                    {
                        Console.WriteLine("Response time out out exceeded :" + DateTime.Now.Ticks);
                        break;
                    }
                }
                else
                {
                    // Timed out waiting for the response from the node
                    Console.WriteLine("Response time out out exceeded:" + DateTime.Now.Ticks);
                    break;
                }
            }
 
            return statusForNodes;
        }
 
        internal void PostNodeStatus(int nodeId, NodeStatus nodeStatus)
        {
            ErrorUtilities.VerifyThrow(nodeStatus.RequestId != NodeStatus.UnrequestedStatus,
                                        "Node manager should not receive unrequested status");
 
            NodeStatus[] currentStatus = statusForNodes;
 
            for (int i = 0; i < nodeList.Count; i++)
            {
                if (nodeList[i].NodeId == nodeId)
                {
                    currentStatus[i] = nodeStatus;
                    break;
                }
            }
 
            statusReplyCount++;
            statusMessageReceived.Set();
        }
 
        internal void PostCycleNotification
        (
            int nodeId,
            TargetInProgessState child,
            TargetInProgessState parent)
        {
            if (nodeId == 0)
            {
                parentEngine.Introspector.BreakCycle(child, parent);
            }
 
            for (int i = 0; i < nodeList.Count; i++)
            {
                if (nodeList[i].NodeId == nodeId)
                {
                    nodeList[i].NodeProvider.PostIntrospectorCommand(nodeList[i].NodeIndex, child, parent);
                    break;
                }
            }
        }
 
        /// <summary>
        /// Shut down each of the nodes for all providers registered to the node manager.
        /// Shuts down the TEM.
        /// </summary>
        internal void ShutdownNodes(Node.NodeShutdownLevel nodeShutdownLevel)
        {
            foreach (INodeProvider nodeProvider in nodeProviders)
            {
                nodeProvider.ShutdownNodes(nodeShutdownLevel);
            }
 
            // Don't shutdown the TEM if the engine maybe reused for another build
            if (nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteFailure &&
                nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteSuccess)
            {
                if (taskExecutionModule != null)
                {
                    taskExecutionModule.Shutdown();
                    taskExecutionModule = null;
                    // At this point we have nulled out the task execution module and have told our task worker threads to exit
                    // we do not want the engine build loop to continue to do any work becasue the operations of the build loop
                    // require the task execution module in many cases. Before this fix, when the engine build loop was allowed
                    // to do work after the task execution module we would get random null reference excetpions depending on
                    // what was the first line to use the TEM after it was nulled out.
                    parentEngine.SetEngineAbortTo(true);
                }
            }
        }
 
        /// <summary>
        /// Post a build result to a node, the node index is an index into the list of nodes provided by all node providers
        /// registered to the node manager, the 0 in index is a local call to taskexecutionmodule
        /// </summary>
        /// <param name="nodeIndex"></param>
        /// <param name="buildResult"></param>
        internal void PostBuildResultToNode(int nodeIndex, BuildResult buildResult)
        {
            ErrorUtilities.VerifyThrow(nodeIndex <= nodeList.Count, "Should not pass a node index higher then the number of nodes in nodeManager");
            if (nodeIndex != 0)
            {
                nodeList[nodeIndex - 1].NodeProvider.PostBuildResultToNode(nodeList[nodeIndex - 1].NodeIndex, buildResult);
            }
            else
            {
                taskExecutionModule.PostBuildResults(buildResult);
            }
        }
        /// <summary>
        /// Post a build request to a node, the node index is an index into the list of nodes provided by all node providers
        /// registered to the node manager, the 0 in index is a local call to taskexecutionmodule
        /// </summary>
        /// <param name="nodeIndex"></param>
        /// <param name="buildRequest"></param>
        internal void PostBuildRequestToNode(int nodeIndex, BuildRequest buildRequest)
        {
            ErrorUtilities.VerifyThrow(nodeIndex != 0, "Should not use NodeManager to post to local TEM");
            nodeList[nodeIndex - 1].NodeProvider.PostBuildRequestToNode(nodeList[nodeIndex - 1].NodeIndex, buildRequest);
        }
 
        /// <summary>
        /// Execute a task on the local node
        /// </summary>
        /// <param name="taskState"></param>
        internal void ExecuteTask(TaskExecutionState taskState)
        {
            taskExecutionModule.ExecuteTask(taskState);
        }
 
        /// <summary>
        /// TEMPORARY
        /// </summary>
        internal void UpdateSettings
        (
            bool enableOutofProcLogging,
            bool enableOnlyLogCriticalEvents,
            bool useBreadthFirstTraversal
        )
        {
            foreach (INodeProvider nodeProvider in nodeProviders)
            {
                nodeProvider.UpdateSettings(enableOutofProcLogging, enableOnlyLogCriticalEvents, useBreadthFirstTraversal);
            }
        }
 
        internal void ChangeNodeTraversalType(bool breadthFirstTraversal)
        {
            UpdateSettings(parentEngine.EnabledCentralLogging, parentEngine.OnlyLogCriticalEvents, breadthFirstTraversal);
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Getter access to the local node
        /// </summary>
        internal TaskExecutionModule TaskExecutionModule
        {
            get
            {
                return taskExecutionModule;
            }
            set
            {
                taskExecutionModule = value;
            }
        }
 
        /// <summary>
        /// Number of Nodes being managed by NodeManager
        /// </summary>
        internal int MaxNodeCount
        {
            get
            {
                // add 1 for the local node (taskExecutionModule)
                return nodeList.Count + 1;
            }
        }
        #endregion
 
        #region Data
        /// <summary>
        /// Pointer to the parent engine
        /// </summary>
        private Engine parentEngine;
        /// <summary>
        /// List of node information of nodes provided by registered node providers
        /// </summary>
        private List<ProvidersNodeInformation> nodeList;
        /// <summary>
        /// List of registered node providers
        /// </summary>
        private List<INodeProvider> nodeProviders;
        /// <summary>
        /// Array of status summaries from the node
        /// </summary>
        private NodeStatus[] statusForNodes;
        /// <summary>
        /// Count of status replies recieved
        /// </summary>
        private int statusReplyCount;
        /// <summary>
        /// An event activated when status message arrives
        /// </summary>
        private ManualResetEvent statusMessageReceived;
        /// <summary>
        /// Local TEM used for executing tasks within the current process
        /// </summary>
        private TaskExecutionModule taskExecutionModule;
        #endregion
    }
 
    /// <summary>
    /// Class which contains, information about each of the nodes provided by each of the node providers registered to node manager
    /// </summary>
    internal class ProvidersNodeInformation
    {
        #region Constructors
        internal ProvidersNodeInformation
        (
            int nodeProviderNodeIndex,
            int nodeId,
            INodeDescription nodeProviderDescription,
            INodeProvider nodeProviderReference
        )
        {
            this.nodeIndex = nodeProviderNodeIndex;
            this.nodeId = nodeId;
            this.description = nodeProviderDescription;
            this.nodeProvider = nodeProviderReference;
        }
        #endregion
 
        #region Properties
        /// <summary>
        /// Node provider for node
        /// </summary>
        internal INodeProvider NodeProvider
        {
            get { return nodeProvider; }
        }
 
        /// <summary>
        /// Node description for node
        /// </summary>
        internal INodeDescription Description
        {
            get
            {
                return description;
            }
        }
 
        /// <summary>
        /// Node index relative to the node provider to which it is attached
        /// </summary>
        internal int NodeIndex
        {
            get
            {
                return nodeIndex;
            }
        }
 
        /// <summary>
        /// The nodeId issued by the engine to this node
        /// </summary>
        internal int NodeId
        {
            get
            {
                return nodeId;
            }
        }
        #endregion
 
        #region Data
        // Index from nodeProvider of a node which it manages
        private int nodeIndex;
 
        // Node description of node in nodeProvider referenced by nodeIndex;
        private INodeDescription description;
 
        // Reference to the nodeProvider which manages the node referenced by nodeIndex
        private INodeProvider nodeProvider;
 
        // The nodeId issued by the engine to this node
        private int nodeId;
        #endregion
    }
}