public async Task <BuildRequest> ReadBuildRequestAsync(CancellationToken cancellationToken) { var request = await BuildRequest.ReadAsync(Stream, cancellationToken).ConfigureAwait(false); // Now that we've read data from the stream we can validate the identity. if (!NamedPipeUtil.CheckClientElevationMatches(Stream)) { throw new Exception("Client identity does not match server identity."); } // The result is deliberately discarded here. The idea is to kick off the monitor code and // when it completes it will trigger the task. Don't want to block on that here. _ = MonitorDisconnect(); return(request); async Task MonitorDisconnect() { try { await BuildServerConnection.MonitorDisconnectAsync(Stream, request.RequestId, Logger, DisconnectCancellationTokenSource.Token).ConfigureAwait(false); } catch (Exception ex) { Logger.LogException(ex, $"Error monitoring disconnect {request.RequestId}"); } finally { DisconnectTaskCompletionSource.TrySetResult(this); } } }
/// <summary> /// Connect to the pipe for a given directory and return it. /// Throws on cancellation. /// </summary> /// <param name="pipeName">Name of the named pipe to connect to.</param> /// <param name="timeoutMs">Timeout to allow in connecting to process.</param> /// <param name="cancellationToken">Cancellation token to cancel connection to server.</param> /// <returns> /// An open <see cref="NamedPipeClientStream"/> to the server process or null on failure. /// </returns> internal static async Task <NamedPipeClientStream> TryConnectToServerAsync( string pipeName, int timeoutMs, CancellationToken cancellationToken) { NamedPipeClientStream pipeStream; try { // If the pipe path would be too long, there cannot be a server at the other end. // We're not using a saved temp path here because pipes are created with // Path.GetTempPath() in corefx NamedPipeClientStream and we want to replicate that behavior. if (IsPipePathTooLong(pipeName, Path.GetTempPath())) { return(null); } // Machine-local named pipes are named "\\.\pipe\<pipename>". // We use the SHA1 of the directory the compiler exes live in as the pipe name. // The NamedPipeClientStream class handles the "\\.\pipe\" part for us. Log("Attempt to open named pipe '{0}'", pipeName); pipeStream = NamedPipeUtil.CreateClient(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); cancellationToken.ThrowIfCancellationRequested(); Log("Attempt to connect named pipe '{0}'", pipeName); try { await pipeStream.ConnectAsync(timeoutMs, cancellationToken).ConfigureAwait(false); } catch (Exception e) when(e is IOException || e is TimeoutException) { // Note: IOException can also indicate timeout. From docs: // TimeoutException: Could not connect to the server within the // specified timeout period. // IOException: The server is connected to another client and the // time-out period has expired. Log($"Connecting to server timed out after {timeoutMs} ms"); return(null); } Log("Named pipe '{0}' connected", pipeName); cancellationToken.ThrowIfCancellationRequested(); // Verify that we own the pipe. if (!NamedPipeUtil.CheckPipeConnectionOwnership(pipeStream)) { Log("Owner of named pipe is incorrect"); return(null); } return(pipeStream); } catch (Exception e) when(!(e is TaskCanceledException || e is OperationCanceledException)) { LogException(e, "Exception while connecting to process"); return(null); } }
/// <summary> /// Creates a Task that waits for a client connection to occur and returns the connected /// <see cref="NamedPipeServerStream"/> object. Throws on any connection error. /// </summary> /// <param name="cancellationToken">Used to cancel the connection sequence.</param> private async Task <NamedPipeServerStream> CreateListenTaskCore(CancellationToken cancellationToken) { // Create the pipe and begin waiting for a connection. This // doesn't block, but could fail in certain circumstances, such // as Windows refusing to create the pipe for some reason // (out of handles?), or the pipe was disconnected before we // starting listening. CompilerServerLogger.Log("Constructing pipe '{0}'.", _pipeName); var pipeStream = NamedPipeUtil.CreateServer(_pipeName); CompilerServerLogger.Log("Successfully constructed pipe '{0}'.", _pipeName); CompilerServerLogger.Log("Waiting for new connection"); await pipeStream.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false); CompilerServerLogger.Log("Pipe connection detected."); if (Environment.Is64BitProcess || MemoryHelper.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); return(pipeStream); } pipeStream.Close(); throw new Exception("Insufficient resources to process new connection."); }
private static async Task ListenCoreAsync( string pipeName, AsyncQueue <ListenResult> queue, Func <string> getClientLoggingIdentifier, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { NamedPipeServerStream?pipeStream = null; try { // Create the pipe and begin waiting for a connection. This // doesn't block, but could fail in certain circumstances, such // as Windows refusing to create the pipe for some reason // (out of handles?), or the pipe was disconnected before we // starting listening CompilerServerLogger.Log($"Constructing pipe and waiting for connections '{pipeName}'"); pipeStream = NamedPipeUtil.CreateServer(pipeName); // The WaitForConnectionAsync API does not fully respect the provided CancellationToken // on all platforms: // // https://github.com/dotnet/runtime/issues/40289 // // To mitigate this we need to setup a cancellation Task and dispose the NamedPipeServerStream // if it ever completes. Once all of the NamedPipeServerStream for the given pipe name are // disposed they will all exit the WaitForConnectionAsync method var connectTask = pipeStream.WaitForConnectionAsync(cancellationToken); if (!PlatformInformation.IsWindows) { var cancelTask = Task.Delay(TimeSpan.FromMilliseconds(-1), cancellationToken); var completedTask = await Task.WhenAny(new[] { connectTask, cancelTask }).ConfigureAwait(false); if (completedTask == cancelTask) { throw new OperationCanceledException(); } } await connectTask.ConfigureAwait(false); CompilerServerLogger.Log("Pipe connection established."); var connection = new NamedPipeClientConnection(pipeStream, getClientLoggingIdentifier()); queue.Enqueue(new ListenResult(connection: connection)); } catch (OperationCanceledException) { // Expected when the host is shutting down. CompilerServerLogger.Log($"Pipe connection cancelled"); pipeStream?.Dispose(); } catch (Exception ex) { CompilerServerLogger.LogException(ex, $"Pipe connection error"); queue.Enqueue(new ListenResult(exception: ex)); pipeStream?.Dispose(); } } }
private void ValidateBuildRequest(BuildRequest request) { // Now that we've read data from the stream we can validate the identity. if (!NamedPipeUtil.CheckClientElevationMatches(_stream)) { throw new Exception("Client identity does not match server identity."); } }
/// <summary> /// Constructs <seealso cref="NamedPipeClientStream"/> /// </summary> NamedPipeClientStream IRarBuildEngine.GetRarClientStream(string pipeName, int timeout) { BuildParameters parameters = _host.BuildParameters; Handshake handshake = NodeProviderOutOfProc.GetHandshake(enableNodeReuse: parameters.EnableNodeReuse, enableLowPriority: parameters.LowPriority, specialNode: true); return(NamedPipeUtil.TryConnectToProcess(pipeName, timeout, handshake)); }
/// <summary> /// Instantiates an endpoint to act as a client /// </summary> internal void InternalConstruct() { _status = LinkStatus.Inactive; _asyncDataMonitor = new object(); _sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer(); _packetStream = new MemoryStream(); _binaryWriter = new BinaryWriter(_packetStream); string pipeName = NamedPipeUtil.GetPipeNameOrPath(); #if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR if (!NativeMethodsShared.IsMono) { SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; PipeSecurity security = new PipeSecurity(); // Restrict access to just this account. We set the owner specifically here, and on the // pipe client side they will check the owner against this one - they must have identical // SIDs or the client will reject this server. This is used to avoid attacks where a // hacked server creates a less restricted pipe in an attempt to lure us into using it and // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow); security.AddAccessRule(rule); security.SetOwner(identifier); _pipeServer = new NamedPipeServerStream ( pipeName, PipeDirection.InOut, 1, // Only allow one connection at a time. PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough, PipeBufferSize, // Default input buffer PipeBufferSize, // Default output buffer security, HandleInheritability.None ); } else #endif { _pipeServer = new NamedPipeServerStream ( pipeName, PipeDirection.InOut, 1, // Only allow one connection at a time. PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough, PipeBufferSize, // Default input buffer PipeBufferSize // Default output buffer ); } }
/// <summary> /// Instantiates an endpoint to act as a client /// </summary> /// <param name="pipeName">The name of the pipe to which we should connect.</param> internal void InternalConstruct(string pipeName) { ErrorUtilities.VerifyThrowArgumentLength(pipeName, nameof(pipeName)); _debugCommunications = (Environment.GetEnvironmentVariable("MSBUILDDEBUGCOMM") == "1"); _status = LinkStatus.Inactive; _asyncDataMonitor = new object(); _sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer(); _pipeServer = NamedPipeUtil.CreateNamedPipeServer(pipeName, PipeBufferSize, PipeBufferSize); }
private static string MakeSocketPathPrefix() { var candidate = NamedPipeUtil.MakePipePathPrefix(Path.GetTempPath(), (uint)LibChildProcess.GetPid()); const int maxBodyLength = 11; if ((ulong)(candidate.Length + maxBodyLength) > LibChildProcess.GetMaxSocketPathLength().ToUInt64()) { throw new PathTooLongException("Path to the temporary directory is too long to accomodate a socket file."); } return(candidate); }
private static async Task <(NamedPipeClientStream Client, NamedPipeServerStream Server)> CreateNamedPipePair() { var pipeName = Guid.NewGuid().ToString("N").Substring(0, 10); var serverStream = NamedPipeUtil.CreateServer(pipeName); var clientStream = NamedPipeUtil.CreateClient(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); var listenTask = serverStream.WaitForConnectionAsync(); await clientStream.ConnectAsync().ConfigureAwait(false); await listenTask.ConfigureAwait(false); return(clientStream, serverStream); }
public async Task ServerShutdownsDuringProcessing() { using (var readyMre = new ManualResetEvent(initialState: false)) using (var doneMre = new ManualResetEvent(initialState: false)) { var pipeName = Guid.NewGuid().ToString(); var mutexName = BuildServerConnection.GetServerMutexName(pipeName); bool created = false; bool connected = false; var thread = new Thread( () => { using (var stream = NamedPipeUtil.CreateServer(pipeName)) { var mutex = new Mutex( initiallyOwned: true, name: mutexName, createdNew: out created ); readyMre.Set(); stream.WaitForConnection(); connected = true; // Client is waiting for a response. Close the mutex now. Then close the connection // so the client gets an error. mutex.ReleaseMutex(); mutex.Dispose(); stream.Close(); doneMre.WaitOne(); } } ); // Block until the mutex and named pipe is setup. thread.Start(); readyMre.WaitOne(); var exitCode = await RunShutdownAsync(pipeName, waitForProcess : false); // Let the fake server exit. doneMre.Set(); thread.Join(); Assert.Equal(CommonCompiler.Succeeded, exitCode); Assert.True(connected); Assert.True(created); } }
public async Task NoServerConnection() { using (var readyMre = new ManualResetEvent(initialState: false)) using (var doneMre = new ManualResetEvent(initialState: false)) { var pipeName = Guid.NewGuid().ToString(); var mutexName = BuildServerConnection.GetServerMutexName(pipeName); bool created = false; bool connected = false; var thread = new Thread( () => { using ( var mutex = new Mutex( initiallyOwned: true, name: mutexName, createdNew: out created ) ) using (var stream = NamedPipeUtil.CreateServer(pipeName)) { readyMre.Set(); // Get a client connection and then immediately close it. Don't give any response. stream.WaitForConnection(); connected = true; stream.Close(); doneMre.WaitOne(); mutex.ReleaseMutex(); } } ); // Block until the mutex and named pipe is setup. thread.Start(); readyMre.WaitOne(); var exitCode = await RunShutdownAsync(pipeName, waitForProcess : false); // Let the fake server exit. doneMre.Set(); thread.Join(); Assert.Equal(CommonCompiler.Failed, exitCode); Assert.True(connected); Assert.True(created); } }
/// <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 = NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: false, specialNode: false)); // If we couldn't connect attempt to connect to the process with the handshake including low priority. nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: true, specialNode: false)); // Attempt to connect to the non-worker process // Attempt to connect to the process with the handshake without low priority. nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: false, specialNode: true)); // If we couldn't connect attempt to connect to the process with the handshake including low priority. nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: true, specialNode: 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.Id, nodeStream, factory, terminateNode); nodeContext.SendData(new NodeBuildComplete(false /* no node reuse */)); nodeStream.Dispose(); } } }
/// <summary> /// Starts up the node and processes messages until the node is requested to shut down. /// </summary> /// <param name="enableReuse">Whether this node is eligible for reuse later.</param> /// <param name="lowPriority">Whether this node should be running with low priority.</param> /// <param name="shutdownException">The exception which caused shutdown, if any.</param> /// <returns>The reason for shutting down.</returns> public NodeEngineShutdownReason Run(bool enableReuse, bool lowPriority, out Exception shutdownException) { // Console.WriteLine("Run called at {0}", DateTime.Now); string pipeName = NamedPipeUtil.GetPipeNameOrPath("MSBuild" + Process.GetCurrentProcess().Id); _nodeEndpoint = new NodeEndpointOutOfProc(pipeName, this, enableReuse, lowPriority); _nodeEndpoint.OnLinkStatusChanged += OnLinkStatusChanged; _nodeEndpoint.Listen(this); var waitHandles = new WaitHandle[] { _shutdownEvent, _packetReceivedEvent }; // Get the current directory before doing any work. We need this so we can restore the directory when the node shutsdown. while (true) { int index = WaitHandle.WaitAny(waitHandles); switch (index) { case 0: NodeEngineShutdownReason shutdownReason = HandleShutdown(out shutdownException); return(shutdownReason); case 1: while (_receivedPackets.TryDequeue(out INodePacket packet)) { if (packet != null) { HandlePacket(packet); } } break; } } // UNREACHABLE }
/// <summary> /// Connect to the pipe for a given directory and return it. /// Throws on cancellation. /// </summary> /// <param name="pipeName">Name of the named pipe to connect to.</param> /// <param name="timeoutMs">Timeout to allow in connecting to process.</param> /// <param name="cancellationToken">Cancellation token to cancel connection to server.</param> /// <returns> /// An open <see cref="NamedPipeClientStream"/> to the server process or null on failure. /// </returns> internal static async Task <NamedPipeClientStream> TryConnectToServerAsync( string pipeName, int timeoutMs, CancellationToken cancellationToken) { NamedPipeClientStream pipeStream; try { // Machine-local named pipes are named "\\.\pipe\<pipename>". // We use the SHA1 of the directory the compiler exes live in as the pipe name. // The NamedPipeClientStream class handles the "\\.\pipe\" part for us. Log("Attempt to open named pipe '{0}'", pipeName); pipeStream = NamedPipeUtil.CreateClient(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); cancellationToken.ThrowIfCancellationRequested(); Log("Attempt to connect named pipe '{0}'", pipeName); try { // NamedPipeClientStream.ConnectAsync on the "full" framework has a bug where it // tries to move potentially expensive work (actually connecting to the pipe) to // a background thread with Task.Factory.StartNew. However, that call will merely // queue the work onto the TaskScheduler associated with the "current" Task which // does not guarantee it will be processed on a background thread and this could // lead to a hang. // To avoid this, we first force ourselves to a background thread using Task.Run. // This ensures that the Task created by ConnectAsync will run on the default // TaskScheduler (i.e., on a threadpool thread) which was the intent all along. await Task.Run(() => pipeStream.ConnectAsync(timeoutMs, cancellationToken)).ConfigureAwait(false); } catch (Exception e) when(e is IOException || e is TimeoutException) { // Note: IOException can also indicate timeout. From docs: // TimeoutException: Could not connect to the server within the // specified timeout period. // IOException: The server is connected to another client and the // time-out period has expired. Log($"Connecting to server timed out after {timeoutMs} ms"); return(null); } Log("Named pipe '{0}' connected", pipeName); cancellationToken.ThrowIfCancellationRequested(); // Verify that we own the pipe. if (!NamedPipeUtil.CheckPipeConnectionOwnership(pipeStream)) { Log("Owner of named pipe is incorrect"); return(null); } return(pipeStream); } catch (Exception e) when(!(e is TaskCanceledException || e is OperationCanceledException)) { LogException(e, "Exception while connecting to process"); return(null); } }
/// <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> /// 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 Handshake handshake = GetHandshake(); gotValidConnection = NamedPipeUtil.ValidateHandshake(handshake, _pipeServer, ClientConnectTimeout); if (!gotValidConnection) { return; } 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 async Task <NodeEngineShutdownReason> RunShutdownCheckAsync(Handshake handshake, CancellationToken cancellationToken = default) { string pipeName = NamedPipeUtil.GetPipeNameOrPath("MSBuild" + Process.GetCurrentProcess().Id);
/// <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 = NamedPipeUtil.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.Id, 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 int msbuildProcessId = LaunchNode(msbuildLocation, commandLineArgs); _processesToIgnore.Add(GetProcessesToIgnoreKey(hostHandshake, msbuildProcessId)); // 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 = NamedPipeUtil.TryConnectToProcess(msbuildProcessId, TimeoutForNewNodeCreation, hostHandshake); if (nodeStream != null) { // Connection successful, use this node. CommunicationsUtilities.Trace("Successfully connected to created node {0} which is PID {1}", nodeId, msbuildProcessId); return(new NodeContext(nodeId, msbuildProcessId, nodeStream, factory, terminateNode)); } } // We were unable to launch a node. CommunicationsUtilities.Trace("FAILED TO CONNECT TO A CHILD NODE"); return(null); }
/// <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 = 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 CommunicationsUtilities.Trace("Writing handshake to pipe {0}", pipeName); nodeStream.WriteLongForHandshake(hostHandshake); CommunicationsUtilities.Trace("Reading handshake from pipe {0}", pipeName); #if NETCOREAPP2_1 || MONO 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) 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 if (nodeStream != null) { nodeStream.Dispose(); } } return(null); }