/// <summary> /// Actually writes and sends the packet. This can't be called in parallel /// because it reuses the _writeBufferMemoryStream, and this is why we use /// the _packetWriteDrainTask to serially chain invocations one after another. /// </summary> /// <param name="packet">The packet to send.</param> private void SendDataCore(INodePacket packet) { MemoryStream writeStream = _writeBufferMemoryStream; // clear the buffer but keep the underlying capacity to avoid reallocations writeStream.SetLength(0); ITranslator writeTranslator = BinaryTranslator.GetWriteTranslator(writeStream); try { writeStream.WriteByte((byte)packet.Type); // Pad for the packet length WriteInt32(writeStream, 0); packet.Translate(writeTranslator); int writeStreamLength = (int)writeStream.Position; // Now plug in the real packet length writeStream.Position = 1; WriteInt32(writeStream, writeStreamLength - 5); byte[] writeStreamBuffer = writeStream.GetBuffer(); for (int i = 0; i < writeStreamLength; i += MaxPacketWriteSize) { int lengthToWrite = Math.Min(writeStreamLength - i, MaxPacketWriteSize); _serverToClientStream.Write(writeStreamBuffer, i, lengthToWrite); } } catch (IOException e) { // Do nothing here because any exception will be caught by the async read handler CommunicationsUtilities.Trace(_nodeId, "EXCEPTION in SendData: {0}", e); } catch (ObjectDisposedException) // This happens if a child dies unexpectedly { // Do nothing here because any exception will be caught by the async read handler } }
/// <summary> /// Shuts down all of the managed nodes permanently. /// </summary> /// <param name="hostHandshake">host handshake key</param> /// <param name="clientHandshake">client handshake key</param> /// <param name="terminateNode">Delegate used to tell the node provider that a context has terminated</param> protected void ShutdownAllNodes(long hostHandshake, long clientHandshake, NodeContextTerminateDelegate terminateNode) { #if FEATURE_NAMED_PIPES_FULL_DUPLEX // INodePacketFactory INodePacketFactory factory = new NodePacketFactory(); // Find proper msbuild executable name string msbuildExeName = Environment.GetEnvironmentVariable("MSBUILD_EXE_NAME"); if (String.IsNullOrEmpty(msbuildExeName)) { msbuildExeName = "MSBuild.exe"; } // Search for all instances of the msbuild process and create a list of them List <Process> nodeProcesses = new List <Process>(Process.GetProcessesByName(Path.GetFileNameWithoutExtension(msbuildExeName))); // Find proper MSBuildTaskHost executable name string msbuildtaskhostExeName = NodeProviderOutOfProcTaskHost.TaskHostNameForClr2TaskHost; // Search for all instances of msbuildtaskhost process and add them to the process list nodeProcesses.AddRange(new List <Process>(Process.GetProcessesByName(Path.GetFileNameWithoutExtension(msbuildtaskhostExeName)))); // For all processes in the list, send signal to terminate if able to connect foreach (Process nodeProcess in nodeProcesses) { Stream nodeStream = TryConnectToProcess(nodeProcess.Id, 30 /*verified to miss nodes if smaller*/, hostHandshake, clientHandshake); if (null != nodeStream) { // If we're able to connect to such a process, send a packet requesting its termination CommunicationsUtilities.Trace("Shutting down node with pid = {0}", nodeProcess.Id); NodeContext nodeContext = new NodeContext(0, nodeProcess.Id, nodeStream, factory, terminateNode); nodeContext.SendData(new NodeBuildComplete(false /* no node reuse */)); nodeStream.Dispose(); } } #else throw new PlatformNotSupportedException("Shutting down nodes by enumerating processes and connecting to them is not supported when using anonymous pipes."); #endif }
private bool ReadAndRoutePacket(NodePacketType packetType, byte [] packetData, int packetLength) { try { // The buffer is publicly visible so that InterningBinaryReader doesn't have to copy to an intermediate buffer. // Since the buffer is publicly visible dispose right away to discourage outsiders from holding a reference to it. using (var packetStream = new MemoryStream(packetData, 0, packetLength, /*writeable*/ false, /*bufferIsPubliclyVisible*/ true)) { ITranslator readTranslator = BinaryTranslator.GetReadTranslator(packetStream, _sharedReadBuffer); _packetFactory.DeserializeAndRoutePacket(_nodeId, packetType, readTranslator); } } catch (IOException e) { CommunicationsUtilities.Trace(_nodeId, "EXCEPTION in ReadAndRoutPacket: {0}", e); _packetFactory.RoutePacket(_nodeId, new NodeShutdown(NodeShutdownReason.ConnectionFailed)); Close(); return(false); } return(true); }
/// <summary> /// Returns true if a task host exists that can service the requested runtime and architecture /// values, and false otherwise. /// </summary> internal static bool DoesTaskHostExist(string runtime, string architecture) { if (runtime != null) { runtime = runtime.Trim(); } if (architecture != null) { architecture = architecture.Trim(); } if (!XMakeAttributes.IsValidMSBuildRuntimeValue(runtime)) { ErrorUtilities.ThrowArgument("InvalidTaskHostFactoryParameter", runtime, "Runtime", XMakeAttributes.MSBuildRuntimeValues.clr2, XMakeAttributes.MSBuildRuntimeValues.clr4, XMakeAttributes.MSBuildRuntimeValues.currentRuntime, XMakeAttributes.MSBuildRuntimeValues.any); } if (!XMakeAttributes.IsValidMSBuildArchitectureValue(architecture)) { ErrorUtilities.ThrowArgument("InvalidTaskHostFactoryParameter", architecture, "Architecture", XMakeAttributes.MSBuildArchitectureValues.x86, XMakeAttributes.MSBuildArchitectureValues.x64, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture, XMakeAttributes.MSBuildArchitectureValues.any); } runtime = XMakeAttributes.GetExplicitMSBuildRuntime(runtime); architecture = XMakeAttributes.GetExplicitMSBuildArchitecture(architecture); IDictionary <string, string> parameters = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); parameters.Add(XMakeAttributes.runtime, runtime); parameters.Add(XMakeAttributes.architecture, architecture); TaskHostContext desiredContext = CommunicationsUtilities.GetTaskHostContext(parameters); string taskHostLocation = NodeProviderOutOfProcTaskHost.GetMSBuildLocationFromHostContext(desiredContext); if (taskHostLocation != null && FileUtilities.FileExistsNoThrow(taskHostLocation)) { return(true); } return(false); }
private bool WaitForConnectionFromProcess(AnonymousPipeServerStream clientToServerStream, AnonymousPipeServerStream serverToClientStream, int nodeProcessId, long hostHandshake, long clientHandshake) { try { CommunicationsUtilities.Trace("Attempting to handshake with PID {0}", nodeProcessId); CommunicationsUtilities.Trace("Writing handshake to pipe"); serverToClientStream.WriteLongForHandshake(hostHandshake); CommunicationsUtilities.Trace("Reading handshake from pipe"); long handshake = clientToServerStream.ReadLongForHandshake(); if (handshake != clientHandshake) { CommunicationsUtilities.Trace("Handshake failed. Received {0} from client not {1}. Probably the client is a different MSBuild build.", handshake, clientHandshake); throw new InvalidOperationException(); } // We got a connection. CommunicationsUtilities.Trace("Successfully connected got connection from PID {0}...!", nodeProcessId); return(true); } catch (Exception ex) { if (ExceptionHandling.IsCriticalException(ex)) { throw; } CommunicationsUtilities.Trace("Failed to get connection from PID {0}. {1}", nodeProcessId, ex.ToString()); clientToServerStream.Dispose(); serverToClientStream.Dispose(); return(false); } }
/// <summary> /// Instantiates a new MSBuild process acting as a child node. /// </summary> public bool CreateNode(int nodeId, INodePacketFactory factory, NodeConfiguration configuration) { ErrorUtilities.VerifyThrowArgumentNull(factory, "factory"); if (_nodeContexts.Count == ComponentHost.BuildParameters.MaxNodeCount) { ErrorUtilities.ThrowInternalError("All allowable nodes already created ({0}).", _nodeContexts.Count); return(false); } // 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. string commandLineArgs = " /nologo /nodemode:1 "; // Enable node re-use if it is set. if (ComponentHost.BuildParameters.EnableNodeReuse) { commandLineArgs += "/nr"; } // Make it here. CommunicationsUtilities.Trace("Starting to acquire a new or existing node to establish node ID {0}...", nodeId); NodeContext context = GetNode(null, commandLineArgs, nodeId, factory, NodeProviderOutOfProc.HostHandshake, NodeProviderOutOfProc.ClientHandshake, NodeContextTerminated); if (null != context) { _nodeContexts[nodeId] = context; // Start the asynchronous read. context.BeginAsyncPacketRead(); // Configure the node. context.SendData(configuration); return(true); } throw new BuildAbortedException(ResourceUtilities.FormatResourceString("CouldNotConnectToMSBuildExe", ComponentHost.BuildParameters.NodeExeLocation)); }
internal bool CreateNode(HandshakeOptions hostContext, INodePacketFactory factory, INodePacketHandler handler, TaskHostConfiguration configuration) { ErrorUtilities.VerifyThrowArgumentNull(factory, nameof(factory)); ErrorUtilities.VerifyThrow(!_nodeIdToPacketFactory.ContainsKey((int)hostContext), "We should not already have a factory for this context! Did we forget to call DisconnectFromHost somewhere?"); if (AvailableNodes <= 0) { ErrorUtilities.ThrowInternalError("All allowable nodes already created ({0}).", _nodeContexts.Count); return(false); } // Start the new process. We pass in a node mode with a node number of 2, to indicate that we // want to start up an MSBuild task host node. string commandLineArgs = $" /nologo /nodemode:2 /nodereuse:{ComponentHost.BuildParameters.EnableNodeReuse} /low:{ComponentHost.BuildParameters.LowPriority} "; string msbuildLocation = GetMSBuildLocationFromHostContext(hostContext); // we couldn't even figure out the location we're trying to launch ... just go ahead and fail. if (msbuildLocation == null) { return(false); } CommunicationsUtilities.Trace("For a host context of {0}, spawning executable from {1}.", hostContext.ToString(), msbuildLocation ?? "MSBuild.exe"); // There is always one task host per host context so we always create just 1 one task host node here. int nodeId = (int)hostContext; IList <NodeContext> nodeContexts = GetNodes( msbuildLocation, commandLineArgs, nodeId, this, new Handshake(hostContext), NodeContextCreated, NodeContextTerminated, 1); return(nodeContexts.Count == 1); }
/// <summary> /// Shuts down all of the managed nodes permanently. /// </summary> /// <param name="nodeReuse">Whether to reuse the node</param> /// <param name="terminateNode">Delegate used to tell the node provider that a context has terminated</param> protected void ShutdownAllNodes(bool nodeReuse, NodeContextTerminateDelegate terminateNode) { // INodePacketFactory INodePacketFactory factory = new NodePacketFactory(); List <Process> nodeProcesses = GetPossibleRunningNodes().nodeProcesses; // Find proper MSBuildTaskHost executable name string msbuildtaskhostExeName = NodeProviderOutOfProcTaskHost.TaskHostNameForClr2TaskHost; // Search for all instances of msbuildtaskhost process and add them to the process list nodeProcesses.AddRange(new List <Process>(Process.GetProcessesByName(Path.GetFileNameWithoutExtension(msbuildtaskhostExeName)))); // For all processes in the list, send signal to terminate if able to connect foreach (Process nodeProcess in nodeProcesses) { // A 2013 comment suggested some nodes take this long to respond, so a smaller timeout would miss nodes. int timeout = 30; // Attempt to connect to the process with the handshake without low priority. Stream nodeStream = TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, false)); if (nodeStream == null) { // If we couldn't connect attempt to connect to the process with the handshake including low priority. nodeStream = TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, true)); } if (nodeStream != null) { // If we're able to connect to such a process, send a packet requesting its termination CommunicationsUtilities.Trace("Shutting down node with pid = {0}", nodeProcess.Id); NodeContext nodeContext = new NodeContext(0, nodeProcess, nodeStream, factory, terminateNode); nodeContext.SendData(new NodeBuildComplete(false /* no node reuse */)); nodeStream.Dispose(); } } }
/// <summary> /// Instantiates a new MSBuild process acting as a child node. /// </summary> public bool CreateNode(int nodeId, INodePacketFactory factory, NodeConfiguration configuration) { ErrorUtilities.VerifyThrowArgumentNull(factory, "factory"); if (_nodeContexts.Count == ComponentHost.BuildParameters.MaxNodeCount) { ErrorUtilities.ThrowInternalError("All allowable nodes already created ({0}).", _nodeContexts.Count); return(false); } // 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. string commandLineArgs = $"/nologo /nodemode:1 /nodeReuse:{ComponentHost.BuildParameters.EnableNodeReuse.ToString().ToLower()} /low:{ComponentHost.BuildParameters.LowPriority.ToString().ToLower()}"; // Make it here. CommunicationsUtilities.Trace("Starting to acquire a new or existing node to establish node ID {0}...", nodeId); long hostHandShake = NodeProviderOutOfProc.GetHostHandshake(ComponentHost.BuildParameters.EnableNodeReuse, ComponentHost.BuildParameters.LowPriority); NodeContext context = GetNode(null, commandLineArgs, nodeId, factory, hostHandShake, NodeProviderOutOfProc.GetClientHandshake(ComponentHost.BuildParameters.EnableNodeReuse, ComponentHost.BuildParameters.LowPriority), NodeContextTerminated); if (null != context) { _nodeContexts[nodeId] = context; // Start the asynchronous read. context.BeginAsyncPacketRead(); // Configure the node. context.SendData(configuration); return(true); } throw new BuildAbortedException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("CouldNotConnectToMSBuildExe", ComponentHost.BuildParameters.NodeExeLocation)); }
private void btnUpdateStatistics_Click(object sender, EventArgs e) { try { this.dgrdNetworkStatisticsIPv4.DataSource = CommunicationsUtilities.GetNetworkingStatiticsTable(System.Net.NetworkInformation.NetworkInterfaceComponent.IPv4); this.dgrdNetworkStatisticsIPv4.Columns[0].Width = (this.dgrdNetworkStatisticsIPv4.Width / this.dgrdNetworkStatisticsIPv4.ColumnCount) - 5; this.dgrdNetworkStatisticsIPv4.Columns[1].Width = (this.dgrdNetworkStatisticsIPv4.Width / this.dgrdNetworkStatisticsIPv4.ColumnCount) - 5; this.dgrdNetworkStatisticsIPv4.Columns[2].Width = (this.dgrdNetworkStatisticsIPv4.Width / this.dgrdNetworkStatisticsIPv4.ColumnCount) - 5; } catch (Exception) { } try { this.dgrdNetworkStatisticsIPv6.DataSource = CommunicationsUtilities.GetNetworkingStatiticsTable(System.Net.NetworkInformation.NetworkInterfaceComponent.IPv6); this.dgrdNetworkStatisticsIPv6.Columns[0].Width = (this.dgrdNetworkStatisticsIPv6.Width / this.dgrdNetworkStatisticsIPv6.ColumnCount) - 5; this.dgrdNetworkStatisticsIPv6.Columns[1].Width = (this.dgrdNetworkStatisticsIPv6.Width / this.dgrdNetworkStatisticsIPv6.ColumnCount) - 5; this.dgrdNetworkStatisticsIPv6.Columns[2].Width = (this.dgrdNetworkStatisticsIPv6.Width / this.dgrdNetworkStatisticsIPv6.ColumnCount) - 5; } catch (Exception) { } }
/// <summary> /// This method handles the asynchronous message pump. It waits for messages to show up on the queue /// and calls FireDataAvailable for each such packet. It will terminate when the terminate event is /// set. /// </summary> private void PacketPumpProc() { NamedPipeServerStream localPipeServer = _pipeServer; AutoResetEvent localPacketAvailable = _packetAvailable; AutoResetEvent localTerminatePacketPump = _terminatePacketPump; ConcurrentQueue <INodePacket> localPacketQueue = _packetQueue; DateTime originalWaitStartTime = DateTime.UtcNow; bool gotValidConnection = false; while (!gotValidConnection) { gotValidConnection = true; DateTime restartWaitTime = DateTime.UtcNow; // We only wait to wait the difference between now and the last original start time, in case we have multiple hosts attempting // to attach. This prevents each attempt from resetting the timer. TimeSpan usedWaitTime = restartWaitTime - originalWaitStartTime; int waitTimeRemaining = Math.Max(0, CommunicationsUtilities.NodeConnectionTimeout - (int)usedWaitTime.TotalMilliseconds); try { // Wait for a connection #if FEATURE_APM IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null); CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); #else Task connectionTask = localPipeServer.WaitForConnectionAsync(); CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); bool connected = connectionTask.Wait(waitTimeRemaining); #endif if (!connected) { CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread."); ChangeLinkStatus(LinkStatus.ConnectionFailed); return; } CommunicationsUtilities.Trace("Parent started connecting. Reading handshake from parent"); #if FEATURE_APM localPipeServer.EndWaitForConnection(resultForConnection); #endif // The handshake protocol is a series of int exchanges. The host sends us a each component, and we // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. // Once the handshake is complete, both sides can be assured the other is ready to accept data. Handshake handshake = GetHandshake(); try { int[] handshakeComponents = handshake.RetrieveHandshakeComponents(); for (int i = 0; i < handshakeComponents.Length; i++) { int handshakePart = _pipeServer.ReadIntForHandshake(i == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ #if NETCOREAPP2_1 || MONO , ClientConnectTimeout /* wait a long time for the handshake from this side */ #endif ); if (handshakePart != handshakeComponents[i]) { CommunicationsUtilities.Trace("Handshake failed. Received {0} from host not {1}. Probably the host is a different MSBuild build.", handshakePart, handshakeComponents[i]); _pipeServer.WriteIntForHandshake(i + 1); gotValidConnection = false; break; } } if (gotValidConnection) { // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS. #if NETCOREAPP2_1 || MONO _pipeServer.ReadEndOfHandshakeSignal(false, ClientConnectTimeout); /* wait a long time for the handshake from this side */ #else _pipeServer.ReadEndOfHandshakeSignal(false); #endif CommunicationsUtilities.Trace("Successfully connected to parent."); _pipeServer.WriteEndOfHandshakeSignal(); #if FEATURE_SECURITY_PERMISSIONS // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the // user we were started by. WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); WindowsIdentity clientIdentity = null; localPipeServer.RunAsClient(delegate() { clientIdentity = WindowsIdentity.GetCurrent(true); }); if (clientIdentity == null || !String.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) { CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "<unknown>" : clientIdentity.Name, currentIdentity.Name); gotValidConnection = false; continue; } #endif } } catch (IOException e) { // We will get here when: // 1. The host (OOP main node) connects to us, it immediately checks for user privileges // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake // 2. The host is too old sending us bits we automatically reject in the handshake // 3. We expected to read the EndOfHandshake signal, but we received something else CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); gotValidConnection = false; } catch (InvalidOperationException) { gotValidConnection = false; } if (!gotValidConnection) { if (localPipeServer.IsConnected) { localPipeServer.Disconnect(); } continue; } ChangeLinkStatus(LinkStatus.Active); } catch (Exception e) { if (ExceptionHandling.IsCriticalException(e)) { throw; } CommunicationsUtilities.Trace("Client connection failed. Exiting comm thread. {0}", e); if (localPipeServer.IsConnected) { localPipeServer.Disconnect(); } ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); return; } } RunReadLoop( new BufferedReadStream(_pipeServer), _pipeServer, localPacketQueue, localPacketAvailable, localTerminatePacketPump); CommunicationsUtilities.Trace("Ending read loop"); try { if (localPipeServer.IsConnected) { localPipeServer.WaitForPipeDrain(); localPipeServer.Disconnect(); } } catch (Exception) { // We don't really care if Disconnect somehow fails, but it gives us a chance to do the right thing. } }
private void HandleNodeConfiguration(NodeConfiguration configuration) { // Grab the system parameters. _buildParameters = configuration.BuildParameters; _buildParameters.ProjectRootElementCache = s_projectRootElementCacheBase; // Snapshot the current environment _savedEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); // Change to the startup directory try { NativeMethodsShared.SetCurrentDirectory(BuildParameters.StartupDirectory); } catch (DirectoryNotFoundException) { // Somehow the startup directory vanished. This can happen if build was started from a USB Key and it was removed. NativeMethodsShared.SetCurrentDirectory(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory); } // Replicate the environment. First, unset any environment variables set by the previous configuration. if (_currentConfiguration != null) { foreach (string key in _currentConfiguration.BuildParameters.BuildProcessEnvironment.Keys) { Environment.SetEnvironmentVariable(key, null); } } // Now set the new environment foreach (KeyValuePair <string, string> environmentPair in _buildParameters.BuildProcessEnvironment) { Environment.SetEnvironmentVariable(environmentPair.Key, environmentPair.Value); } // We want to make sure the global project collection has the toolsets which were defined on the parent // so that any custom toolsets defined can be picked up by tasks who may use the global project collection but are // executed on the child node. ICollection <Toolset> parentToolSets = _buildParameters.ToolsetProvider.Toolsets; if (parentToolSets != null) { ProjectCollection.GlobalProjectCollection.RemoveAllToolsets(); foreach (Toolset toolSet in parentToolSets) { ProjectCollection.GlobalProjectCollection.AddToolset(toolSet); } } // Set the culture. CultureInfo.CurrentCulture = _buildParameters.Culture; CultureInfo.CurrentUICulture = _buildParameters.UICulture; // Get the node ID. _buildParameters.NodeId = configuration.NodeId; _buildParameters.IsOutOfProc = true; #if FEATURE_APPDOMAIN // And the AppDomainSetup _buildParameters.AppDomainSetup = configuration.AppDomainSetup; #endif // Set up the logging service. LoggingServiceFactory loggingServiceFactory = new LoggingServiceFactory(LoggerMode.Asynchronous, configuration.NodeId); _componentFactories.ReplaceFactory(BuildComponentType.LoggingService, loggingServiceFactory.CreateInstance); _loggingService = _componentFactories.GetComponent(BuildComponentType.LoggingService) as ILoggingService; BuildEventArgTransportSink sink = new BuildEventArgTransportSink(SendPacket); _shutdownException = null; if (configuration.LoggingNodeConfiguration.IncludeEvaluationMetaprojects) { _loggingService.IncludeEvaluationMetaprojects = true; } if (configuration.LoggingNodeConfiguration.IncludeEvaluationProfiles) { _loggingService.IncludeEvaluationProfile = true; } if (configuration.LoggingNodeConfiguration.IncludeTaskInputs) { _loggingService.IncludeTaskInputs = true; } if (configuration.LoggingNodeConfiguration.IncludeEvaluationPropertiesAndItems) { _loggingService.IncludeEvaluationPropertiesAndItems = true; } try { // If there are no node loggers to initialize dont do anything if (configuration.LoggerDescriptions?.Length > 0) { _loggingService.InitializeNodeLoggers(configuration.LoggerDescriptions, sink, configuration.NodeId); } } catch (Exception ex) when(!ExceptionHandling.IsCriticalException(ex)) { OnEngineException(ex); } _loggingService.OnLoggingThreadException += OnLoggingThreadException; string forwardPropertiesFromChild = Environment.GetEnvironmentVariable("MSBUILDFORWARDPROPERTIESFROMCHILD"); string[] propertyListToSerialize = null; // Get a list of properties which should be serialized if (!String.IsNullOrEmpty(forwardPropertiesFromChild)) { propertyListToSerialize = forwardPropertiesFromChild.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); } _loggingService.PropertiesToSerialize = propertyListToSerialize; _loggingService.RunningOnRemoteNode = true; string forwardAllProperties = Environment.GetEnvironmentVariable("MSBUILDFORWARDALLPROPERTIESFROMCHILD"); if (String.Equals(forwardAllProperties, "1", StringComparison.OrdinalIgnoreCase) || _buildParameters.LogInitialPropertiesAndItems) { _loggingService.SerializeAllProperties = true; } else { _loggingService.SerializeAllProperties = false; } // Now prep the buildRequestEngine for the build. _loggingContext = new NodeLoggingContext(_loggingService, configuration.NodeId, false /* inProcNode */); if (_shutdownException != null) { HandleShutdown(out Exception exception); throw exception; } _buildRequestEngine.InitializeForBuild(_loggingContext); // Finally store off this configuration packet. _currentConfiguration = configuration; }
private NodeEngineShutdownReason HandleShutdown(out Exception exception) { CommunicationsUtilities.Trace("Shutting down with reason: {0}, and exception: {1}.", _shutdownReason, _shutdownException); // Clean up the engine if (_buildRequestEngine != null && _buildRequestEngine.Status != BuildRequestEngineStatus.Uninitialized) { _buildRequestEngine.CleanupForBuild(); if (_shutdownReason == NodeEngineShutdownReason.BuildCompleteReuse) { ((IBuildComponent)_buildRequestEngine).ShutdownComponent(); } } // Signal the SDK resolver service to shutdown ((IBuildComponent)_sdkResolverService).ShutdownComponent(); // Dispose of any build registered objects IRegisteredTaskObjectCache objectCache = (IRegisteredTaskObjectCache)(_componentFactories.GetComponent(BuildComponentType.RegisteredTaskObjectCache)); objectCache.DisposeCacheObjects(RegisteredTaskObjectLifetime.Build); if (_shutdownReason != NodeEngineShutdownReason.BuildCompleteReuse) { // Dispose of any node registered objects. ((IBuildComponent)objectCache).ShutdownComponent(); } // Shutdown any Out Of Proc Nodes Created _taskHostNodeManager.ShutdownConnectedNodes(_shutdownReason == NodeEngineShutdownReason.BuildCompleteReuse); // On Windows, a process holds a handle to the current directory, // so reset it away from a user-requested folder that may get deleted. NativeMethodsShared.SetCurrentDirectory(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory); // Restore the original environment. // If the node was never configured, this will be null. if (_savedEnvironment != null) { foreach (KeyValuePair <string, string> entry in CommunicationsUtilities.GetEnvironmentVariables()) { if (!_savedEnvironment.ContainsKey(entry.Key)) { Environment.SetEnvironmentVariable(entry.Key, null); } } foreach (KeyValuePair <string, string> entry in _savedEnvironment) { Environment.SetEnvironmentVariable(entry.Key, entry.Value); } } try { // Shut down logging, which will cause all queued logging messages to be sent. if (_loggingContext != null && _loggingService != null) { _loggingContext.LogBuildFinished(true); ((IBuildComponent)_loggingService).ShutdownComponent(); } } finally { // Shut down logging, which will cause all queued logging messages to be sent. if (_loggingContext != null && _loggingService != null) { _loggingContext.LoggingService.OnLoggingThreadException -= OnLoggingThreadException; _loggingContext = null; } exception = _shutdownException; if (_nodeEndpoint.LinkStatus == LinkStatus.Active) { // Notify the BuildManager that we are done. _nodeEndpoint.SendData(new NodeShutdown(_shutdownReason == NodeEngineShutdownReason.Error ? NodeShutdownReason.Error : NodeShutdownReason.Requested, exception)); // Flush all packets to the pipe and close it down. This blocks until the shutdown is complete. _nodeEndpoint.OnLinkStatusChanged -= OnLinkStatusChanged; } _nodeEndpoint.Disconnect(); CleanupCaches(); } CommunicationsUtilities.Trace("Shut down complete."); return(_shutdownReason); }
/// <summary> /// Returns the host handshake for this node endpoint /// </summary> protected override Handshake GetHandshake() { return(new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, is64Bit: EnvironmentUtilities.Is64BitProcess, nodeReuse: _enableReuse, lowPriority: _lowPriority))); }
/// <summary> /// This method handles the asynchronous message pump. It waits for messages to show up on the queue /// and calls FireDataAvailable for each such packet. It will terminate when the terminate event is /// set. /// </summary> private void PacketPumpProc() { NamedPipeServerStream localPipeServer = _pipeServer; PipeStream localWritePipe = _pipeServer; PipeStream localReadPipe = _pipeServer; AutoResetEvent localPacketAvailable = _packetAvailable; AutoResetEvent localTerminatePacketPump = _terminatePacketPump; ConcurrentQueue <INodePacket> localPacketQueue = _packetQueue; DateTime originalWaitStartTime = DateTime.UtcNow; bool gotValidConnection = false; while (!gotValidConnection) { DateTime restartWaitTime = DateTime.UtcNow; // We only wait to wait the difference between now and the last original start time, in case we have multiple hosts attempting // to attach. This prevents each attempt from resetting the timer. TimeSpan usedWaitTime = restartWaitTime - originalWaitStartTime; int waitTimeRemaining = Math.Max(0, CommunicationsUtilities.NodeConnectionTimeout - (int)usedWaitTime.TotalMilliseconds); try { // Wait for a connection #if FEATURE_APM IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null); #else Task connectionTask = localPipeServer.WaitForConnectionAsync(); #endif CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); #if FEATURE_APM bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); #else bool connected = connectionTask.Wait(waitTimeRemaining); #endif if (!connected) { CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread."); ChangeLinkStatus(LinkStatus.ConnectionFailed); return; } CommunicationsUtilities.Trace("Parent started connecting. Reading handshake from parent"); #if FEATURE_APM localPipeServer.EndWaitForConnection(resultForConnection); #endif // The handshake protocol is a simple long exchange. The host sends us a long, and we // respond with another long. Once the handshake is complete, both sides can be assured the // other is ready to accept data. // To avoid mixing client and server builds, the long is the MSBuild binary timestamp. // Compatibility issue here. // Previous builds of MSBuild 4.0 would exchange just a byte. // Host would send either 0x5F or 0x60 depending on whether it was the toolset or not respectively. // Client would return either 0xF5 or 0x06 respectively. // Therefore an old host on a machine with new clients running will hang, // sending a byte and waiting for a byte until it eventually times out; // because the new client will want 7 more bytes before it returns anything. // The other way around is not a problem, because the old client would immediately return the (wrong) // byte on receiving the first byte of the long sent by the new host, and the new host would disconnect. // To avoid the hang, special case here: // Make sure our handshakes always start with 00. // If we received ONLY one byte AND it's 0x5F or 0x60, return 0xFF (it doesn't matter what as long as // it will cause the host to reject us; new hosts expect 00 and old hosts expect F5 or 06). try { long handshake = localReadPipe.ReadLongForHandshake(/* reject these leads */ new byte[] { 0x5F, 0x60 }, 0xFF /* this will disconnect the host; it expects leading 00 or F5 or 06 */ #if NETCOREAPP2_1 , ClientConnectTimeout /* wait a long time for the handshake from this side */ #endif ); #if FEATURE_SECURITY_PERMISSIONS WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); #endif if (handshake != GetHostHandshake()) { CommunicationsUtilities.Trace("Handshake failed. Received {0} from host not {1}. Probably the host is a different MSBuild build.", handshake, GetHostHandshake()); localPipeServer.Disconnect(); continue; } #if FEATURE_SECURITY_PERMISSIONS // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the // user we were started by. WindowsIdentity clientIdentity = null; localPipeServer.RunAsClient(delegate() { clientIdentity = WindowsIdentity.GetCurrent(true); }); if (clientIdentity == null || !String.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) { CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "<unknown>" : clientIdentity.Name, currentIdentity.Name); localPipeServer.Disconnect(); continue; } #endif } catch (IOException e) { // We will get here when: // 1. The host (OOP main node) connects to us, it immediately checks for user privileges // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake // 2. The host is too old sending us bits we automatically reject in the handshake CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); if (localPipeServer.IsConnected) { localPipeServer.Disconnect(); } continue; } gotValidConnection = true; } catch (Exception e) { if (ExceptionHandling.IsCriticalException(e)) { throw; } CommunicationsUtilities.Trace("Client connection failed. Exiting comm thread. {0}", e); if (localPipeServer.IsConnected) { localPipeServer.Disconnect(); } ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); return; } } CommunicationsUtilities.Trace("Writing handshake to parent"); localWritePipe.WriteLongForHandshake(GetClientHandshake()); ChangeLinkStatus(LinkStatus.Active); RunReadLoop( new BufferedReadStream(localReadPipe), localWritePipe, localPacketQueue, localPacketAvailable, localTerminatePacketPump); CommunicationsUtilities.Trace("Ending read loop"); try { if (localPipeServer.IsConnected) { localPipeServer.WaitForPipeDrain(); localPipeServer.Disconnect(); } } catch (Exception) { // We don't really care if Disconnect somehow fails, but it gives us a chance to do the right thing. } }
/// <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 long GetHostHandshake(bool enableNodeReuse, bool enableLowPriority) { CommunicationsUtilities.Trace("MSBUILDNODEHANDSHAKESALT=\"{0}\", msbuildDirectory=\"{1}\", enableNodeReuse={2}, enableLowPriority={3}", Traits.MSBuildNodeHandshakeSalt, BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32, enableNodeReuse, enableLowPriority); return(CommunicationsUtilities.GetHostHandshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, nodeReuse: enableNodeReuse, lowPriority: enableLowPriority, is64Bit: EnvironmentUtilities.Is64BitProcess))); }
/// <summary> /// Finds or creates a child process which can act as a node. /// </summary> /// <returns>The pipe stream representing the node.</returns> protected NodeContext GetNode(string msbuildLocation, string commandLineArgs, int nodeId, INodePacketFactory factory, Handshake hostHandshake, NodeContextTerminateDelegate terminateNode) { #if DEBUG if (Execution.BuildManager.WaitForDebugger) { commandLineArgs += " /wfd"; } #endif if (String.IsNullOrEmpty(msbuildLocation)) { msbuildLocation = _componentHost.BuildParameters.NodeExeLocation; } if (String.IsNullOrEmpty(msbuildLocation)) { string msbuildExeName = Environment.GetEnvironmentVariable("MSBUILD_EXE_NAME"); if (!String.IsNullOrEmpty(msbuildExeName)) { // we assume that MSBUILD_EXE_NAME is, in fact, just the name. msbuildLocation = Path.Combine(msbuildExeName, ".exe"); } } #if FEATURE_NODE_REUSE // Try to connect to idle nodes if node reuse is enabled. if (_componentHost.BuildParameters.EnableNodeReuse) { (string expectedProcessName, List <Process> processes)runningNodesTuple = GetPossibleRunningNodes(msbuildLocation); CommunicationsUtilities.Trace("Attempting to connect to each existing {1} process in turn to establish node {0}...", nodeId, runningNodesTuple.expectedProcessName); foreach (Process nodeProcess in runningNodesTuple.processes) { if (nodeProcess.Id == Process.GetCurrentProcess().Id) { continue; } // Get the full context of this inspection so that we can always skip this process when we have the same taskhost context string nodeLookupKey = GetProcessesToIgnoreKey(hostHandshake, nodeProcess.Id); if (_processesToIgnore.Contains(nodeLookupKey)) { continue; } // We don't need to check this again _processesToIgnore.Add(nodeLookupKey); // Attempt to connect to each process in turn. Stream nodeStream = TryConnectToProcess(nodeProcess.Id, 0 /* poll, don't wait for connections */, hostHandshake); if (nodeStream != null) { // Connection successful, use this node. CommunicationsUtilities.Trace("Successfully connected to existed node {0} which is PID {1}", nodeId, nodeProcess.Id); return(new NodeContext(nodeId, nodeProcess, nodeStream, factory, terminateNode)); } } } #endif // None of the processes we tried to connect to allowed a connection, so create a new one. // We try this in a loop because it is possible that there is another MSBuild multiproc // host process running somewhere which is also trying to create nodes right now. It might // find our newly created node and connect to it before we get a chance. CommunicationsUtilities.Trace("Could not connect to existing process, now creating a process..."); int retries = NodeCreationRetries; while (retries-- > 0) { #if FEATURE_NET35_TASKHOST // We will also check to see if .NET 3.5 is installed in the case where we need to launch a CLR2 OOP TaskHost. // Failure to detect this has been known to stall builds when Windows pops up a related dialog. // It's also a waste of time when we attempt several times to launch multiple MSBuildTaskHost.exe (CLR2 TaskHost) // nodes because we should never be able to connect in this case. string taskHostNameForClr2TaskHost = Path.GetFileNameWithoutExtension(NodeProviderOutOfProcTaskHost.TaskHostNameForClr2TaskHost); if (Path.GetFileNameWithoutExtension(msbuildLocation).Equals(taskHostNameForClr2TaskHost, StringComparison.OrdinalIgnoreCase)) { if (FrameworkLocationHelper.GetPathToDotNetFrameworkV35(DotNetFrameworkArchitecture.Current) == null) { CommunicationsUtilities.Trace ( "Failed to launch node from {0}. The required .NET Framework v3.5 is not installed or enabled. CommandLine: {1}", msbuildLocation, commandLineArgs ); string nodeFailedToLaunchError = ResourceUtilities.GetResourceString("TaskHostNodeFailedToLaunchErrorCodeNet35NotInstalled"); throw new NodeFailedToLaunchException(null, nodeFailedToLaunchError); } } #endif // Create the node process Process msbuildProcess = LaunchNode(msbuildLocation, commandLineArgs); _processesToIgnore.Add(GetProcessesToIgnoreKey(hostHandshake, msbuildProcess.Id)); // Note, when running under IMAGEFILEEXECUTIONOPTIONS registry key to debug, the process ID // gotten back from CreateProcess is that of the debugger, which causes this to try to connect // to the debugger process. Instead, use MSBUILDDEBUGONSTART=1 // Now try to connect to it. Stream nodeStream = TryConnectToProcess(msbuildProcess.Id, TimeoutForNewNodeCreation, hostHandshake); if (nodeStream != null) { // Connection successful, use this node. CommunicationsUtilities.Trace("Successfully connected to created node {0} which is PID {1}", nodeId, msbuildProcess.Id); return(new NodeContext(nodeId, msbuildProcess, nodeStream, factory, terminateNode)); } } // We were unable to launch a node. CommunicationsUtilities.Trace("FAILED TO CONNECT TO A CHILD NODE"); return(null); }
protected override Handshake GetHandshake() { return(new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: true))); }
///<summary> /// Hash the string independent of bitness and target framework. /// </summary> internal static int StableStringHash(string toHash) { return(CommunicationsUtilities.GetHashCode(toHash)); }
/// <summary> /// Sends the specified packet to this node. /// </summary> /// <param name="packet">The packet to send.</param> public void SendData(INodePacket packet) { MemoryStream writeStream = new MemoryStream(); INodePacketTranslator writeTranslator = NodePacketTranslator.GetWriteTranslator(writeStream); try { writeStream.WriteByte((byte)packet.Type); // Pad for the packet length writeStream.Write(BitConverter.GetBytes((int)0), 0, 4); packet.Translate(writeTranslator); // Now plug in the real packet length writeStream.Position = 1; writeStream.Write(BitConverter.GetBytes((int)writeStream.Length - 5), 0, 4); #if FALSE if (trace) // Avoid method call { CommunicationsUtilities.Trace(nodeId, "Sending Packet of type {0} with length {1}", packet.Type.ToString(), writeStream.Length - 5); } #endif byte[] writeStreamBuffer = writeStream.GetBuffer(); for (int i = 0; i < writeStream.Length; i += MaxPacketWriteSize) { int lengthToWrite = Math.Min((int)writeStream.Length - i, MaxPacketWriteSize); if ((int)writeStream.Length - i <= MaxPacketWriteSize) { // We are done, write the last bit asynchronously. This is actually the general case for // most packets in the build, and the asynchronous behavior here is desirable. #if FEATURE_APM _serverToClientStream.BeginWrite(writeStreamBuffer, i, lengthToWrite, PacketWriteComplete, null); #else _serverToClientStream.WriteAsync(writeStreamBuffer, i, lengthToWrite); #endif return; } else { // If this packet is longer that we can write in one go, then we need to break it up. We can't // return out of this function and let the rest of the system continue because another operation // might want to send data immediately afterward, and that could result in overlapping writes // to the pipe on different threads. #if FEATURE_APM IAsyncResult result = _serverToClientStream.BeginWrite(writeStream.GetBuffer(), i, lengthToWrite, null, null); _serverToClientStream.EndWrite(result); #else _serverToClientStream.Write(writeStreamBuffer, i, lengthToWrite); #endif } } } catch (IOException e) { // Do nothing here because any exception will be caught by the async read handler CommunicationsUtilities.Trace(_nodeId, "EXCEPTION in SendData: {0}", e); } catch (ObjectDisposedException) // This happens if a child dies unexpectedly { // Do nothing here because any exception will be caught by the async read handler } }
public async Task RunPacketReadLoopAsync() { while (true) { try { int bytesRead = await CommunicationsUtilities.ReadAsync(_clientToServerStream, _headerByte, _headerByte.Length); if (!ProcessHeaderBytesRead(bytesRead)) { return; } } catch (IOException e) { CommunicationsUtilities.Trace(_nodeId, "EXCEPTION in RunPacketReadLoopAsync: {0}", e); _packetFactory.RoutePacket(_nodeId, new NodeShutdown(NodeShutdownReason.ConnectionFailed)); Close(); return; } NodePacketType packetType = (NodePacketType)_headerByte[0]; int packetLength = BitConverter.ToInt32(_headerByte, 1); byte[] packetData; if (packetLength < _smallReadBuffer.Length) { packetData = _smallReadBuffer; } else { // Preallocated buffer is not large enough to hold the body. Allocate now, but don't hold it forever. packetData = new byte[packetLength]; } try { int bytesRead = await CommunicationsUtilities.ReadAsync(_clientToServerStream, packetData, packetLength); if (!ProcessBodyBytesRead(bytesRead, packetLength, packetType)) { return; } } catch (IOException e) { CommunicationsUtilities.Trace(_nodeId, "EXCEPTION in RunPacketReadLoopAsync (Reading): {0}", e); _packetFactory.RoutePacket(_nodeId, new NodeShutdown(NodeShutdownReason.ConnectionFailed)); Close(); return; } // Read and route the packet. if (!ReadAndRoutePacket(packetType, packetData, packetLength)) { return; } if (packetType == NodePacketType.NodeShutdown) { Close(); return; } } }
/// <summary> /// Returns the client handshake for this node endpoint /// </summary> protected override long GetClientHandshake() { return(CommunicationsUtilities.GetClientHandshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: true))); }
/// <summary> /// Attempts to connect to the specified process. /// </summary> private Stream TryConnectToProcess(int nodeProcessId, int timeout, long hostHandshake, long clientHandshake) { // Try and connect to the process. string pipeName = "MSBuild" + nodeProcessId; NamedPipeClientStream nodeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous #if FEATURE_PIPEOPTIONS_CURRENTUSERONLY | PipeOptions.CurrentUserOnly #endif ); CommunicationsUtilities.Trace("Attempting connect to PID {0} with pipe {1} with timeout {2} ms", nodeProcessId, pipeName, timeout); try { nodeStream.Connect(timeout); #if !MONO && !FEATURE_PIPEOPTIONS_CURRENTUSERONLY if (NativeMethodsShared.IsWindows) { // Verify that the owner of the pipe is us. This prevents a security hole where a remote node has // been faked up with ACLs that would let us attach to it. It could then issue fake build requests back to // us, potentially causing us to execute builds that do harmful or unexpected things. The pipe owner can // only be set to the user's own SID by a normal, unprivileged process. The conditions where a faked up // remote node could set the owner to something else would also let it change owners on other objects, so // this would be a security flaw upstream of us. ValidateRemotePipeSecurityOnWindows(nodeStream); } #endif CommunicationsUtilities.Trace("Writing handshake to pipe {0}", pipeName); nodeStream.WriteLongForHandshake(hostHandshake); CommunicationsUtilities.Trace("Reading handshake from pipe {0}", pipeName); #if NETCOREAPP2_1 long handshake = nodeStream.ReadLongForHandshake(timeout); #else long handshake = nodeStream.ReadLongForHandshake(); #endif if (handshake != clientHandshake) { CommunicationsUtilities.Trace("Handshake failed. Received {0} from client not {1}. Probably the client is a different MSBuild build.", handshake, clientHandshake); throw new InvalidOperationException(); } // We got a connection. CommunicationsUtilities.Trace("Successfully connected to pipe {0}...!", pipeName); return(nodeStream); } catch (Exception e) { if (ExceptionHandling.IsCriticalException(e)) { throw; } // Can be: // UnauthorizedAccessException -- Couldn't connect, might not be a node. // IOException -- Couldn't connect, already in use. // TimeoutException -- Couldn't connect, might not be a node. // InvalidOperationException – Couldn’t connect, probably a different build CommunicationsUtilities.Trace("Failed to connect to pipe {0}. {1}", pipeName, e.Message.TrimEnd()); // If we don't close any stream, we might hang up the child if (nodeStream != null) { nodeStream.Dispose(); } } return(null); }
private void RunReadLoop(Stream localReadPipe, Stream localWritePipe, ConcurrentQueue <INodePacket> localPacketQueue, AutoResetEvent localPacketAvailable, AutoResetEvent localTerminatePacketPump) { // Ordering of the wait handles is important. The first signalled wait handle in the array // will be returned by WaitAny if multiple wait handles are signalled. We prefer to have the // terminate event triggered so that we cannot get into a situation where packets are being // spammed to the endpoint and it never gets an opportunity to shutdown. CommunicationsUtilities.Trace("Entering read loop."); byte[] headerByte = new byte[5]; #if FEATURE_APM IAsyncResult result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); #else Task <int> readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length); #endif bool exitLoop = false; do { // Ordering is important. We want packetAvailable to supercede terminate otherwise we will not properly wait for all // packets to be sent by other threads which are shutting down, such as the logging thread. WaitHandle[] handles = new WaitHandle[] { #if FEATURE_APM result.AsyncWaitHandle, #else ((IAsyncResult)readTask).AsyncWaitHandle, #endif localPacketAvailable, localTerminatePacketPump }; int waitId = WaitHandle.WaitAny(handles); switch (waitId) { case 0: { int bytesRead = 0; try { #if FEATURE_APM bytesRead = localReadPipe.EndRead(result); #else bytesRead = readTask.Result; #endif } catch (Exception e) { // Lost communications. Abort (but allow node reuse) CommunicationsUtilities.Trace("Exception reading from server. {0}", e); ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Inactive); exitLoop = true; break; } if (bytesRead != headerByte.Length) { // Incomplete read. Abort. if (bytesRead == 0) { CommunicationsUtilities.Trace("Parent disconnected abruptly"); } else { CommunicationsUtilities.Trace("Incomplete header read from server. {0} of {1} bytes read", bytesRead, headerByte.Length); } ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; break; } NodePacketType packetType = (NodePacketType)Enum.ToObject(typeof(NodePacketType), headerByte[0]); try { _packetFactory.DeserializeAndRoutePacket(0, packetType, BinaryTranslator.GetReadTranslator(localReadPipe, _sharedReadBuffer)); } catch (Exception e) { // Error while deserializing or handling packet. Abort. CommunicationsUtilities.Trace("Exception while deserializing packet {0}: {1}", packetType, e); ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; break; } #if FEATURE_APM result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); #else readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length); #endif } break; case 1: case 2: try { // Write out all the queued packets. INodePacket packet; while (localPacketQueue.TryDequeue(out packet)) { MemoryStream packetStream = new MemoryStream(); ITranslator writeTranslator = BinaryTranslator.GetWriteTranslator(packetStream); packetStream.WriteByte((byte)packet.Type); // Pad for packet length packetStream.Write(BitConverter.GetBytes((int)0), 0, 4); // Reset the position in the write buffer. packet.Translate(writeTranslator); // Now write in the actual packet length packetStream.Position = 1; packetStream.Write(BitConverter.GetBytes((int)packetStream.Length - 5), 0, 4); localWritePipe.Write(packetStream.GetBuffer(), 0, (int)packetStream.Length); } } catch (Exception e) { // Error while deserializing or handling packet. Abort. CommunicationsUtilities.Trace("Exception while serializing packets: {0}", e); ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; break; } if (waitId == 2) { CommunicationsUtilities.Trace("Disconnecting voluntarily"); ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; } break; default: ErrorUtilities.ThrowInternalError("waitId {0} out of range.", waitId); break; } }while (!exitLoop); }
/// <summary> /// Returns the client handshake for this node endpoint /// </summary> protected override long GetClientHandshake() { long clientHandshake = CommunicationsUtilities.GetTaskHostClientHandshake(CommunicationsUtilities.GetCurrentTaskHostContext()); return(clientHandshake); }
/// <summary> /// Attempts to connect to the specified process. /// </summary> private Stream TryConnectToProcess(int nodeProcessId, int timeout, Handshake handshake) { // Try and connect to the process. string pipeName = NamedPipeUtil.GetPipeNameOrPath("MSBuild" + nodeProcessId); NamedPipeClientStream nodeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous #if FEATURE_PIPEOPTIONS_CURRENTUSERONLY | PipeOptions.CurrentUserOnly #endif ); CommunicationsUtilities.Trace("Attempting connect to PID {0} with pipe {1} with timeout {2} ms", nodeProcessId, pipeName, timeout); try { nodeStream.Connect(timeout); #if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY if (NativeMethodsShared.IsWindows && !NativeMethodsShared.IsMono) { // Verify that the owner of the pipe is us. This prevents a security hole where a remote node has // been faked up with ACLs that would let us attach to it. It could then issue fake build requests back to // us, potentially causing us to execute builds that do harmful or unexpected things. The pipe owner can // only be set to the user's own SID by a normal, unprivileged process. The conditions where a faked up // remote node could set the owner to something else would also let it change owners on other objects, so // this would be a security flaw upstream of us. ValidateRemotePipeSecurityOnWindows(nodeStream); } #endif int[] handshakeComponents = handshake.RetrieveHandshakeComponents(); for (int i = 0; i < handshakeComponents.Length; i++) { CommunicationsUtilities.Trace("Writing handshake part {0} ({1}) to pipe {2}", i, handshakeComponents[i], pipeName); nodeStream.WriteIntForHandshake(handshakeComponents[i]); } // This indicates that we have finished all the parts of our handshake; hopefully the endpoint has as well. nodeStream.WriteEndOfHandshakeSignal(); CommunicationsUtilities.Trace("Reading handshake from pipe {0}", pipeName); #if NETCOREAPP2_1_OR_GREATER || MONO nodeStream.ReadEndOfHandshakeSignal(true, timeout); #else nodeStream.ReadEndOfHandshakeSignal(true); #endif // We got a connection. CommunicationsUtilities.Trace("Successfully connected to pipe {0}...!", pipeName); return(nodeStream); } catch (Exception e) when(!ExceptionHandling.IsCriticalException(e)) { // Can be: // UnauthorizedAccessException -- Couldn't connect, might not be a node. // IOException -- Couldn't connect, already in use. // TimeoutException -- Couldn't connect, might not be a node. // InvalidOperationException – Couldn’t connect, probably a different build CommunicationsUtilities.Trace("Failed to connect to pipe {0}. {1}", pipeName, e.Message.TrimEnd()); // If we don't close any stream, we might hang up the child nodeStream?.Dispose(); } return(null); }
/// <summary> /// Magic number sent by the client to the host during the handshake. /// Munged version of the host handshake. /// </summary> internal static long GetClientHandshake(bool enableNodeReuse, bool enableLowPriority) { return(CommunicationsUtilities.GetClientHandshake(CommunicationsUtilities.GetHandshakeOptions(false, nodeReuse: enableNodeReuse, lowPriority: enableLowPriority, is64Bit: EnvironmentUtilities.Is64BitProcess))); }
/// <summary> /// Creates a new MSBuild process /// </summary> private Process LaunchNode(string msbuildLocation, string commandLineArgs) { // Should always have been set already. ErrorUtilities.VerifyThrowInternalLength(msbuildLocation, nameof(msbuildLocation)); if (!FileSystems.Default.FileExists(msbuildLocation)) { throw new BuildAbortedException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("CouldNotFindMSBuildExe", msbuildLocation)); } // Repeat the executable name as the first token of the command line because the command line // parser logic expects it and will otherwise skip the first argument commandLineArgs = $"\"{msbuildLocation}\" {commandLineArgs}"; BackendNativeMethods.STARTUP_INFO startInfo = new(); startInfo.cb = Marshal.SizeOf <BackendNativeMethods.STARTUP_INFO>(); // Null out the process handles so that the parent process does not wait for the child process // to exit before it can exit. uint creationFlags = 0; if (Traits.Instance.EscapeHatches.EnsureStdOutForChildNodesIsPrimaryStdout) { creationFlags = BackendNativeMethods.NORMALPRIORITYCLASS; } if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDNODEWINDOW"))) { if (!Traits.Instance.EscapeHatches.EnsureStdOutForChildNodesIsPrimaryStdout) { // Redirect the streams of worker nodes so that this MSBuild.exe's // parent doesn't wait on idle worker nodes to close streams // after the build is complete. startInfo.hStdError = BackendNativeMethods.InvalidHandle; startInfo.hStdInput = BackendNativeMethods.InvalidHandle; startInfo.hStdOutput = BackendNativeMethods.InvalidHandle; startInfo.dwFlags = BackendNativeMethods.STARTFUSESTDHANDLES; creationFlags |= BackendNativeMethods.CREATENOWINDOW; } } else { creationFlags |= BackendNativeMethods.CREATE_NEW_CONSOLE; } CommunicationsUtilities.Trace("Launching node from {0}", msbuildLocation); string exeName = msbuildLocation; #if RUNTIME_TYPE_NETCORE || MONO // Mono automagically uses the current mono, to execute a managed assembly if (!NativeMethodsShared.IsMono) { // Run the child process with the same host as the currently-running process. exeName = GetCurrentHost(); } #endif if (!NativeMethodsShared.IsWindows) { ProcessStartInfo processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = exeName; processStartInfo.Arguments = commandLineArgs; if (!Traits.Instance.EscapeHatches.EnsureStdOutForChildNodesIsPrimaryStdout) { // Redirect the streams of worker nodes so that this MSBuild.exe's // parent doesn't wait on idle worker nodes to close streams // after the build is complete. processStartInfo.RedirectStandardInput = true; processStartInfo.RedirectStandardOutput = true; processStartInfo.RedirectStandardError = true; processStartInfo.CreateNoWindow = (creationFlags | BackendNativeMethods.CREATENOWINDOW) == BackendNativeMethods.CREATENOWINDOW; } processStartInfo.UseShellExecute = false; Process process; try { process = Process.Start(processStartInfo); } catch (Exception ex) { CommunicationsUtilities.Trace ( "Failed to launch node from {0}. CommandLine: {1}" + Environment.NewLine + "{2}", msbuildLocation, commandLineArgs, ex.ToString() ); throw new NodeFailedToLaunchException(ex); } CommunicationsUtilities.Trace("Successfully launched {1} node with PID {0}", process.Id, exeName); return(process); } else { #if RUNTIME_TYPE_NETCORE // Repeat the executable name in the args to suit CreateProcess commandLineArgs = $"\"{exeName}\" {commandLineArgs}"; #endif BackendNativeMethods.PROCESS_INFORMATION processInfo = new(); BackendNativeMethods.SECURITY_ATTRIBUTES processSecurityAttributes = new(); BackendNativeMethods.SECURITY_ATTRIBUTES threadSecurityAttributes = new(); processSecurityAttributes.nLength = Marshal.SizeOf <BackendNativeMethods.SECURITY_ATTRIBUTES>(); threadSecurityAttributes.nLength = Marshal.SizeOf <BackendNativeMethods.SECURITY_ATTRIBUTES>(); bool result = BackendNativeMethods.CreateProcess ( exeName, commandLineArgs, ref processSecurityAttributes, ref threadSecurityAttributes, false, creationFlags, BackendNativeMethods.NullPtr, null, ref startInfo, out processInfo ); if (!result) { // Creating an instance of this exception calls GetLastWin32Error and also converts it to a user-friendly string. System.ComponentModel.Win32Exception e = new System.ComponentModel.Win32Exception(); CommunicationsUtilities.Trace ( "Failed to launch node from {0}. System32 Error code {1}. Description {2}. CommandLine: {2}", msbuildLocation, e.NativeErrorCode.ToString(CultureInfo.InvariantCulture), e.Message, commandLineArgs ); throw new NodeFailedToLaunchException(e.NativeErrorCode.ToString(CultureInfo.InvariantCulture), e.Message); } int childProcessId = processInfo.dwProcessId; if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != NativeMethods.InvalidHandle) { NativeMethodsShared.CloseHandle(processInfo.hProcess); } if (processInfo.hThread != IntPtr.Zero && processInfo.hThread != NativeMethods.InvalidHandle) { NativeMethodsShared.CloseHandle(processInfo.hThread); } CommunicationsUtilities.Trace("Successfully launched {1} node with PID {0}", childProcessId, exeName); return(Process.GetProcessById(childProcessId)); } }
public bool Execute() { // log that we are about to spawn the task host string runtime = _taskHostParameters[XMakeAttributes.runtime]; string architecture = _taskHostParameters[XMakeAttributes.architecture]; _taskLoggingContext.LogComment(MessageImportance.Low, "ExecutingTaskInTaskHost", _taskType.Type.Name, _taskType.Assembly.AssemblyLocation, runtime, architecture); // set up the node lock (_taskHostLock) { _taskHostProvider = (NodeProviderOutOfProcTaskHost)_buildComponentHost.GetComponent(BuildComponentType.OutOfProcTaskHostNodeProvider); ErrorUtilities.VerifyThrowInternalNull(_taskHostProvider, "taskHostProvider"); } TaskHostConfiguration hostConfiguration = new TaskHostConfiguration ( _buildComponentHost.BuildParameters.NodeId, NativeMethodsShared.GetCurrentDirectory(), CommunicationsUtilities.GetEnvironmentVariables(), _buildComponentHost.BuildParameters.Culture, _buildComponentHost.BuildParameters.UICulture, #if FEATURE_APPDOMAIN _appDomainSetup, #endif BuildEngine.LineNumberOfTaskNode, BuildEngine.ColumnNumberOfTaskNode, BuildEngine.ProjectFileOfTaskNode, BuildEngine.ContinueOnError, _taskType.Type.FullName, AssemblyUtilities.GetAssemblyLocation(_taskType.Type.GetTypeInfo().Assembly), _buildComponentHost.BuildParameters.LogTaskInputs, _setParameters, new Dictionary <string, string>(_buildComponentHost.BuildParameters.GlobalProperties), _taskLoggingContext.GetWarningsAsErrors(), _taskLoggingContext.GetWarningsAsMessages() ); try { lock (_taskHostLock) { _requiredContext = CommunicationsUtilities.GetHandshakeOptions(taskHost: true, taskHostParameters: _taskHostParameters); _connectedToTaskHost = _taskHostProvider.AcquireAndSetUpHost(_requiredContext, this, this, hostConfiguration); } if (_connectedToTaskHost) { try { bool taskFinished = false; while (!taskFinished) { _packetReceivedEvent.WaitOne(); INodePacket packet = null; // Handle the packet that's coming in while (_receivedPackets.TryDequeue(out packet)) { if (packet != null) { HandlePacket(packet, out taskFinished); } } } } finally { lock (_taskHostLock) { _taskHostProvider.DisconnectFromHost(_requiredContext); _connectedToTaskHost = false; } } } else { LogErrorUnableToCreateTaskHost(_requiredContext, runtime, architecture, null); } } catch (BuildAbortedException) { LogErrorUnableToCreateTaskHost(_requiredContext, runtime, architecture, null); } catch (NodeFailedToLaunchException e) { LogErrorUnableToCreateTaskHost(_requiredContext, runtime, architecture, e); } return(_taskExecutionSucceeded); }
/// <summary> /// Creates a new MSBuild process /// </summary> private int LaunchNode(string msbuildLocation, string commandLineArgs) { // Should always have been set already. ErrorUtilities.VerifyThrowInternalLength(msbuildLocation, "msbuildLocation"); if (!File.Exists(msbuildLocation)) { throw new BuildAbortedException(ResourceUtilities.FormatResourceString("CouldNotFindMSBuildExe", msbuildLocation)); } // Repeat the executable name as the first token of the command line because the command line // parser logic expects it and will otherwise skip the first argument commandLineArgs = msbuildLocation + " " + commandLineArgs; BackendNativeMethods.STARTUP_INFO startInfo = new BackendNativeMethods.STARTUP_INFO(); startInfo.cb = Marshal.SizeOf <BackendNativeMethods.STARTUP_INFO>(); // Null out the process handles so that the parent process does not wait for the child process // to exit before it can exit. uint creationFlags = 0; startInfo.dwFlags = BackendNativeMethods.STARTFUSESTDHANDLES; if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDNODEWINDOW"))) { startInfo.hStdError = BackendNativeMethods.InvalidHandle; startInfo.hStdInput = BackendNativeMethods.InvalidHandle; startInfo.hStdOutput = BackendNativeMethods.InvalidHandle; creationFlags = creationFlags | BackendNativeMethods.CREATENOWINDOW; } else { creationFlags = creationFlags | BackendNativeMethods.CREATE_NEW_CONSOLE; } BackendNativeMethods.SECURITY_ATTRIBUTES processSecurityAttributes = new BackendNativeMethods.SECURITY_ATTRIBUTES(); BackendNativeMethods.SECURITY_ATTRIBUTES threadSecurityAttributes = new BackendNativeMethods.SECURITY_ATTRIBUTES(); processSecurityAttributes.nLength = Marshal.SizeOf <BackendNativeMethods.SECURITY_ATTRIBUTES>(); threadSecurityAttributes.nLength = Marshal.SizeOf <BackendNativeMethods.SECURITY_ATTRIBUTES>(); CommunicationsUtilities.Trace("Launching node from {0}", msbuildLocation); string exeName = msbuildLocation; #if RUNTIME_TYPE_NETCORE // Run the child process with the same host as the currently-running process. exeName = GetCurrentHost(); commandLineArgs = "\"" + msbuildLocation + "\" " + commandLineArgs; if (NativeMethodsShared.IsWindows) { // Repeat the executable name _again_ because Core MSBuild expects it commandLineArgs = exeName + " " + commandLineArgs; } #endif if (!NativeMethodsShared.IsWindows) { ProcessStartInfo processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = exeName; processStartInfo.Arguments = commandLineArgs; processStartInfo.CreateNoWindow = (creationFlags | BackendNativeMethods.CREATENOWINDOW) == BackendNativeMethods.CREATENOWINDOW; processStartInfo.UseShellExecute = false; Process process; try { process = Process.Start(processStartInfo); } catch (Exception ex) { CommunicationsUtilities.Trace ( "Failed to launch node from {0}. CommandLine: {1}" + Environment.NewLine + "{2}", msbuildLocation, commandLineArgs, ex.ToString() ); throw new NodeFailedToLaunchException(ex); } CommunicationsUtilities.Trace("Successfully launched msbuild.exe node with PID {0}", process.Id); return(process.Id); } else { BackendNativeMethods.PROCESS_INFORMATION processInfo = new BackendNativeMethods.PROCESS_INFORMATION(); bool result = BackendNativeMethods.CreateProcess ( exeName, commandLineArgs, ref processSecurityAttributes, ref threadSecurityAttributes, #if FEATURE_NAMED_PIPES_FULL_DUPLEX false, #else true, // Inherit handles for the anonymous pipes for IPC #endif creationFlags, BackendNativeMethods.NullPtr, null, ref startInfo, out processInfo ); if (!result) { // Creating an instance of this exception calls GetLastWin32Error and also converts it to a user-friendly string. System.ComponentModel.Win32Exception e = new System.ComponentModel.Win32Exception(); CommunicationsUtilities.Trace ( "Failed to launch node from {0}. System32 Error code {1}. Description {2}. CommandLine: {2}", msbuildLocation, e.NativeErrorCode.ToString(CultureInfo.InvariantCulture), e.Message, commandLineArgs ); throw new NodeFailedToLaunchException(e.NativeErrorCode.ToString(CultureInfo.InvariantCulture), e.Message); } int childProcessId = processInfo.dwProcessId; if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != NativeMethods.InvalidHandle) { NativeMethodsShared.CloseHandle(processInfo.hProcess); } if (processInfo.hThread != IntPtr.Zero && processInfo.hThread != NativeMethods.InvalidHandle) { NativeMethodsShared.CloseHandle(processInfo.hThread); } CommunicationsUtilities.Trace("Successfully launched msbuild.exe node with PID {0}", childProcessId); return(childProcessId); } }