/// <summary> /// Gets the translator for this configuration. /// </summary> private INodePacketTranslator GetConfigurationTranslator(TranslationDirection direction) { string cacheFile = GetCacheFile(); try { if (direction == TranslationDirection.WriteToStream) { Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); return(NodePacketTranslator.GetWriteTranslator(File.Create(cacheFile))); } else { // Not using sharedReadBuffer because this is not a memory stream and so the buffer won't be used anyway. return(NodePacketTranslator.GetReadTranslator(File.OpenRead(cacheFile), null)); } } catch (Exception e) { if (e is DirectoryNotFoundException || e is UnauthorizedAccessException) { ErrorUtilities.ThrowInvalidOperation("CacheFileInaccessible", cacheFile, e); } // UNREACHABLE throw; } }
/// <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); 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 } }
/// <summary> /// Determine if the event is serializable. If we are running with multiple nodes we need to make sure the logging events are serializable. If not /// we need to log a warning. /// </summary> internal bool IsEventSerializable(BuildEventArgs e) { #if FEATURE_BINARY_SERIALIZATION if (!e.GetType().GetTypeInfo().IsSerializable) #else if (!NodePacketTranslator.IsSerializable(e)) #endif { _taskLoggingContext.LogWarning(null, new BuildEventFileInfo(string.Empty), "ExpectedEventToBeSerializable", e.GetType().Name); return(false); } return(true); }
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)) { INodePacketTranslator readTranslator = NodePacketTranslator.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); }
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]); int packetLength = BitConverter.ToInt32(headerByte, 1); try { _packetFactory.DeserializeAndRoutePacket(0, packetType, NodePacketTranslator.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(); INodePacketTranslator writeTranslator = NodePacketTranslator.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> /// 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; Queue <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 IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null); CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); 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"); localPipeServer.EndWaitForConnection(resultForConnection); // 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 = localPipeServer.ReadLongForHandshake(/* reject these leads */ new byte[] { 0x5F, 0x60 }, 0xFF /* this will disconnect the host; it expects leading 00 or F5 or 06 */); WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); string remoteUserName = localPipeServer.GetImpersonationUserName(); 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; } // 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; } } catch (IOException e) { // We will get here when: // 1. The host (OOP main node) connects to us, it immediately checks for user priviledges // 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"); localPipeServer.WriteLongForHandshake(GetClientHandshake()); ChangeLinkStatus(LinkStatus.Active); // 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]; IAsyncResult result = localPipeServer.BeginRead(headerByte, 0, headerByte.Length, null, null); 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[] { result.AsyncWaitHandle, localPacketAvailable, localTerminatePacketPump }; int waitId = WaitHandle.WaitAny(handles); switch (waitId) { case 0: { int bytesRead = 0; try { bytesRead = localPipeServer.EndRead(result); } 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]); int packetLength = BitConverter.ToInt32(headerByte, 1); try { _packetFactory.DeserializeAndRoutePacket(0, packetType, NodePacketTranslator.GetReadTranslator(localPipeServer, _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; } result = localPipeServer.BeginRead(headerByte, 0, headerByte.Length, null, null); } break; case 1: case 2: try { int packetCount = localPacketQueue.Count; // Write out all the queued packets. while (packetCount > 0) { INodePacket packet; lock (_packetQueue) { packet = localPacketQueue.Dequeue(); } MemoryStream packetStream = new MemoryStream(); INodePacketTranslator writeTranslator = NodePacketTranslator.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); localPipeServer.Write(packetStream.GetBuffer(), 0, (int)packetStream.Length); packetCount--; } } 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); 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> /// Method called when the body of a packet has been read. /// </summary> private void BodyReadComplete(IAsyncResult result) { NodePacketType packetType = (NodePacketType)_headerByte[0]; var state = (Tuple <byte[], int>)result.AsyncState; byte[] packetData = state.Item1; int packetLength = state.Item2; int bytesRead; try { try { bytesRead = _nodePipe.EndRead(result); } // Workaround for CLR stress bug; it sporadically calls us twice on the same async // result, and EndRead will throw on the second one. Pretend the second one never happened. catch (ArgumentException) { CommunicationsUtilities.Trace(_nodeId, "Hit CLR bug #825607: called back twice on same async result; ignoring"); return; } if (bytesRead != packetLength) { CommunicationsUtilities.Trace(_nodeId, "Bad packet read for packet {0} - Expected {1} bytes, got {2}", packetType, packetLength, bytesRead); _packetFactory.RoutePacket(_nodeId, new NodeShutdown(NodeShutdownReason.ConnectionFailed)); Close(); return; } } catch (IOException e) { CommunicationsUtilities.Trace(_nodeId, "EXCEPTION in BodyReadComplete (Reading): {0}", e); _packetFactory.RoutePacket(_nodeId, new NodeShutdown(NodeShutdownReason.ConnectionFailed)); Close(); return; } // Read and route the packet. 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)) { INodePacketTranslator readTranslator = NodePacketTranslator.GetReadTranslator(packetStream, _sharedReadBuffer); _packetFactory.DeserializeAndRoutePacket(_nodeId, packetType, readTranslator); } } catch (IOException e) { CommunicationsUtilities.Trace(_nodeId, "EXCEPTION in BodyReadComplete (Routing): {0}", e); _packetFactory.RoutePacket(_nodeId, new NodeShutdown(NodeShutdownReason.ConnectionFailed)); Close(); return; } if (packetType != NodePacketType.NodeShutdown) { // Read the next packet. BeginAsyncPacketRead(); } else { Close(); } }