| File: BackEnd\Node\InProcNode.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.Frozen; using System.Globalization; using System.Threading; using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared.Debugging; using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; using NodeLoggingContext = Microsoft.Build.BackEnd.Logging.NodeLoggingContext; #nullable disable namespace Microsoft.Build.BackEnd { /// <summary> /// This class represents an implementation of INode for in-proc nodes. /// </summary> internal class InProcNode : INode, INodePacketFactory { /// <summary> /// The build component host. /// </summary> private readonly IBuildComponentHost _componentHost; /// <summary> /// The environment at the time the build is started. /// </summary> private FrozenDictionary<string, string> _savedEnvironment; /// <summary> /// The current directory at the time the build is started. /// </summary> private string _savedCurrentDirectory; /// <summary> /// The node logging context. /// </summary> private NodeLoggingContext _loggingContext; /// <summary> /// The build request engine. /// </summary> private readonly IBuildRequestEngine _buildRequestEngine; /// <summary> /// The queue of packets we have received but which have not yet been processed. /// </summary> private readonly ConcurrentQueue<INodePacket> _receivedPackets; /// <summary> /// The event which is set when we receive packets. /// </summary> private readonly AutoResetEvent _packetReceivedEvent; /// <summary> /// The event which is set when we should shut down. /// </summary> private readonly AutoResetEvent _shutdownEvent; /// <summary> /// The reason we are shutting down. /// </summary> private NodeEngineShutdownReason _shutdownReason; /// <summary> /// The exception, if any, which caused shutdown. /// </summary> private Exception _shutdownException; /// <summary> /// The node endpoint /// </summary> private readonly INodeEndpoint _nodeEndpoint; /// <summary> /// Handler for engine exceptions. /// </summary> private readonly EngineExceptionDelegate _engineExceptionEventHandler; /// <summary> /// Handler for new configuration requests. /// </summary> private readonly NewConfigurationRequestDelegate _newConfigurationRequestEventHandler; /// <summary> /// Handler for blocked request events. /// </summary> private readonly RequestBlockedDelegate _requestBlockedEventHandler; /// <summary> /// Handler for request completed events. /// </summary> private readonly RequestCompleteDelegate _requestCompleteEventHandler; /// <summary> /// Handler for resource request events. /// </summary> private readonly ResourceRequestDelegate _resourceRequestHandler; private readonly int _nodeId; /// <summary> /// Constructor. /// </summary> public InProcNode(int nodeId, IBuildComponentHost componentHost, INodeEndpoint inProcNodeEndpoint) { _nodeId = nodeId; _componentHost = componentHost; _nodeEndpoint = inProcNodeEndpoint; _receivedPackets = new ConcurrentQueue<INodePacket>(); _packetReceivedEvent = new AutoResetEvent(false); _shutdownEvent = new AutoResetEvent(false); IBuildComponent buildRequestEngine = BuildRequestEngine.CreateComponent(BuildComponentType.RequestEngine); buildRequestEngine.InitializeComponent(componentHost); _buildRequestEngine = (IBuildRequestEngine)buildRequestEngine; _engineExceptionEventHandler = OnEngineException; _newConfigurationRequestEventHandler = OnNewConfigurationRequest; _requestBlockedEventHandler = OnNewRequest; _requestCompleteEventHandler = OnRequestComplete; _resourceRequestHandler = OnResourceRequest; } #region INode Members /// <summary> /// Starts up the node and processes messages until the node is requested to shut down. /// </summary> /// <param name="shutdownException">The exception which caused shutdown, if any.</param> /// <returns>The reason for shutting down.</returns> public NodeEngineShutdownReason Run(out Exception shutdownException) { try { _nodeEndpoint.OnLinkStatusChanged += OnLinkStatusChanged; _nodeEndpoint.Listen(this); WaitHandle[] waitHandles = [_shutdownEvent, _packetReceivedEvent]; // Get the current directory before doing work. We need this so we can restore the directory when the node shuts down. _savedCurrentDirectory = Environment.CurrentDirectory; while (true) { int index = WaitHandle.WaitAny(waitHandles); switch (index) { case 0: { NodeEngineShutdownReason shutdownReason = HandleShutdown(out shutdownException); if (_componentHost.BuildParameters.ShutdownInProcNodeOnBuildFinish) { return shutdownReason; } break; } case 1: while (_receivedPackets.TryDequeue(out INodePacket packet)) { if (packet != null) { HandlePacket(packet); } } break; } } } catch (ThreadAbortException) { // Do nothing. This will happen when the thread is forcibly terminated because we are shutting down, for example // when the unit test framework terminates. throw; } catch (Exception e) { // Dump all engine exceptions to a temp file // so that we have something to go on in the // event of a failure DebugUtils.DumpExceptionToFile(e); // This is fatal: process will terminate: make sure the // debugger launches InternalError.Throw(e.Message, e); throw; } // UNREACHABLE } #endregion #region INodePacketFactory Members /// <summary> /// Not necessary for in-proc node - we don't serialize. /// </summary> public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler) { // The in-proc node doesn't need to do this. } /// <summary> /// Not necessary for in-proc node - we don't serialize. /// </summary> public void UnregisterPacketHandler(NodePacketType packetType) { // The in-proc node doesn't need to do this. } /// <summary> /// Not necessary for in-proc node - we don't serialize. /// </summary> public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator) // The in-proc endpoint shouldn't be serializing, just routing. => InternalError.Throw("Unexpected call to DeserializeAndRoutePacket on the in-proc node."); /// <summary> /// Not necessary for in-proc node - we don't serialize. /// </summary> public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) // The in-proc endpoint shouldn't be serializing, just routing. => InternalError.Throw<INodePacket>("Unexpected call to DeserializePacket on the in-proc node."); /// <summary> /// Routes the packet to the appropriate handler. /// </summary> /// <param name="nodeId">The node id.</param> /// <param name="packet">The packet.</param> public void RoutePacket(int nodeId, INodePacket packet) { _receivedPackets.Enqueue(packet); _packetReceivedEvent.Set(); } #endregion /// <summary> /// Event handler for the BuildEngine's OnRequestComplete event. /// </summary> private void OnRequestComplete(BuildRequest request, BuildResult result) { if (_nodeEndpoint.LinkStatus == LinkStatus.Active) { _nodeEndpoint.SendData(result); } } /// <summary> /// Event handler for the BuildEngine's OnNewRequest event. /// </summary> private void OnNewRequest(BuildRequestBlocker blocker) { if (_nodeEndpoint.LinkStatus == LinkStatus.Active) { _nodeEndpoint.SendData(blocker); } } /// <summary> /// Event handler for the BuildEngine's OnNewConfigurationRequest event. /// </summary> private void OnNewConfigurationRequest(BuildRequestConfiguration config) { if (_nodeEndpoint.LinkStatus == LinkStatus.Active) { _nodeEndpoint.SendData(config); } } /// <summary> /// Event handler for the BuildEngine's OnResourceRequest event. /// </summary> private void OnResourceRequest(ResourceRequest request) { if (_nodeEndpoint.LinkStatus == LinkStatus.Active) { _nodeEndpoint.SendData(request); } } /// <summary> /// Event handler for the LoggingService's OnLoggingThreadException event. /// </summary> private void OnLoggingThreadException(Exception e) { OnEngineException(e); } /// <summary> /// Event handler for the BuildEngine's OnEngineException event. /// </summary> private void OnEngineException(Exception e) { _shutdownException = e; _shutdownReason = NodeEngineShutdownReason.Error; _shutdownEvent.Set(); } /// <summary> /// Perform necessary actions to shut down the node. /// </summary> private NodeEngineShutdownReason HandleShutdown(out Exception exception) { // Console.WriteLine("Node shutting down with reason {0} and exception: {1}", shutdownReason, shutdownException); try { // Clean up the engine if (_buildRequestEngine != null && _buildRequestEngine.Status != BuildRequestEngineStatus.Uninitialized) { _buildRequestEngine.CleanupForBuild(); } } catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex)) { // If we had some issue shutting down, don't reuse the node because we may be in some weird state. if (_shutdownReason == NodeEngineShutdownReason.BuildCompleteReuse) { _shutdownReason = NodeEngineShutdownReason.BuildComplete; } } // Dispose of any build registered objects IRegisteredTaskObjectCache objectCache = (IRegisteredTaskObjectCache)(_componentHost.GetComponent(BuildComponentType.RegisteredTaskObjectCache)); objectCache.DisposeCacheObjects(RegisteredTaskObjectLifetime.Build); if (_shutdownReason != NodeEngineShutdownReason.BuildCompleteReuse) { // Dispose of any node registered objects. ((IBuildComponent)objectCache).ShutdownComponent(); } if (_componentHost.BuildParameters.SaveOperatingEnvironment) { // Restore the original current directory. NativeMethodsShared.SetCurrentDirectory(_savedCurrentDirectory); // Restore the original environment. CommunicationsUtilities.SetEnvironment(_savedEnvironment); } exception = _shutdownException; if (_loggingContext != null) { _loggingContext.LoggingService.OnLoggingThreadException -= OnLoggingThreadException; _loggingContext = null; } // Notify the BuildManager that we are done. if (_nodeEndpoint.LinkStatus == LinkStatus.Active) { _nodeEndpoint.SendData(new NodeShutdown(_shutdownReason == NodeEngineShutdownReason.Error ? NodeShutdownReason.Error : NodeShutdownReason.Requested, exception)); } _buildRequestEngine.OnEngineException -= _engineExceptionEventHandler; _buildRequestEngine.OnNewConfigurationRequest -= _newConfigurationRequestEventHandler; _buildRequestEngine.OnRequestBlocked -= _requestBlockedEventHandler; _buildRequestEngine.OnRequestComplete -= _requestCompleteEventHandler; _buildRequestEngine.OnResourceRequest -= _resourceRequestHandler; return _shutdownReason; } /// <summary> /// Dispatches the packet to the correct handler. /// </summary> private void HandlePacket(INodePacket packet) { switch (packet.Type) { case NodePacketType.BuildRequest: HandleBuildRequest(packet as BuildRequest); break; case NodePacketType.BuildRequestConfiguration: HandleBuildRequestConfiguration(); break; case NodePacketType.BuildRequestConfigurationResponse: HandleBuildRequestConfigurationResponse(packet as BuildRequestConfigurationResponse); break; case NodePacketType.BuildRequestUnblocker: HandleBuildResult(packet as BuildRequestUnblocker); break; case NodePacketType.NodeConfiguration: HandleNodeConfiguration(packet as NodeConfiguration); break; case NodePacketType.NodeBuildComplete: HandleNodeBuildComplete(packet as NodeBuildComplete); break; case NodePacketType.ResourceResponse: HandleResourceResponse(packet as ResourceResponse); break; } } /// <summary> /// Event handler for the node endpoint's LinkStatusChanged event. /// </summary> private void OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status) { switch (status) { case LinkStatus.ConnectionFailed: case LinkStatus.Failed: _shutdownReason = NodeEngineShutdownReason.ConnectionFailed; _shutdownEvent.Set(); break; case LinkStatus.Inactive: break; default: break; } } /// <summary> /// Handles the BuildRequest packet. /// </summary> private void HandleBuildRequest(BuildRequest request) { if (_componentHost.BuildParameters.MultiThreaded) { request.ScheduledNodeId = _nodeId; } _buildRequestEngine.SubmitBuildRequest(request); } /// <summary> /// Handles the BuildRequestConfiguration packet. /// </summary> private static void HandleBuildRequestConfiguration() { // Configurations are already in the cache, which we share with the BuildManager. } /// <summary> /// Handles the BuildRequestConfigurationResponse packet. /// </summary> private void HandleBuildRequestConfigurationResponse(BuildRequestConfigurationResponse response) { _buildRequestEngine.ReportConfigurationResponse(response); } /// <summary> /// Handles the BuildResult packet. /// </summary> private void HandleBuildResult(BuildRequestUnblocker unblocker) { _buildRequestEngine.UnblockBuildRequest(unblocker); } /// <summary> /// Handles the NodeConfiguration packet. /// </summary> private void HandleNodeConfiguration(NodeConfiguration configuration) { // Set the culture. CultureInfo.CurrentCulture = configuration.BuildParameters.Culture; CultureInfo.CurrentUICulture = configuration.BuildParameters.UICulture; // Snapshot the initial environment. _savedEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); // Save the current directory. _savedCurrentDirectory = Environment.CurrentDirectory; // Set the node id. _componentHost.BuildParameters.NodeId = configuration.NodeId; _shutdownException = null; #if FEATURE_APPDOMAIN // And the AppDomainSetup _componentHost.BuildParameters.AppDomainSetup = configuration.AppDomainSetup; #endif // Declare in-proc _componentHost.BuildParameters.IsOutOfProc = false; // Set the logging exception handler ILoggingService loggingService = _componentHost.LoggingService; loggingService.OnLoggingThreadException += OnLoggingThreadException; // Now prep the buildRequestEngine for the build. _loggingContext = new NodeLoggingContext(loggingService, configuration.NodeId, true /* inProcNode */); _buildRequestEngine.OnEngineException += _engineExceptionEventHandler; _buildRequestEngine.OnNewConfigurationRequest += _newConfigurationRequestEventHandler; _buildRequestEngine.OnRequestBlocked += _requestBlockedEventHandler; _buildRequestEngine.OnRequestComplete += _requestCompleteEventHandler; _buildRequestEngine.OnResourceRequest += _resourceRequestHandler; if (_shutdownException != null) { HandleShutdown(out Exception exception); throw exception; } _buildRequestEngine.InitializeForBuild(_loggingContext); } /// <summary> /// Handles the NodeBuildComplete packet. /// </summary> private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) { _shutdownReason = buildComplete.PrepareForReuse ? NodeEngineShutdownReason.BuildCompleteReuse : NodeEngineShutdownReason.BuildComplete; _shutdownEvent.Set(); } /// <summary> /// Handles the ResourceResponse packet. /// </summary> private void HandleResourceResponse(ResourceResponse response) { _buildRequestEngine.GrantResources(response); } } }