/// <summary> /// This function attempts to find out a node number for which /// the event named Node_x_ProviderMutex doesn't exist. The existance /// of the event indicates that some other node provider is using the node. /// </summary> private void ReserveNextAvailableNodeNumber(int currentNodeNumber) { while (nodeReserveHandle == null) { bool createdNew; nodeReserveHandle = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeReserveEventName(currentNodeNumber), out createdNew); if (!createdNew) { nodeReserveHandle.Close(); nodeReserveHandle = null; currentNodeNumber++; } else { nodeNumber = currentNodeNumber; // Create the shared memory resources if (!CreateSharedMemoryBuffers()) { nodeReserveHandle.Close(); nodeReserveHandle = null; currentNodeNumber++; } } } }
/// <summary> /// This method causes the reader and writer threads to start and create the shared memory structures /// </summary> void StartCommunicationThreads() { // The writer thread should be created before the // reader thread because some LocalCallDescriptors // assume the shared memory for the writer thread // has already been created. The method will both // instantiate the shared memory for the writer // thread and also start the writer thread itself. // We will verifyThrow in the method if the // sharedMemory was not created correctly. engineCallback.StartWriterThread(nodeNumber); // Create the shared memory buffer this.sharedMemory = new SharedMemory ( // Generate the name for the shared memory region LocalNodeProviderGlobalNames.NodeInputMemoryName(nodeNumber), SharedMemoryType.ReadOnly, // Reuse an existing shared memory region as it should have already // been created by the parent node side true ); ErrorUtilities.VerifyThrow(this.sharedMemory.IsUsable, "Failed to create shared memory for local node input."); // Start the thread that will be processing the calls from the parent engine ThreadStart threadState = new ThreadStart(this.SharedMemoryReaderThread); readerThread = new Thread(threadState); readerThread.Name = "MSBuild Child<-Parent Reader"; readerThread.Start(); }
/// <summary> /// This method creates the shared memory buffers for communicating with the node /// </summary> /// <returns>Was the shared memory created and is useable</returns> internal bool CreateSharedMemoryBuffers() { this.sharedMemoryToNode = new SharedMemory ( LocalNodeProviderGlobalNames.NodeInputMemoryName(this.nodeNumber), SharedMemoryType.WriteOnly, false ); if (!this.sharedMemoryToNode.IsUsable) { return(false); } this.sharedMemoryFromNode = new SharedMemory ( LocalNodeProviderGlobalNames.NodeOutputMemoryName(this.nodeNumber), SharedMemoryType.ReadOnly, false ); if (!this.sharedMemoryFromNode.IsUsable) { return(false); } return(true); }
/// <summary> /// Create global events necessary for handshaking with the parent /// </summary> /// <param name="nodeNumber"></param> /// <returns>True if events created successfully and false otherwise</returns> private static bool CreateGlobalEvents(int nodeNumber) { bool createdNew; if (NativeMethods.IsUserAdministrator()) { EventWaitHandleSecurity mSec = new EventWaitHandleSecurity(); // Add a rule that grants the access only to admins and systems mSec.SetSecurityDescriptorSddlForm(NativeMethods.ADMINONLYSDDL); // Create an initiation event to allow the parent side to prove to the child that we have the same level of privilege as it does. // this is done by having the parent set this event which means it needs to have administrative permissions to do so. globalInitiateActivationEvent = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeInitiateActivationEventName(nodeNumber), out createdNew, mSec); } else { // Create an initiation event to allow the parent side to prove to the child that we have the same level of privilege as it does. // this is done by having the parent set this event which means it has atleast the same permissions as the child process globalInitiateActivationEvent = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeInitiateActivationEventName(nodeNumber), out createdNew); } // This process must be the creator of the event to prevent squating by a lower privilaged attacker if (!createdNew) { return(false); } // Informs the parent process that the child process has been created. globalNodeActive = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeActiveEventName(nodeNumber)); globalNodeActive.Set(); // Indicate to the parent process, this node is currently is ready to start to recieve requests globalNodeInUse = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeInUseEventName(nodeNumber)); // Used by the parent process to inform the child process to shutdown due to the child process // not recieving the initialization command. globalNodeErrorShutdown = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeErrorShutdownEventName(nodeNumber)); // Inform the parent process the node has started its communication threads. globalNodeActivate = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeActivedEventName(nodeNumber)); return(true); }
/// <summary> /// This function attempts to find out if there is currently a node running /// for a given index. The node is running if the global mutex with a /// "Node_" + nodeId + "_ActiveReady" as a name was created /// </summary> private static bool checkIfNodeActive(int nodeNumber) { bool nodeIsActive = false; EventWaitHandle nodeActiveHandle = null; try { nodeActiveHandle = EventWaitHandle.OpenExisting(LocalNodeProviderGlobalNames.NodeActiveEventName(nodeNumber)); nodeIsActive = true; } catch (WaitHandleCannotBeOpenedException) { // Assume that the node is not running } finally { nodeActiveHandle?.Close(); } return(nodeIsActive); }
internal void StartWriterThread(int nodeNumber) { this.writerThreadHasExited = false; this.sharedMemory = new SharedMemory ( LocalNodeProviderGlobalNames.NodeOutputMemoryName(nodeNumber), SharedMemoryType.WriteOnly, true ); ErrorUtilities.VerifyThrow(this.sharedMemory.IsUsable, "Failed to create shared memory for engine callback."); // Start the thread that will be processing the calls to the parent engine ThreadStart threadState = new ThreadStart(this.SharedMemoryWriterThread); writerThread = new Thread(threadState); writerThread.Name = "MSBuild Child->Parent Writer"; writerThread.Start(); }
/// <summary> /// This function starts local node when process is launched and shuts it down on time out /// Called by msbuild.exe. /// </summary> public static void StartLocalNodeServer(int nodeNumber) { // Create global events necessary for handshaking with the parent if (!CreateGlobalEvents(nodeNumber)) { return; } LocalNode localNode = new LocalNode(nodeNumber); WaitHandle[] waitHandles = new WaitHandle[4]; waitHandles[0] = shutdownEvent; waitHandles[1] = globalNodeErrorShutdown; waitHandles[2] = inUseEvent; waitHandles[3] = globalInitiateActivationEvent; // This is necessary to make build.exe finish promptly. Dont remove. if (!Engine.debugMode) { // Create null streams for the current input/output/error streams Console.SetOut(new StreamWriter(Stream.Null)); Console.SetError(new StreamWriter(Stream.Null)); Console.SetIn(new StreamReader(Stream.Null)); } bool continueRunning = true; while (continueRunning) { int eventType = WaitHandle.WaitAny(waitHandles, inactivityTimeout, false); if (eventType == 0 || eventType == 1 || eventType == WaitHandle.WaitTimeout) { continueRunning = false; localNode.ShutdownNode(eventType != 1 ? Node.NodeShutdownLevel.PoliteShutdown : Node.NodeShutdownLevel.ErrorShutdown, true, true); } else if (eventType == 2) { // reset the event as we do not want it to go into this state again when we are done with this if statement. inUseEvent.Reset(); // The parent knows at this point the child process has been launched globalNodeActivate.Reset(); // Set the global inuse event so other parent processes know this node is now initialized globalNodeInUse.Set(); // Make a copy of the parents handle to protect ourselves in case the parent dies, // this is to prevent a parent from reserving a node another parent is trying to use. globalNodeReserveHandle = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeReserveEventName(nodeNumber)); WaitHandle[] waitHandlesActive = new WaitHandle[3]; waitHandlesActive[0] = shutdownEvent; waitHandlesActive[1] = globalNodeErrorShutdown; waitHandlesActive[2] = notInUseEvent; eventType = WaitHandle.WaitTimeout; while (eventType == WaitHandle.WaitTimeout && continueRunning) { eventType = WaitHandle.WaitAny(waitHandlesActive, parentCheckInterval, false); if (eventType == 0 || /* nice shutdown due to shutdownEvent */ eventType == 1 || /* error shutdown due to globalNodeErrorShutdown */ (eventType == WaitHandle.WaitTimeout && !localNode.IsParentProcessAlive())) { continueRunning = false; // If the exit is not triggered by running of shutdown method if (eventType != 0) { localNode.ShutdownNode(Node.NodeShutdownLevel.ErrorShutdown, true, true); } } else if (eventType == 2) { // Trigger a collection before the node goes idle to insure that // the memory is released to the system as soon as possible GC.Collect(); // Change the current directory to a safe one so that the directory // last used by the build can be safely deleted. We must have read // access to the safe directory so use SystemDirectory for this purpose. Directory.SetCurrentDirectory(Environment.SystemDirectory); notInUseEvent.Reset(); globalNodeInUse.Reset(); } } ErrorUtilities.VerifyThrow(localNode.node == null, "Expected either node to be null or continueRunning to be false."); // Stop the communication threads and release the shared memory object so that the next parent can create it localNode.StopCommunicationThreads(); // Close the local copy of the reservation handle (this allows another parent to reserve // the node) globalNodeReserveHandle.Close(); globalNodeReserveHandle = null; } else if (eventType == 3) { globalInitiateActivationEvent.Reset(); localNode.StartCommunicationThreads(); globalNodeActivate.Set(); } } // Stop the communication threads and release the shared memory object so that the next parent can create it localNode.StopCommunicationThreads(); globalNodeActive.Close(); globalNodeInUse.Close(); }
/// <summary> /// This function launches a new node given a node index /// </summary> private void LaunchNode(int nodeIndex) { EventWaitHandle nodeReadyEvent = null; string msbuildLocation = Path.Combine(locationOfMSBuildExe, "MSBuild.exe"); ErrorUtilities.VerifyThrow(File.Exists(msbuildLocation), "Msbuild.exe cannot be found at: " + msbuildLocation); bool exitedDueToError = true; try { NativeMethods.STARTUPINFO startInfo = new NativeMethods.STARTUPINFO(); startInfo.cb = Marshal.SizeOf(startInfo); uint dwCreationFlags = NativeMethods.NORMAL_PRIORITY_CLASS; if (!Engine.debugMode) { startInfo.hStdError = NativeMethods.InvalidHandle; startInfo.hStdInput = NativeMethods.InvalidHandle; startInfo.hStdOutput = NativeMethods.InvalidHandle; startInfo.dwFlags = NativeMethods.STARTF_USESTDHANDLES; dwCreationFlags = dwCreationFlags | NativeMethods.CREATE_NO_WINDOW; } NativeMethods.SECURITY_ATTRIBUTES pSec = new NativeMethods.SECURITY_ATTRIBUTES(); NativeMethods.SECURITY_ATTRIBUTES tSec = new NativeMethods.SECURITY_ATTRIBUTES(); pSec.nLength = Marshal.SizeOf(pSec); tSec.nLength = Marshal.SizeOf(tSec); NativeMethods.PROCESS_INFORMATION pInfo = new NativeMethods.PROCESS_INFORMATION(); string appName = 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 string cmdLine = msbuildLocation + " /nologo /oldom /nodemode:" + nodeData[nodeIndex].NodeNumber; NativeMethods.CreateProcess(appName, cmdLine, ref pSec, ref tSec, false, dwCreationFlags, NativeMethods.NullPtr, null, ref startInfo, out pInfo); nodeReadyEvent = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeActiveEventName(nodeData[nodeIndex].NodeNumber)); // Wait until the node is ready to process the requests if (nodeReadyEvent.WaitOne(launchTimeout, false)) { exitedDueToError = false; } } finally { // Dispose before losing scope if (nodeReadyEvent != null) { nodeReadyEvent.Close(); } if (exitedDueToError) { nodeData[nodeIndex].CommunicationFailed = true; } } }
/// <summary> /// This function establishes communication with a node given an index. If a node /// is not running it is launched. /// </summary> private void InitializeNode(int nodeIndex) { bool nodeConnected = false; int restartCount = 0; try { IncreaseActiveNodeCount(); while (!nodeConnected && restartCount < maximumNodeRestartCount) { if (!checkIfNodeActive(nodeData[nodeIndex].NodeNumber)) { // Attempt to launch a new node process LaunchNode(nodeIndex); // If we could not launch the node there is no reason to continue if (nodeData[nodeIndex].CommunicationFailed) { break; } } if (checkIfNodeActive(nodeData[nodeIndex].NodeNumber)) { nodeData[nodeIndex].SharedMemoryToNode.Reset(); nodeData[nodeIndex].SharedMemoryFromNode.Reset(); // Activate the initiation event to prove to the child that we have the same level of privilege as it does. This operation will not fail because each privilege level creates // events in different namespaces EventWaitHandle nodeInitiateActivationEvent = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeInitiateActivationEventName(nodeData[nodeIndex].NodeNumber)); nodeInitiateActivationEvent.Set(); nodeInitiateActivationEvent.Close(); // Wait for node to indicate that it is activated EventWaitHandle nodeActivatedEvent = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeActivedEventName(nodeData[nodeIndex].NodeNumber)); nodeActivatedEvent.WaitOne(initializationTimeout, false); nodeActivatedEvent.Close(); // Looked in Environment.cs the IDictionary is a HashTable IDictionary variableDictionary = Environment.GetEnvironmentVariables(); Hashtable environmentVariablesTable = new Hashtable(variableDictionary); LocalCallDescriptorForInitializeNode callDescriptorInit = new LocalCallDescriptorForInitializeNode(environmentVariablesTable, nodeLoggers.ToArray(), nodeData[nodeIndex].NodeId, parentGlobalProperties, toolsetSearchLocations, Process.GetCurrentProcess().Id, startupDirectory); nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptorInit); EventWaitHandle nodeInUseEvent = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeInUseEventName(nodeData[nodeIndex].NodeNumber)); // Wait for node to indicate that it is ready. The node may time out and exit, between // when we check that it is active and before the initialization messages reaches it. // In that rare case we have to restart the node. if (nodeInUseEvent.WaitOne(initializationTimeout, false)) { UpdateSettings(nodeIndex); nodeConnected = true; } nodeInUseEvent.Close(); // If the node is still active and has not replied to the initialization message it must // be in bad state - try to get that node to exit if (!nodeConnected && checkIfNodeActive(nodeData[nodeIndex].NodeNumber)) { EventWaitHandle nodeShutdownEvent = new EventWaitHandle(false, EventResetMode.ManualReset, LocalNodeProviderGlobalNames.NodeErrorShutdownEventName(nodeData[nodeIndex].NodeNumber)); nodeShutdownEvent.Set(); nodeShutdownEvent.Close(); restartCount = maximumNodeRestartCount; } restartCount++; } } } finally { // Make sure to decrement the active node count if the communication has failed if (nodeConnected != true) { DecreaseActiveNodeCount(nodeData[nodeIndex].NodeId); nodeData[nodeIndex].CommunicationFailed = true; } } }