From 8a74a3e9403a0b4ef5db1289bda06f2d7904255e Mon Sep 17 00:00:00 2001 From: Eric Arndt Date: Thu, 9 Jan 2025 14:29:45 -0800 Subject: [PATCH 1/3] Cache process info where possible and dispose Process objects. --- .../BackEnd/BuildManager/BuildManager.cs | 4 +- src/Build/BackEnd/Client/MSBuildClient.cs | 2 +- .../BuildRequestEngine/BuildRequestEngine.cs | 2 +- .../Components/Communications/CurrentHost.cs | 5 +- .../NodeProviderOutOfProcBase.cs | 12 +- .../BackEnd/Components/Scheduler/Scheduler.cs | 8 +- src/Build/BackEnd/Node/OutOfProcNode.cs | 5 +- .../Evaluation/ProjectRootElementCache.cs | 2 +- .../Logging/BinaryLogger/BinaryLogger.cs | 9 +- src/Build/Microsoft.Build.csproj | 3 - .../Engine/LocalProvider/LocalNode.cs | 684 +++++++++++ .../Engine/LocalProvider/LocalNodeProvider.cs | 1056 +++++++++++++++++ .../Engine/Microsoft.Build.Engine.csproj | 224 ++++ src/Framework/FileClassifier.cs | 6 +- .../Microsoft.Build.Framework.csproj | 3 +- .../Profiler/EvaluationIdProvider.cs | 3 +- src/MSBuild/MSBuildClientApp.cs | 5 +- src/MSBuild/OutOfProcTaskHostNode.cs | 13 +- src/MSBuild/PerformanceLogEventListener.cs | 2 +- src/MSBuild/XMake.cs | 21 +- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 3 +- src/MSBuildTaskHost/OutOfProcTaskHost.cs | 4 +- src/Shared/BuildEnvironmentHelper.cs | 5 +- src/Shared/CommunicationsUtilities.cs | 5 +- src/Shared/Debugging/DebugUtils.cs | 4 +- src/Shared/EnvironmentUtilities.cs | 83 ++ src/Shared/ExceptionHandling.cs | 2 +- src/Shared/FileUtilities.cs | 4 +- src/Shared/NamedPipeUtil.cs | 2 +- src/Tasks/Microsoft.Build.Tasks.csproj | 3 - .../RoslynCodeTaskFactoryCompilers.cs | 3 +- .../Microsoft.Build.Utilities.csproj | 3 - 32 files changed, 2119 insertions(+), 71 deletions(-) create mode 100644 src/Deprecated/Engine/LocalProvider/LocalNode.cs create mode 100644 src/Deprecated/Engine/LocalProvider/LocalNodeProvider.cs create mode 100644 src/Deprecated/Engine/Microsoft.Build.Engine.csproj diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 10cf3577bf1..7e72e4ebea8 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -759,8 +759,8 @@ private static void AttachDebugger() #endif case "2": // Sometimes easier to attach rather than deal with JIT prompt - Process currentProcess = Process.GetCurrentProcess(); - Console.WriteLine($"Waiting for debugger to attach ({currentProcess.MainModule!.FileName} PID {currentProcess.Id}). Press enter to continue..."); + Console.WriteLine($"Waiting for debugger to attach ({EnvironmentUtilities.ProcessPath} PID {EnvironmentUtilities.CurrentProcessId}). Press enter to continue..."); + Console.ReadLine(); break; } diff --git a/src/Build/BackEnd/Client/MSBuildClient.cs b/src/Build/BackEnd/Client/MSBuildClient.cs index c23b5134407..4a496ed7f8d 100644 --- a/src/Build/BackEnd/Client/MSBuildClient.cs +++ b/src/Build/BackEnd/Client/MSBuildClient.cs @@ -474,7 +474,7 @@ private bool TryLaunchServer() ]; NodeLauncher nodeLauncher = new NodeLauncher(); CommunicationsUtilities.Trace("Starting Server..."); - Process msbuildProcess = nodeLauncher.Start(_msbuildLocation, string.Join(" ", msBuildServerOptions), nodeId: 0); + using Process msbuildProcess = nodeLauncher.Start(_msbuildLocation, string.Join(" ", msBuildServerOptions), nodeId: 0); CommunicationsUtilities.Trace("Server started with PID: {0}", msbuildProcess?.Id); } catch (Exception ex) diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 9c633d14b8c..aa90f8d1277 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -1426,7 +1426,7 @@ private void TraceEngine(string format, params object[] stuff) { FileUtilities.EnsureDirectoryExists(_debugDumpPath); - using (StreamWriter file = FileUtilities.OpenWrite(String.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, @"EngineTrace_{0}.txt"), Process.GetCurrentProcess().Id), append: true)) + using (StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, @"EngineTrace_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true)) { string message = String.Format(CultureInfo.CurrentCulture, format, stuff); file.WriteLine("{0}({1})-{2}: {3}", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId, DateTime.UtcNow.Ticks, message); diff --git a/src/Build/BackEnd/Components/Communications/CurrentHost.cs b/src/Build/BackEnd/Components/Communications/CurrentHost.cs index 37bc10fc28e..a9c8336b837 100644 --- a/src/Build/BackEnd/Components/Communications/CurrentHost.cs +++ b/src/Build/BackEnd/Components/Communications/CurrentHost.cs @@ -35,10 +35,7 @@ public static string GetCurrentHost() } else { - using (Process currentProcess = Process.GetCurrentProcess()) - { - s_currentHost = currentProcess.MainModule.FileName; - } + s_currentHost = EnvironmentUtilities.ProcessPath; } } diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs index 87602148672..ef30661bbb5 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs @@ -56,6 +56,9 @@ internal abstract class NodeProviderOutOfProcBase /// private const int TimeoutForWaitForExit = 30000; +#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY + private static readonly WindowsIdentity s_currentWindowsIdentity = WindowsIdentity.GetCurrent(); +#endif /// /// The build component host. /// @@ -237,11 +240,12 @@ protected IList GetNodes(string msbuildLocation, #endif ConcurrentQueue nodeContexts = new(); ConcurrentQueue exceptions = new(); + int currentProcessId = EnvironmentUtilities.CurrentProcessId; Parallel.For(nextNodeId, nextNodeId + numberOfNodesToCreate, (nodeId) => { try { - if (!TryReuseAnyFromPossibleRunningNodes(nodeId) && !StartNewNode(nodeId)) + if (!TryReuseAnyFromPossibleRunningNodes(currentProcessId, nodeId) && !StartNewNode(nodeId)) { // We were unable to reuse or launch a node. CommunicationsUtilities.Trace("FAILED TO CONNECT TO A CHILD NODE"); @@ -260,12 +264,12 @@ protected IList GetNodes(string msbuildLocation, return nodeContexts.ToList(); - bool TryReuseAnyFromPossibleRunningNodes(int nodeId) + bool TryReuseAnyFromPossibleRunningNodes(int currentProcessId, int nodeId) { while (possibleRunningNodes != null && possibleRunningNodes.TryDequeue(out var nodeToReuse)) { CommunicationsUtilities.Trace("Trying to connect to existing process {2} with id {1} to establish node {0}...", nodeId, nodeToReuse.Id, nodeToReuse.ProcessName); - if (nodeToReuse.Id == Process.GetCurrentProcess().Id) + if (nodeToReuse.Id == currentProcessId) { continue; } @@ -421,7 +425,7 @@ private string GetProcessesToIgnoreKey(Handshake hostHandshake, int nodeProcessI // on non-Windows operating systems private static void ValidateRemotePipeSecurityOnWindows(NamedPipeClientStream nodeStream) { - SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; + SecurityIdentifier identifier = s_currentWindowsIdentity.Owner; #if FEATURE_PIPE_SECURITY PipeSecurity remoteSecurity = nodeStream.GetAccessControl(); #else diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 74adf85158f..e6c7331f93d 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -2569,7 +2569,7 @@ private void TraceScheduler(string format, params object[] stuff) { FileUtilities.EnsureDirectoryExists(_debugDumpPath); - using StreamWriter file = FileUtilities.OpenWrite(String.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerTrace_{0}.txt"), Process.GetCurrentProcess().Id), append: true); + using StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerTrace_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true); file.Write("{0}({1})-{2}: ", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId, _schedulingData.EventTime.Ticks); file.WriteLine(format, stuff); file.Flush(); @@ -2593,7 +2593,7 @@ private void DumpSchedulerState() try { FileUtilities.EnsureDirectoryExists(_debugDumpPath); - using StreamWriter file = FileUtilities.OpenWrite(String.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerState_{0}.txt"), Process.GetCurrentProcess().Id), append: true); + using StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerState_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true); file.WriteLine("Scheduler state at timestamp {0}:", _schedulingData.EventTime.Ticks); file.WriteLine("------------------------------------------------"); @@ -2707,7 +2707,7 @@ private void DumpConfigurations() { try { - using StreamWriter file = FileUtilities.OpenWrite(String.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerState_{0}.txt"), Process.GetCurrentProcess().Id), append: true); + using StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerState_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true); file.WriteLine("Configurations used during this build"); file.WriteLine("-------------------------------------"); @@ -2747,7 +2747,7 @@ private void DumpRequests() { try { - using StreamWriter file = FileUtilities.OpenWrite(String.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerState_{0}.txt"), Process.GetCurrentProcess().Id), append: true); + using StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerState_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true); file.WriteLine("Requests used during the build:"); file.WriteLine("-------------------------------"); diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs index 5148f89be9f..30e0f29b969 100644 --- a/src/Build/BackEnd/Node/OutOfProcNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcNode.cs @@ -847,7 +847,8 @@ private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) _shutdownReason = buildComplete.PrepareForReuse ? NodeEngineShutdownReason.BuildCompleteReuse : NodeEngineShutdownReason.BuildComplete; if (_shutdownReason == NodeEngineShutdownReason.BuildCompleteReuse) { - ProcessPriorityClass priorityClass = Process.GetCurrentProcess().PriorityClass; + using Process currentProcess = Process.GetCurrentProcess(); + ProcessPriorityClass priorityClass = currentProcess.PriorityClass; if (priorityClass != ProcessPriorityClass.Normal && priorityClass != ProcessPriorityClass.BelowNormal) { // This isn't a priority class known by MSBuild. We should avoid connecting to this node. @@ -860,7 +861,7 @@ private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) { if (!lowPriority || NativeMethodsShared.IsWindows) { - Process.GetCurrentProcess().PriorityClass = lowPriority ? ProcessPriorityClass.Normal : ProcessPriorityClass.BelowNormal; + currentProcess.PriorityClass = lowPriority ? ProcessPriorityClass.Normal : ProcessPriorityClass.BelowNormal; } else { diff --git a/src/Build/Evaluation/ProjectRootElementCache.cs b/src/Build/Evaluation/ProjectRootElementCache.cs index e086298a380..d70e5648f3e 100644 --- a/src/Build/Evaluation/ProjectRootElementCache.cs +++ b/src/Build/Evaluation/ProjectRootElementCache.cs @@ -674,7 +674,7 @@ private void DebugTraceCache(string message, string param1) if (s_debugLogCacheActivity) { string prefix = OutOfProcNode.IsOutOfProcNode ? "C" : "P"; - Trace.WriteLine(prefix + " " + Process.GetCurrentProcess().Id + " | " + message + param1); + Trace.WriteLine(prefix + " " + EnvironmentUtilities.CurrentProcessId + " | " + message + param1); } } } diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index be4eaa2288d..278d0d77573 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -502,13 +502,6 @@ private string GetUniqueStamp() => (PathParameterExpander ?? ExpandPathParameter)(string.Empty); private static string ExpandPathParameter(string parameters) - => $"{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}--{ProcessId}--{StringUtils.GenerateRandomString(6)}"; - - private static int ProcessId -#if NET - => Environment.ProcessId; -#else - => System.Diagnostics.Process.GetCurrentProcess().Id; -#endif + => $"{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}--{EnvironmentUtilities.CurrentProcessId}--{StringUtils.GenerateRandomString(6)}"; } } diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 66d6c38d007..9d83e1304f8 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -59,9 +59,6 @@ - - SharedUtilities\EnvironmentUtilities.cs - SharedUtilities\BuildEnvironmentHelper.cs diff --git a/src/Deprecated/Engine/LocalProvider/LocalNode.cs b/src/Deprecated/Engine/LocalProvider/LocalNode.cs new file mode 100644 index 00000000000..d957cf840bb --- /dev/null +++ b/src/Deprecated/Engine/LocalProvider/LocalNode.cs @@ -0,0 +1,684 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE +// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL +// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED. + +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Security.AccessControl; +using System.Threading; +using Microsoft.Build.BuildEngine.Shared; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.BuildEngine +{ + /// + /// This class (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: + /// Microsoft.Build.Construction + /// Microsoft.Build.Evaluation + /// Microsoft.Build.Execution + /// + /// This class hosts a node class in the child process. It uses shared memory to communicate + /// with the local node provider. + /// Wraps a Node. + /// + /// + /// [!WARNING] + /// > This class (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: + /// > + /// > + /// > + /// ]]> + /// + public class LocalNode + { + #region Static Constructors + /// + /// This method (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: + /// Microsoft.Build.Construction + /// Microsoft.Build.Evaluation + /// Microsoft.Build.Execution + /// + /// Hook up an unhandled exception handler, in case our error handling paths are leaky + /// + /// + /// [!WARNING] + /// > This method (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: + /// > + /// > + /// > + /// ]]> + /// + static LocalNode() + { + AppDomain currentDomain = AppDomain.CurrentDomain; + currentDomain.UnhandledException += UnhandledExceptionHandler; + } + #endregion + + #region Static Methods + + /// + /// Dump any unhandled exceptions to a file so they can be diagnosed + /// + private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) + { + Exception ex = (Exception)e.ExceptionObject; + DumpExceptionToFile(ex); + } + + /// + /// Dump the exception information to a file + /// + internal static void DumpExceptionToFile(Exception ex) + { + // Lock as multiple threads may throw simultaneously + lock (dumpFileLocker) + { + if (dumpFileName == null) + { + Guid guid = Guid.NewGuid(); + string tempPath = Path.GetTempPath(); + + // For some reason we get Watson buckets because GetTempPath gives us a folder here that doesn't exist. + // Either because %TMP% is misdefined, or because they deleted the temp folder during the build. + if (!Directory.Exists(tempPath)) + { + // If this throws, no sense catching it, we can't log it now, and we're here + // because we're a child node with no console to log to, so die + Directory.CreateDirectory(tempPath); + } + + dumpFileName = Path.Combine(tempPath, "MSBuild_" + guid.ToString()); + + using (StreamWriter writer = new StreamWriter(dumpFileName, true /*append*/)) + { + writer.WriteLine("UNHANDLED EXCEPTIONS FROM CHILD NODE:"); + writer.WriteLine("==================="); + } + } + + using (StreamWriter writer = new StreamWriter(dumpFileName, true /*append*/)) + { + writer.WriteLine(DateTime.Now.ToLongTimeString()); + writer.WriteLine(ex.ToString()); + writer.WriteLine("==================="); + } + } + } + + #endregion + + #region Constructors + + /// + /// Creates an instance of this class. + /// + internal LocalNode(int nodeNumberIn) + { + this.nodeNumber = nodeNumberIn; + + engineCallback = new LocalNodeCallback(communicationThreadExitEvent, this); + } + + #endregion + + #region Communication Methods + + /// + /// This method causes the reader and writer threads to start and create the shared memory structures + /// + private 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(); + } + + /// + /// This method causes the reader and writer threads to exit and dispose of the shared memory structures + /// + private void StopCommunicationThreads() + { + communicationThreadExitEvent.Set(); + + // Wait for communication threads to exit + Thread writerThread = engineCallback.GetWriterThread(); + // The threads may not exist if the child has timed out before the parent has told the node + // to start up its communication threads. This can happen if the node is started with /nodemode:x + // and no parent is running, or if the parent node has spawned a new process and then crashed + // before establishing communication with the child node. + writerThread?.Join(); + + readerThread?.Join(); + + // Make sure the exit event is not set + communicationThreadExitEvent.Reset(); + } + + #endregion + + #region Startup Methods + + /// + /// Create global events necessary for handshaking with the parent + /// + /// + /// True if events created successfully and false otherwise + 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; + } + + /// + /// This method (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: + /// Microsoft.Build.Construction + /// Microsoft.Build.Evaluation + /// Microsoft.Build.Execution + /// + /// This function starts local node when process is launched and shuts it down on time out + /// Called by msbuild.exe. + /// + /// + /// [!WARNING] + /// > This method (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: + /// > + /// > + /// > + /// ]]> + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Agreed not to touch entries from Deprecated folder")] + 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(); + } + + #endregion + + #region Methods + + /// + /// This method is run in its own thread, it is responsible for reading messages sent from the parent process + /// through the shared memory region. + /// + private void SharedMemoryReaderThread() + { + // Create an array of event to the node thread responds + WaitHandle[] waitHandles = new WaitHandle[2]; + waitHandles[0] = communicationThreadExitEvent; + waitHandles[1] = sharedMemory.ReadFlag; + + bool continueExecution = true; + + try + { + while (continueExecution) + { + // Wait for the next work item or an exit command + int eventType = WaitHandle.WaitAny(waitHandles); + + if (eventType == 0) + { + // Exit node event + continueExecution = false; + } + else + { + // Read the list of LocalCallDescriptors from sharedMemory, + // this will be null if a large object is being read from shared + // memory and will continue to be null until the large object has + // been completly sent. + IList localCallDescriptorList = sharedMemory.Read(); + + if (localCallDescriptorList != null) + { + foreach (LocalCallDescriptor callDescriptor in localCallDescriptorList) + { + // Execute the command method which relates to running on a child node + callDescriptor.NodeAction(node, this); + + if ((callDescriptor.IsReply) && (callDescriptor is LocalReplyCallDescriptor)) + { + // Process the reply from the parent so it can be looked in a hashtable based + // on the call descriptor who requested the reply. + engineCallback.PostReplyFromParent((LocalReplyCallDescriptor)callDescriptor); + } + } + } + } + } + } + catch (Exception e) + { + // Will rethrow the exception if necessary + ReportFatalCommunicationError(e); + } + + // Dispose of the shared memory buffer + if (sharedMemory != null) + { + sharedMemory.Dispose(); + sharedMemory = null; + } + } + + /// + /// This method will shutdown the node being hosted by the child process and notify the parent process if requested, + /// + /// What kind of shutdown is causing the child node to shutdown + /// should the child process exit as part of the shutdown process + /// Indicates if the parent process should be notified the child node is being shutdown + internal void ShutdownNode(Node.NodeShutdownLevel shutdownLevel, bool exitProcess, bool noParentNotification) + { + if (node != null) + { + try + { + node.ShutdownNode(shutdownLevel); + + if (!noParentNotification) + { + // Write the last event out directly + LocalCallDescriptorForShutdownComplete callDescriptor = + + new LocalCallDescriptorForShutdownComplete(shutdownLevel, node.TotalTaskTime); + // Post the message indicating that the shutdown is complete + engineCallback.PostMessageToParent(callDescriptor, true); + } + } + catch (Exception e) + { + if (shutdownLevel != Node.NodeShutdownLevel.ErrorShutdown) + { + ReportNonFatalCommunicationError(e); + } + } + } + + // If the shutdownLevel is not a build complete message, then this means there was a politeshutdown or an error shutdown, null the node out + // as either it is no longer needed due to the node goign idle or there was a error and it is now in a bad state. + if (shutdownLevel != Node.NodeShutdownLevel.BuildCompleteSuccess && + shutdownLevel != Node.NodeShutdownLevel.BuildCompleteFailure) + { + node = null; + notInUseEvent.Set(); + } + + if (exitProcess) + { + // Even if we completed a build, if we are goign to exit the process we need to null out the node and set the notInUseEvent, this is + // accomplished by calling this method again with the ErrorShutdown handle + if (shutdownLevel == Node.NodeShutdownLevel.BuildCompleteSuccess || shutdownLevel == Node.NodeShutdownLevel.BuildCompleteFailure) + { + ShutdownNode(Node.NodeShutdownLevel.ErrorShutdown, false, true); + } + // Signal all the communication threads to exit + shutdownEvent.Set(); + } + } + + /// + /// This methods activates the local node + /// + internal void Activate + ( + Hashtable environmentVariables, + LoggerDescription[] nodeLoggers, + int nodeId, + BuildPropertyGroup parentGlobalProperties, + ToolsetDefinitionLocations toolsetSearchLocations, + int parentId, + string parentStartupDirectory + ) + { + ErrorUtilities.VerifyThrow(node == null, "Expected node to be null on activation."); + + this.parentProcessId = parentId; + + engineCallback.Reset(); + + inUseEvent.Set(); + + // Clear the environment so that we dont have extra variables laying around, this + // may be a performance hog but needs to be done + IDictionary variableDictionary = Environment.GetEnvironmentVariables(); + foreach (string variableName in variableDictionary.Keys) + { + Environment.SetEnvironmentVariable(variableName, null); + } + + foreach (string key in environmentVariables.Keys) + { + Environment.SetEnvironmentVariable(key, (string)environmentVariables[key]); + } + + // Host the msbuild engine and system + node = new Node(nodeId, nodeLoggers, engineCallback, parentGlobalProperties, toolsetSearchLocations, parentStartupDirectory); + + // Write the initialization complete event out directly + LocalCallDescriptorForInitializationComplete callDescriptor = + new LocalCallDescriptorForInitializationComplete(EnvironmentUtilities.CurrentProcessId); + + // Post the message indicating that the initialization is complete + engineCallback.PostMessageToParent(callDescriptor, true); + } + + /// + /// This method checks is the parent process has not exited + /// + /// True if the parent process is still alive + private bool IsParentProcessAlive() + { + bool isParentAlive = true; + try + { + // Check if the parent is still there + using Process parentProcess = Process.GetProcessById(parentProcessId); + if (parentProcess.HasExited) + { + isParentAlive = false; + } + } + catch (ArgumentException) + { + isParentAlive = false; + } + + if (!isParentAlive) + { + // No logging's going to reach the parent at this point: + // indicate on the console what's going on + string message = ResourceUtilities.FormatResourceString("ParentProcessUnexpectedlyDied", node.NodeId); + Console.WriteLine(message); + } + + return isParentAlive; + } + + /// + /// Any error occuring in the shared memory transport is considered to be fatal + /// + /// + /// Re-throws exception passed in + internal void ReportFatalCommunicationError(Exception originalException) + { + try + { + DumpExceptionToFile(originalException); + } + finally + { + node?.ReportFatalCommunicationError(originalException, null); + } + } + + /// + /// This function is used to report exceptions which don't indicate breakdown + /// of communication with the parent + /// + /// + internal void ReportNonFatalCommunicationError(Exception originalException) + { + if (node != null) + { + try + { + DumpExceptionToFile(originalException); + } + finally + { + node.ReportUnhandledError(originalException); + } + } + else + { + // Since there is no node object report rethrow the exception + ReportFatalCommunicationError(originalException); + } + } + + #endregion + #region Properties + internal static string DumpFileName + { + get + { + return dumpFileName; + } + } + #endregion + + #region Member data + + private Node node; + private SharedMemory sharedMemory; + private LocalNodeCallback engineCallback; + private int parentProcessId; + private int nodeNumber; + private Thread readerThread; + private static object dumpFileLocker = new Object(); + + // Public named events + // If this event is set the node host process is currently running + private static EventWaitHandle globalNodeActive; + // If this event is set the node is currently running a build + private static EventWaitHandle globalNodeInUse; + // If this event exists the node is reserved for use by a particular parent engine + // the node keeps a handle to this event during builds to prevent it from being used + // by another parent engine if the original dies + private static EventWaitHandle globalNodeReserveHandle; + // If this event is set the node will immediatelly exit. The event is used by the + // parent engine to cause the node to exit if communication is lost. + private static EventWaitHandle globalNodeErrorShutdown; + // This event is used to cause the child to create the shared memory structures to start communication + // with the parent + private static EventWaitHandle globalInitiateActivationEvent; + // This event is used to indicate to the parent that shared memory buffers have been created and are ready for + // use + private static EventWaitHandle globalNodeActivate; + // Private local events + private static ManualResetEvent communicationThreadExitEvent = new ManualResetEvent(false); + private static ManualResetEvent shutdownEvent = new ManualResetEvent(false); + private static ManualResetEvent notInUseEvent = new ManualResetEvent(false); + + /// + /// Indicates the node is now in use. This means the node has recieved an activate command with initialization + /// data from the parent procss + /// + private static ManualResetEvent inUseEvent = new ManualResetEvent(false); + + /// + /// Randomly generated file name for all exceptions thrown by this node that need to be dumped to a file. + /// (There may be more than one exception, if they occur on different threads.) + /// + private static string dumpFileName = null; + + // Timeouts && Constants + private const int inactivityTimeout = 60 * 1000; // 60 seconds of inactivity to exit + private const int parentCheckInterval = 5 * 1000; // Check if the parent process is there every 5 seconds + + #endregion + + } +} + diff --git a/src/Deprecated/Engine/LocalProvider/LocalNodeProvider.cs b/src/Deprecated/Engine/LocalProvider/LocalNodeProvider.cs new file mode 100644 index 00000000000..b97ae16dad6 --- /dev/null +++ b/src/Deprecated/Engine/LocalProvider/LocalNodeProvider.cs @@ -0,0 +1,1056 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE +// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL +// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Globalization; +using System.IO; +using Microsoft.Build.BuildEngine.Shared; +using System.Runtime.InteropServices; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.BuildEngine +{ + internal class LocalNodeProvider : INodeProvider + { + #region Methods implementing INodeProvider + public void Initialize + ( + string configuration, + IEngineCallback parentEngineCallback, + BuildPropertyGroup parentGlobalPropertyGroup, + ToolsetDefinitionLocations toolSetSearchLocations, + string startupDirectory + ) + { + // Get from the environment how long we should wait in seconds for shutdown to complete + string shutdownTimeoutFromEnvironment = Environment.GetEnvironmentVariable("MSBUILDNODESHUTDOWNTIMEOUT"); + int result; + if (int.TryParse(shutdownTimeoutFromEnvironment, out result) && result >= 0) + { + shutdownTimeout = result; + } + + this.cpuCount = 1; + + if (configuration != null) + { + // Split out the parameter sets based on ; + string[] parameters = configuration.Split(parameterDelimiters); + // Go through each of the parameter name value pairs and split them appart + for (int param = 0; param < parameters.Length; param++) + { + if (parameters[param].Length > 0) + { + string[] parameterComponents = parameters[param].Split(valueDelimiters); + // If there is a name and value associated with the parameter, apply the paramter to the provider + if (parameterComponents.Length == 2) + { + ApplyParameter(parameterComponents[0], parameterComponents[1]); + } + else // Only the parameter name is known, this could be for a boolean parameter + { + ApplyParameter(parameters[param], null); + } + } + } + } + + /* If we dont get a path passed in as a parameter, we can only assume that our path + is in the current appdomain basedirectory, this is the base directory + that the assembly resolver uses to probe for assemblies + */ + if (string.IsNullOrEmpty(this.locationOfMSBuildExe)) + { + this.locationOfMSBuildExe = AppDomain.CurrentDomain.BaseDirectory; + } + if ((cpuCount - 1) <= 0) + { + return; + } + + this.exitCommunicationThreads = new ManualResetEvent(false); + + this.activeNodeCount = 0; + this.responseCountChangeEvent = new ManualResetEvent(false); + + this.nodeStateLock = new object(); + this.nodesToLaunch = new Queue(); + this.nodeLoggers = new List(); + + nodeData = new LocalNodeInfo[cpuCount - 1]; + + // Initialize the internal state indicating that no nodes have been launched + int lastUsedNodeNumber = 0; + for (int i = 0; i < nodeData.Length; i++) + { + nodeData[i] = new LocalNodeInfo(lastUsedNodeNumber); + lastUsedNodeNumber = nodeData[i].NodeNumber + 1; + } + + // Set up the callback + this.engineCallback = parentEngineCallback; + this.parentGlobalProperties = parentGlobalPropertyGroup; + this.toolsetSearchLocations = toolSetSearchLocations; + this.startupDirectory = startupDirectory; + + // Default node settings + centralizedLogging = false; + onlyLogCriticalEvents = false; + useBreadthFirstTraversal = true; + shuttingDown = false; + + // Start the thread that will be processing the calls from the parent engine + ThreadStart threadState = new ThreadStart(this.SharedMemoryWriterThread); + Thread taskThread = new Thread(threadState); + taskThread.Name = "MSBuild Parent->Child Writer"; + taskThread.Start(); + threadState = new ThreadStart(this.SharedMemoryReaderThread); + taskThread = new Thread(threadState); + taskThread.Name = "MSBuild Parent<-Child Reader"; + taskThread.Start(); + } + + /// + /// Apply a parameter. + /// + public void ApplyParameter(string parameterName, string parameterValue) + { + ErrorUtilities.VerifyThrowArgumentNull(parameterName, nameof(parameterName)); + + if (String.Equals(parameterName, "MAXCPUCOUNT", StringComparison.OrdinalIgnoreCase)) + { + try + { + this.cpuCount = Convert.ToInt32(parameterValue, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + // + } + catch (OverflowException) + { + // + } + } + else if (String.Equals(parameterName, "MSBUILDLOCATION", StringComparison.OrdinalIgnoreCase)) + { + this.locationOfMSBuildExe = parameterValue; + } + else if (String.Equals(parameterName, "NODEREUSE", StringComparison.OrdinalIgnoreCase)) + { + try + { + // There does not seem to be a localizable function for this + if (bool.Parse(parameterValue)) + { + this.enableNodeReuse = true; + } + else + { + this.enableNodeReuse = false; + } + } + catch (FormatException) + { + // + } + catch (ArgumentNullException) + { + // + } + } + } + + public INodeDescription[] QueryNodeDescriptions() + { + return new INodeDescription[cpuCount - 1]; + } + + public void AssignNodeIdentifiers(int[] nodeIds) + { + if ((cpuCount - 1) <= 0) + { + return; + } + + ErrorUtilities.VerifyThrow(nodeIds.Length == nodeData.Length, "Expected an ID for each node"); + + for (int i = 0; i < nodeIds.Length; i++) + { + nodeData[i].NodeId = nodeIds[i]; + } + } + + public void RegisterNodeLogger(LoggerDescription loggerDescription) + { + ErrorUtilities.VerifyThrow(nodeLoggers != null, "Must call Initialize first"); + ErrorUtilities.VerifyThrow(loggerDescription != null, "Logger description should be non-null"); + + if (!nodeLoggers.Contains(loggerDescription)) + { + nodeLoggers.Add(loggerDescription); + } + } + + public void RequestNodeStatus(int nodeIndex, int requestId) + { + ErrorUtilities.VerifyThrow(nodeLoggers != null, "Must call Initialize first"); + ErrorUtilities.VerifyThrow(nodeIndex < nodeData.Length && nodeIndex >= 0, "Node index must be within array boundaries"); + + // If the node has not been launched we need to create a reply + // on its behalf + if (nodeData[nodeIndex].NodeState != NodeState.Launched) + { + NodeStatus nodeStatus = new NodeStatus(requestId, false, 0, 0, 0, nodeData[nodeIndex].NodeState == NodeState.LaunchInProgress); + engineCallback.PostStatus(nodeData[nodeIndex].NodeId, nodeStatus, false); + } + else if (!IsNodeProcessAliveOrUninitialized(nodeIndex)) + { + NodeStatus nodeStatus = new NodeStatus(requestId); // Indicate that the node has exited + engineCallback.PostStatus(nodeData[nodeIndex].NodeId, nodeStatus, false); + } + else + { + // Send the request to the node + LocalCallDescriptorForRequestStatus callDescriptor = + new LocalCallDescriptorForRequestStatus(requestId); + nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); + } + } + + public void PostBuildRequestToNode(int nodeIndex, BuildRequest buildRequest) + { + ErrorUtilities.VerifyThrow(nodeIndex < nodeData.Length && nodeIndex >= 0, "Node index must be within array boundaries"); + + if (nodeData[nodeIndex].NodeState != NodeState.Launched) + { + // Note that we have to check the node status again inside the mutex. This + // ensures that that after flipping the status to launched inside the mutex + // there will be no more writes to the queue of targets waiting to be sent + lock (nodeStateLock) + { + // Check if we didn't initialize this node + if (nodeData[nodeIndex].NodeState != NodeState.Launched && !shuttingDown) + { + // Check if launch is in progress + if (nodeData[nodeIndex].NodeState == NodeState.NotLaunched) + { + nodeData[nodeIndex].NodeState = NodeState.LaunchInProgress; + lock (nodesToLaunch) + { + nodesToLaunch.Enqueue(nodeIndex); + } + ThreadStart threadState = new ThreadStart(this.LaunchNodeAndPostBuildRequest); + Thread taskThread = new Thread(threadState); + taskThread.Name = "MSBuild Node Launcher"; + taskThread.Start(); + } + nodeData[nodeIndex].TargetList.AddFirst(new LinkedListNode(buildRequest)); + } + else + { + LocalCallDescriptorForPostBuildRequests callDescriptor = + new LocalCallDescriptorForPostBuildRequests(buildRequest); + nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); + } + } + } + else + { + LocalCallDescriptorForPostBuildRequests callDescriptor = + new LocalCallDescriptorForPostBuildRequests(buildRequest); + nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); + } + } + + public void PostBuildResultToNode(int nodeIndex, BuildResult buildResult) + { + ErrorUtilities.VerifyThrow(nodeIndex < nodeData.Length && nodeIndex >= 0, "Node index must be within array boundaries"); + ErrorUtilities.VerifyThrow(nodeData[nodeIndex].NodeState == NodeState.Launched, "Node must be launched before result can be posted"); + + LocalCallDescriptorForPostBuildResult callDescriptor = + new LocalCallDescriptorForPostBuildResult(buildResult); + nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); + } + + /// + /// Shutdown the nodes which are being tracked and managed by this localNodeProvider. + /// + public void ShutdownNodes(Node.NodeShutdownLevel nodeShutdownLevel) + { + // Indicate that nodes should no longer be launched + shuttingDown = true; + + // Send out shutdown requests to all active launched nodes + responseCount = activeNodeCount; + SendShutdownRequests(nodeShutdownLevel); + + DateTime startTime = DateTime.Now; + + // Wait for all nodes to shutdown + bool timeoutExpired = false; + + // Loop until we are ready to shutdown. We are ready to shutdown when + // all nodes either have sent their shutdown completed response or they are dead. + // Secondly, we will exit the loop if our shudtownTimeout has expired + TimeSpan shutdownTimeoutSpan = new TimeSpan(0, 0, shutdownTimeout); + while (!ReadyToShutdown() && !timeoutExpired) + { + responseCountChangeEvent.WaitOne(shutdownResponseTimeout, false); + responseCountChangeEvent.Reset(); + + // Timeout when the loop has been executing for more than shutdownTimeout seconds. + timeoutExpired = DateTime.Now.Subtract(startTime) >= shutdownTimeoutSpan; + } + + if (timeoutExpired) + { + foreach (LocalNodeInfo nodeInfo in nodeData) + { + //Terminate all of the nodes which have valid processId's but for which we + // have not recieved a shutdown response + if (nodeInfo.ProcessId > 0 && !nodeInfo.ShutdownResponseReceived) + { + TerminateChildNode(nodeInfo.ProcessId); + } + } + } + + // Reset the shutdown response received properties incase the nodes are going + // to be used for another build on the same engine. + foreach (LocalNodeInfo nodeInfo in nodeData) + { + nodeInfo.ShutdownResponseReceived = false; + } + + // If all nodes are exiting - exit the communication threads + if (nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteSuccess && + nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteFailure) + { + exitCommunicationThreads.Set(); + } + + shuttingDown = false; + } + + /// + /// Determine when the child node has either responsed with a shutdown complete event or the node has died + /// + internal bool ReadyToShutdown() + { + for (int i = 0; i < nodeData.Length; i++) + { + LocalNodeInfo nodeInfo = nodeData[i]; + // Determine if the node is alive or dead, this check will set the processId to invalid if + // the process is dead + IsNodeProcessAliveOrUninitialized(i); + // If any node is still alive and we have not recieved a shutdown response say we are not ready to shutdown + if (nodeInfo.ProcessId > 0 && !nodeInfo.ShutdownResponseReceived) + { + return false; + } + } + return true; + } + /// + /// TEMPORARY + /// + public void UpdateSettings + ( + bool enableCentralizedLogging, + bool enableOnlyLogCriticalEvents, + bool useBreadthFirstTraversalSetting + ) + { + this.centralizedLogging = enableCentralizedLogging; + this.onlyLogCriticalEvents = enableOnlyLogCriticalEvents; + this.useBreadthFirstTraversal = useBreadthFirstTraversalSetting; + + for (int i = 0; i < nodeData.Length; i++) + { + if (nodeData[i].NodeState == NodeState.Launched) + { + UpdateSettings(i); + } + } + } + + private void UpdateSettings(int nodeIndex) + { + // Send the updated settings once the node has initialized + LocalCallDescriptorForUpdateNodeSettings callDescriptor = + new LocalCallDescriptorForUpdateNodeSettings(onlyLogCriticalEvents, centralizedLogging, useBreadthFirstTraversal); + nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); + } + + public void PostIntrospectorCommand(int nodeIndex, TargetInProgessState child, TargetInProgessState parent) + { + // Send the updated settings once the node has initialized + LocalCallDescriptorForPostIntrospectorCommand callDescriptor = + new LocalCallDescriptorForPostIntrospectorCommand(child, parent); + nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); + } + + #endregion + + #region Private methods + + /// + /// Send shutdown message to the launched child nodes. + /// + private void SendShutdownRequests(Node.NodeShutdownLevel nodeShutdownLevel) + { + for (int i = 0; i < nodeData.Length; i++) + { + // If there is a node launch in progress wait for it complete or fail + // before shutting down the node + while (nodeData[i].NodeState == NodeState.LaunchInProgress && !nodeData[i].CommunicationFailed) + { + Thread.Sleep(500); + } + + if (nodeData[i].NodeState == NodeState.Launched) + { + if (!nodeData[i].CommunicationFailed) + { + bool exitProcess = !enableNodeReuse; + // If we are shutting down due to a BuildComplete then dont kill the nodes as this method will be called again in the engine shutdown method + if (nodeShutdownLevel == Node.NodeShutdownLevel.BuildCompleteSuccess || nodeShutdownLevel == Node.NodeShutdownLevel.BuildCompleteFailure) + { + exitProcess = false; + } + // Signal to the node to shutdown + LocalCallDescriptorForShutdownNode callDescriptor = + new LocalCallDescriptorForShutdownNode(nodeShutdownLevel, exitProcess); + nodeData[i].NodeCommandQueue.Enqueue(callDescriptor); + } + else + { + TerminateChildNode(nodeData[i].ProcessId); + } + + if (nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteSuccess && + nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteFailure) + { + nodeData[i].NodeState = NodeState.NotLaunched; + } + } + } + } + + /// + /// Kill the child process directly if we can't communicate with it + /// + private void TerminateChildNode(int processId) + { + try + { + using Process process = Process.GetProcessById(processId); + if (!process.HasExited) + { + process.Kill(); + } + } + catch (ArgumentException) + { + // The exception indicates that the child process is no longer running + } + catch (System.ComponentModel.Win32Exception) + { + // The exception indicates that the child process is no longer running or + // the parent cannot access the child process information due to insufficent security permissions + } + } + + /// + /// Returns true if the process for the given node was started and has not exited + /// + private bool IsNodeProcessAliveOrUninitialized(int nodeId) + { + // If it's alive but not being communicated with anymore, that counts as not alive + if (nodeData[nodeId].CommunicationFailed) + { + return false; + } + + try + { + bool isUninitialized = nodeData[nodeId].ProcessId == LocalNodeInfo.unInitializedProcessId; + + if (isUninitialized) + { + return true; + } + + bool isInvalidProcessId = nodeData[nodeId].ProcessId == LocalNodeInfo.invalidProcessId; + + if (!isInvalidProcessId) + { + using Process process = Process.GetProcessById(nodeData[nodeId].ProcessId); + if (!process.HasExited) + { + return true; + } + + } + } + catch (ArgumentException) + { + // Process already exited + + } + + nodeData[nodeId].ProcessId = LocalNodeInfo.invalidProcessId; + nodeData[nodeId].CommunicationFailed = true; + + return false; + } + + private void DecreaseActiveNodeCount(int nodeId) + { + int i = 0; + for (; i < nodeData.Length; i++) + { + if (nodeData[i].NodeId == nodeId) + { + nodeData[i].ReleaseNode(); + Interlocked.Decrement(ref activeNodeCount); + break; + } + } + ErrorUtilities.VerifyThrow(i < nodeData.Length, "Expected to find a node to decrement count"); + } + + /// + /// This function is used to increment the count of active nodes + /// + private void IncreaseActiveNodeCount() + { + Interlocked.Increment(ref activeNodeCount); + } + + /// + /// This function is used to decrement the count of active nodes + /// + internal void RecordNodeResponse(int nodeId, Node.NodeShutdownLevel shutdownLevel, int totalTaskTime) + { + // If the node is shutting down - decrease the count of active nodes + if (shutdownLevel == Node.NodeShutdownLevel.ErrorShutdown || + shutdownLevel == Node.NodeShutdownLevel.PoliteShutdown) + { + DecreaseActiveNodeCount(nodeId); + } + + //Console.WriteLine("Node " + nodeId + " Task Time " + totalTaskTime); + + int i = 0; + for (; i < nodeData.Length; i++) + { + if (nodeData[i].NodeId == nodeId) + { + nodeData[i].ShutdownResponseReceived = true; + Interlocked.Decrement(ref responseCount); + responseCountChangeEvent.Set(); + break; + } + } + ErrorUtilities.VerifyThrow(i < nodeData.Length, "Expected to find a node to decrement count"); + } + + /// + /// This function is used by the node to set its own processId after it has been initialized + /// + internal void SetNodeProcessId(int processId, int nodeId) + { + for (int i = 0; i < nodeData.Length; i++) + { + if (nodeData[i].NodeId == nodeId) + { + nodeData[i].ProcessId = processId; + break; + } + } + } + + /// + /// This function will start a node and send requests to it + /// + private void LaunchNodeAndPostBuildRequest() + { + int nodeIndex = 0; + + // Find out what node to launch + lock (nodesToLaunch) + { + nodeIndex = nodesToLaunch.Dequeue(); + } + + // If the provider is shutting down - don't launch the node + if (shuttingDown) + { + nodeData[nodeIndex].NodeState = NodeState.NotLaunched; + return; + } + + try + { + // Either launch node or connect to an already running node + InitializeNode(nodeIndex); + + if (!nodeData[nodeIndex].CommunicationFailed) + { + // Change the state of the node to launched + lock (nodeStateLock) + { + nodeData[nodeIndex].NodeState = NodeState.Launched; + } + + // Send all the requests to the node. Note that the requests may end up in + // mixed order with the request currently being posted. + LinkedListNode current = nodeData[nodeIndex].TargetList.First; + BuildRequest[] buildRequests = new BuildRequest[nodeData[nodeIndex].TargetList.Count]; + int i = 0; + while (current != null) + { + buildRequests[i] = current.Value; + i++; + + current = current.Next; + } + LocalCallDescriptorForPostBuildRequests callDescriptor = + new LocalCallDescriptorForPostBuildRequests(buildRequests); + nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); + + nodeData[nodeIndex].TargetList = null; + } + else + { + // Allow the engine to decide how to proceed since the node failed to launch + string message = ResourceUtilities.FormatResourceString("NodeProviderFailure"); + ReportNodeCommunicationFailure(nodeIndex, new Exception(message), false); + } + } + catch (Exception e) + { + // Allow the engine to deal with the exception + ReportNodeCommunicationFailure(nodeIndex, e, false); + } + } + + /// + /// This function establishes communication with a node given an index. If a node + /// is not running it is launched. + /// + 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, EnvironmentUtilities.CurrentProcessId, 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) + { + DecreaseActiveNodeCount(nodeData[nodeIndex].NodeId); + nodeData[nodeIndex].CommunicationFailed = true; + } + } + } + + /// + /// 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 + /// + 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; + } + + /// + /// This function launches a new node given a node index + /// + 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 |= 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 + nodeReadyEvent?.Close(); + + if (exitedDueToError) + { + nodeData[nodeIndex].CommunicationFailed = true; + } + } + } + + /// + /// Report communication failure and update internal state + /// + private void ReportNodeCommunicationFailure + ( + int nodeIndex, + Exception innerException, + bool decreaseActiveNodeCount + ) + { + // Indicate that communication with a particular node has failed + if (nodeIndex >= 0 && nodeIndex < nodeData.Length) + { + if (decreaseActiveNodeCount && !nodeData[nodeIndex].CommunicationFailed) + { + DecreaseActiveNodeCount(nodeData[nodeIndex].NodeId); + } + + nodeData[nodeIndex].CommunicationFailed = true; + } + + string message = ResourceUtilities.FormatResourceString("NodeProviderFailure"); + RemoteErrorException wrappedException = new RemoteErrorException(message, innerException, null); + NodeStatus nodeStatus = new NodeStatus(wrappedException); + + if (nodeIndex < 0 || nodeIndex >= nodeData.Length) + { + // Bogus node index came out of the wait handle, perhaps due to memory pressure + // We can't really do anything except re-throw so this problem can be diagnosed. + throw wrappedException; + } + + engineCallback.PostStatus(nodeData[nodeIndex].NodeId, nodeStatus, false); + } + + /// + /// This thread writes out the messages to the shared memory, where the LocalNode class + /// reads it. + /// + private void SharedMemoryWriterThread() + { + // Create an array of event to the node thread responds + WaitHandle[] waitHandles = new WaitHandle[1 + nodeData.Length]; + waitHandles[0] = exitCommunicationThreads; + for (int i = 0; i < nodeData.Length; i++) + { + waitHandles[i + 1] = nodeData[i].NodeCommandQueue.QueueReadyEvent; + } + + bool continueExecution = true; + + while (continueExecution) + { + int nodeIndex = -1; + try + { + // Wait for the next work item or an exit command + int eventType = WaitHandle.WaitAny(waitHandles); + + if (eventType == 0) + { + // Exit node event + continueExecution = false; + } + else + { + nodeIndex = eventType - 1; + nodeData[nodeIndex].SharedMemoryToNode.Write(nodeData[nodeIndex].NodeCommandQueue, nodeData[nodeIndex].NodeHiPriCommandQueue, false); + } + } + catch (Exception e) + { + // Ignore the queue of commands to the node that failed + if (nodeIndex >= 0 && nodeIndex < nodeData.Length) + { + waitHandles[1 + nodeIndex] = new ManualResetEvent(false); + } + ReportNodeCommunicationFailure(nodeIndex, e, true); + } + } + + for (int i = 0; i < nodeData.Length; i++) + { + // Dispose of the shared memory buffer + if (nodeData[i].SharedMemoryToNode != null) + { + nodeData[i].SharedMemoryToNode.Dispose(); + nodeData[i].SharedMemoryToNode = null; + } + } + } + + /// + /// This thread is responsible for reading messages from the nodes. The messages are posted + /// to the shared memory by the LocalNodeCallback + /// + private void SharedMemoryReaderThread() + { + // Create an array of event to the node thread responds + WaitHandle[] waitHandles = new WaitHandle[1 + nodeData.Length]; + waitHandles[0] = exitCommunicationThreads; + for (int i = 0; i < nodeData.Length; i++) + { + waitHandles[i + 1] = nodeData[i].SharedMemoryFromNode.ReadFlag; + } + + bool continueExecution = true; + + while (continueExecution) + { + int nodeIndex = -1; + try + { + // Wait for the next work item or an exit command + int eventType = WaitHandle.WaitAny(waitHandles); + + if (eventType == 0) + { + // Exit node event + continueExecution = false; + } + else + { + nodeIndex = eventType - 1; + IList localCallDescriptorList = nodeData[nodeIndex].SharedMemoryFromNode.Read(); + + if (localCallDescriptorList != null) + { + foreach (LocalCallDescriptor callDescriptor in localCallDescriptorList) + { + // Act as requested by the call + callDescriptor.HostAction(engineCallback, this, nodeData[nodeIndex].NodeId); + // Check if there is a reply to this call + if (callDescriptor.NeedsReply) + { + nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor.ReplyFromHostAction()); + } + } + } + } + } + catch (Exception e) + { + ReportNodeCommunicationFailure(nodeIndex, e, true); + // Ignore the events reported from that node from now on + if (nodeIndex >= 0 && nodeIndex < nodeData.Length) + { + waitHandles[1 + nodeIndex] = new ManualResetEvent(false); + } + } + } + + // Dispose of shared memory when done + for (int i = 0; i < nodeData.Length; i++) + { + // Dispose of the shared memory buffer + if (nodeData[i].SharedMemoryFromNode != null) + { + nodeData[i].SharedMemoryFromNode.Dispose(); + nodeData[i].SharedMemoryFromNode = null; + } + } + } + + #endregion + + #region Data + private IEngineCallback engineCallback; + private ManualResetEvent exitCommunicationThreads; + + private ManualResetEvent responseCountChangeEvent; + private int activeNodeCount; + private int responseCount; + + private int cpuCount; + + private object nodeStateLock; + private Queue nodesToLaunch; + + private bool centralizedLogging; + + private bool onlyLogCriticalEvents; + + private bool useBreadthFirstTraversal; + + private bool enableNodeReuse = true; + + // True after shut down has been called, this flag prevents launching of new nodes after shutdown has been called + private bool shuttingDown; + + private List nodeLoggers; + + private string locationOfMSBuildExe = null; + + private BuildPropertyGroup parentGlobalProperties; + private ToolsetDefinitionLocations toolsetSearchLocations; + private string startupDirectory; + + private static readonly char[] parameterDelimiters = { ';' }; + private static readonly char[] valueDelimiters = { '=' }; + + private LocalNodeInfo[] nodeData; + + // Timeouts and contants + private const int initializationTimeout = 10 * 1000; // 10 seconds to process the init message + private const int launchTimeout = 60 * 1000; // 60 seconds to launch the process + private const int maximumNodeRestartCount = 2; // try twice to connect to the node + private const int shutdownResponseTimeout = 1000; // every second check if the children are still alive + private static int shutdownTimeout = 30; // Wait for 30 seconds for all nodes to shutdown. + #endregion + + #region Local enums + internal enum NodeState + { + /// + /// This node has not been launched + /// + NotLaunched = 0, + /// + /// This node is in progress of being launched + /// + LaunchInProgress = 1, + /// + /// This node is launched + /// + Launched = 2, + /// + /// This node has been shutdown + /// + Shutdown = 3 + } + #endregion + } +} diff --git a/src/Deprecated/Engine/Microsoft.Build.Engine.csproj b/src/Deprecated/Engine/Microsoft.Build.Engine.csproj new file mode 100644 index 00000000000..5ba31507e2f --- /dev/null +++ b/src/Deprecated/Engine/Microsoft.Build.Engine.csproj @@ -0,0 +1,224 @@ + + + $(FullFrameworkTFM) + $(NoWarn);618 + true + false + + true + $(XMakeRefPath) + $(XMakeRefPath) + true + true + true + This package contains the $(MSBuildProjectName) assembly which contains the legacy compatibility shim for the MSBuild engine. NOTE: This assembly is deprecated. + false + $(NoWarn);1570;1572;1573;1587 + disable + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(AssemblyName).Strings.resources + Designer + + + + Resources\Strings.shared.resx + $(AssemblyName).Strings.shared.resources + Designer + + + + + diff --git a/src/Framework/FileClassifier.cs b/src/Framework/FileClassifier.cs index 2072633870b..0e08b5c13c6 100644 --- a/src/Framework/FileClassifier.cs +++ b/src/Framework/FileClassifier.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; +using Microsoft.Build.Shared; + #if !RUNTIME_TYPE_NETCORE using System.Diagnostics; using System.Linq; @@ -141,10 +143,10 @@ public FileClassifier() // Seems like MSBuild did not run from VS but from CLI. // Identify current process and run it - string processName = Process.GetCurrentProcess().MainModule.FileName; + string? processName = EnvironmentUtilities.ProcessPath; string processFileName = Path.GetFileNameWithoutExtension(processName); - if (string.IsNullOrEmpty(processFileName)) + if (processName == null || string.IsNullOrEmpty(processFileName)) { return null; } diff --git a/src/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj index 271026705ba..911198afdf5 100644 --- a/src/Framework/Microsoft.Build.Framework.csproj +++ b/src/Framework/Microsoft.Build.Framework.csproj @@ -1,4 +1,4 @@ - + $(LibraryTargetFrameworks) true @@ -40,6 +40,7 @@ Shared\BinaryWriterExtensions.cs + Shared\IMSBuildElementLocation.cs diff --git a/src/Framework/Profiler/EvaluationIdProvider.cs b/src/Framework/Profiler/EvaluationIdProvider.cs index e81cc016beb..573204e1e15 100644 --- a/src/Framework/Profiler/EvaluationIdProvider.cs +++ b/src/Framework/Profiler/EvaluationIdProvider.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Threading; +using Microsoft.Build.Shared; #nullable disable @@ -14,7 +15,7 @@ namespace Microsoft.Build.Framework.Profiler internal static class EvaluationIdProvider { private static long _sAssignedId = -1; - private static readonly long ProcessId = Process.GetCurrentProcess().Id; + private static readonly long ProcessId = EnvironmentUtilities.CurrentProcessId; /// /// Returns a unique evaluation id diff --git a/src/MSBuild/MSBuildClientApp.cs b/src/MSBuild/MSBuildClientApp.cs index fbe3d349fc4..be768b58b8d 100644 --- a/src/MSBuild/MSBuildClientApp.cs +++ b/src/MSBuild/MSBuildClientApp.cs @@ -113,10 +113,7 @@ private static string GetCurrentHost() } else { - using (Process currentProcess = Process.GetCurrentProcess()) - { - CurrentHost = currentProcess.MainModule?.FileName ?? throw new InvalidOperationException("Failed to retrieve process executable."); - } + CurrentHost = EnvironmentUtilities.ProcessPath ?? throw new InvalidOperationException("Failed to retrieve process executable."); } } diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index 42b71f65fe9..b71dddae1f3 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -810,9 +810,8 @@ private NodeEngineShutdownReason HandleShutdown() // Wait for the RunTask task runner thread before shutting down so that we can cleanly dispose all WaitHandles. _taskRunnerThread?.Join(); - using StreamWriter debugWriter = _debugCommunications - ? File.CreateText(string.Format(CultureInfo.CurrentCulture, Path.Combine(FileUtilities.TempFileDirectory, @"MSBuild_NodeShutdown_{0}.txt"), Process.GetCurrentProcess().Id)) - : null; + using StreamWriter debugWriter = GetDebugWriter(_debugCommunications); + debugWriter?.WriteLine("Node shutting down with reason {0}.", _shutdownReason); #if !CLR2COMPATIBILITY @@ -859,6 +858,14 @@ private NodeEngineShutdownReason HandleShutdown() #endif return _shutdownReason; + + static StreamWriter GetDebugWriter(bool debugCommunications) + { + StreamWriter debugWriter = debugCommunications + ? File.CreateText(string.Format(CultureInfo.CurrentCulture, Path.Combine(FileUtilities.TempFileDirectory, @"MSBuild_NodeShutdown_{0}.txt"), EnvironmentUtilities.CurrentProcessId)) + : null; + return debugWriter; + } } /// diff --git a/src/MSBuild/PerformanceLogEventListener.cs b/src/MSBuild/PerformanceLogEventListener.cs index 3eb6090cd4b..6772a6aeefc 100644 --- a/src/MSBuild/PerformanceLogEventListener.cs +++ b/src/MSBuild/PerformanceLogEventListener.cs @@ -79,7 +79,7 @@ private PerformanceLogEventListener() internal void Initialize(string logDirectory) { - _processIDStr = Process.GetCurrentProcess().Id.ToString(); + _processIDStr = EnvironmentUtilities.CurrentProcessId.ToString(); // Use a GUID disambiguator to make sure that we have a unique file name. string logFilePath = Path.Combine(logDirectory, $"perf-{_processIDStr}-{Guid.NewGuid().ToString("N")}.log"); diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index aeddef7aba4..7bdfb82380d 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -431,7 +431,7 @@ private static void AppendOutputFile(string path, long elapsedTime) /// private static void DumpCounters(bool initializeOnly) { - Process currentProcess = Process.GetCurrentProcess(); + using Process currentProcess = Process.GetCurrentProcess(); if (!initializeOnly) { @@ -461,7 +461,7 @@ private static void DumpCounters(bool initializeOnly) using PerformanceCounter counter = new PerformanceCounter(".NET CLR Memory", "Process ID", instance, true); try { - if ((int)counter.RawValue == currentProcess.Id) + if ((int)counter.RawValue == EnvironmentUtilities.CurrentProcessId) { currentInstance = instance; break; @@ -627,9 +627,9 @@ private static void DebuggerLaunchCheck() #endif case "2": // Sometimes easier to attach rather than deal with JIT prompt - Process currentProcess = Process.GetCurrentProcess(); - Console.WriteLine($"Waiting for debugger to attach ({currentProcess.MainModule.FileName} PID {currentProcess.Id}). Press enter to continue..."); + Console.WriteLine($"Waiting for debugger to attach ({EnvironmentUtilities.ProcessPath} PID {EnvironmentUtilities.CurrentProcessId}). Press enter to continue..."); Console.ReadLine(); + break; } } @@ -1736,7 +1736,7 @@ private static bool PrintTargets(string projectFile, string toolsVersion, Dictio new BuildManager.DeferredBuildMessage( ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( "Process", - Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty), + EnvironmentUtilities.ProcessPath ?? string.Empty), MessageImportance.Low), new BuildManager.DeferredBuildMessage( ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( @@ -2527,8 +2527,7 @@ private static bool ProcessCommandLineSwitches( if (!Debugger.IsAttached) { - Process currentProcess = Process.GetCurrentProcess(); - Console.WriteLine($"Waiting for debugger to attach... ({currentProcess.MainModule.FileName} PID {currentProcess.Id})"); + Console.WriteLine($"Waiting for debugger to attach... ({EnvironmentUtilities.ProcessPath} PID {EnvironmentUtilities.CurrentProcessId})"); while (!Debugger.IsAttached) { Thread.Sleep(100); @@ -2555,9 +2554,13 @@ private static bool ProcessCommandLineSwitches( } try { - if (lowPriority && Process.GetCurrentProcess().PriorityClass != ProcessPriorityClass.Idle) + if (lowPriority) { - Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.BelowNormal; + using Process currentProcess = Process.GetCurrentProcess(); + if (currentProcess.PriorityClass != ProcessPriorityClass.Idle) + { + currentProcess.PriorityClass = ProcessPriorityClass.BelowNormal; + } } } // We avoid increasing priority because that causes failures on mac/linux, but there is no good way to diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index da3dae7bf0f..a189f58567a 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -1,4 +1,4 @@ - + @@ -70,6 +70,7 @@ CopyOnWriteDictionary.cs + ErrorUtilities.cs diff --git a/src/MSBuildTaskHost/OutOfProcTaskHost.cs b/src/MSBuildTaskHost/OutOfProcTaskHost.cs index 0998734f9ce..90a7970b5e7 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHost.cs @@ -90,8 +90,8 @@ internal static ExitType Execute() #endif case "2": // Sometimes easier to attach rather than deal with JIT prompt - Process currentProcess = Process.GetCurrentProcess(); - Console.WriteLine($"Waiting for debugger to attach ({currentProcess.MainModule.FileName} PID {currentProcess.Id}). Press enter to continue..."); + Console.WriteLine($"Waiting for debugger to attach ({EnvironmentUtilities.ProcessPath} PID {EnvironmentUtilities.CurrentProcessId}). Press enter to continue..."); + Console.ReadLine(); break; } diff --git a/src/Shared/BuildEnvironmentHelper.cs b/src/Shared/BuildEnvironmentHelper.cs index 2f3dce9f66b..c3615e4acf6 100644 --- a/src/Shared/BuildEnvironmentHelper.cs +++ b/src/Shared/BuildEnvironmentHelper.cs @@ -431,12 +431,13 @@ private static string GetProcessFromRunningProcess() // an unmanaged application (for example, using custom CLR hosting). if (AssemblyUtilities.EntryAssembly == null) { - return Process.GetCurrentProcess().MainModule.FileName; + return EnvironmentUtilities.ProcessPath; } return AssemblyUtilities.GetAssemblyLocation(AssemblyUtilities.EntryAssembly); #else - return Process.GetCurrentProcess().MainModule.FileName; + + return EnvironmentUtilities.ProcessPath; #endif } diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index d9d361df7ba..e0af8233119 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -108,7 +108,8 @@ protected internal Handshake(HandshakeOptions nodeType) fileVersionMinor = fileVersion.Minor; fileVersionBuild = fileVersion.Build; fileVersionPrivate = fileVersion.Revision; - sessionId = Process.GetCurrentProcess().SessionId; + using Process currentProcess = Process.GetCurrentProcess(); + sessionId = currentProcess.SessionId; } // This is used as a key, so it does not need to be human readable. @@ -836,7 +837,7 @@ private static void TraceCore(int nodeId, string message) fileName += ".txt"; using (StreamWriter file = FileUtilities.OpenWrite( - String.Format(CultureInfo.CurrentCulture, Path.Combine(s_debugDumpPath, fileName), Process.GetCurrentProcess().Id, nodeId), append: true)) + string.Format(CultureInfo.CurrentCulture, Path.Combine(s_debugDumpPath, fileName), EnvironmentUtilities.CurrentProcessId, nodeId), append: true)) { long now = DateTime.UtcNow.Ticks; float millisecondsSinceLastLog = (float)(now - s_lastLoggedTicks) / 10000L; diff --git a/src/Shared/Debugging/DebugUtils.cs b/src/Shared/Debugging/DebugUtils.cs index de83a6de360..ed9da076678 100644 --- a/src/Shared/Debugging/DebugUtils.cs +++ b/src/Shared/Debugging/DebugUtils.cs @@ -92,13 +92,13 @@ private static bool CurrentProcessMatchesDebugName() { var processNameToBreakInto = Environment.GetEnvironmentVariable("MSBuildDebugProcessName"); var thisProcessMatchesName = string.IsNullOrWhiteSpace(processNameToBreakInto) || - Process.GetCurrentProcess().ProcessName.Contains(processNameToBreakInto); + EnvironmentUtilities.ProcessName.Contains(processNameToBreakInto); return thisProcessMatchesName; } public static readonly string ProcessInfoString = - $"{ProcessNodeMode.Value}_{Process.GetCurrentProcess().ProcessName}_PID={Process.GetCurrentProcess().Id}_x{(Environment.Is64BitProcess ? "64" : "86")}"; + $"{ProcessNodeMode.Value}_{EnvironmentUtilities.ProcessName}_PID={EnvironmentUtilities.CurrentProcessId}_x{(Environment.Is64BitProcess ? "64" : "86")}"; public static readonly bool ShouldDebugCurrentProcess = CurrentProcessMatchesDebugName(); diff --git a/src/Shared/EnvironmentUtilities.cs b/src/Shared/EnvironmentUtilities.cs index 3b275ef40b5..b64e792b53d 100644 --- a/src/Shared/EnvironmentUtilities.cs +++ b/src/Shared/EnvironmentUtilities.cs @@ -1,17 +1,100 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + using System; +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading; namespace Microsoft.Build.Shared { internal static partial class EnvironmentUtilities { +#if NET472_OR_GREATER || NETCOREAPP public static bool Is64BitProcess => Marshal.SizeOf() == 8; public static bool Is64BitOperatingSystem => Environment.Is64BitOperatingSystem; +#endif + +#if !NETCOREAPP + private static volatile int s_processId; + private static volatile string? s_processPath; +#endif + private static volatile string? s_processName; + + /// Gets the unique identifier for the current process. + public static int CurrentProcessId + { + get + { +#if NETCOREAPP + return Environment.ProcessId; +#else + // copied from Environment.ProcessId + int processId = s_processId; + if (processId == 0) + { + using Process currentProcess = Process.GetCurrentProcess(); + s_processId = processId = currentProcess.Id; + + // Assume that process Id zero is invalid for user processes. It holds for all mainstream operating systems. + Debug.Assert(processId != 0); + } + + return processId; +#endif + } + } + + /// + /// Returns the path of the executable that started the currently executing process. Returns null when the path is not available. + /// + /// Path of the executable that started the currently executing process + /// + /// If the executable is renamed or deleted before this property is first accessed, the return value is undefined and depends on the operating system. + /// + public static string? ProcessPath + { + get + { +#if NETCOREAPP + return Environment.ProcessPath; +#else + // copied from Environment.ProcessPath + string? processPath = s_processPath; + if (processPath == null) + { + // The value is cached both as a performance optimization and to ensure that the API always returns + // the same path in a given process. + using Process currentProcess = Process.GetCurrentProcess(); + Interlocked.CompareExchange(ref s_processPath, currentProcess.MainModule.FileName ?? "", null); + processPath = s_processPath; + Debug.Assert(processPath != null); + } + + return (processPath?.Length != 0) ? processPath : null; +#endif + } + } + + public static string ProcessName + { + get + { + string? processName = s_processName; + if (processName == null) + { + using Process currentProcess = Process.GetCurrentProcess(); + Interlocked.CompareExchange(ref s_processName, currentProcess.ProcessName, null); + processName = s_processName; + } + + return processName; + } + } public static bool IsWellKnownEnvironmentDerivedProperty(string propertyName) { diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index bfce3376e3e..16a19dcadc2 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -352,7 +352,7 @@ internal static void DumpExceptionToFile(Exception ex) // because we're a child node with no console to log to, so die Directory.CreateDirectory(DebugDumpPath); - var pid = Process.GetCurrentProcess().Id; + var pid = EnvironmentUtilities.CurrentProcessId; // This naming pattern is assumed in ReadAnyExceptionFromFile s_dumpFileName = Path.Combine(DebugDumpPath, $"MSBuild_pid-{pid}_{guid:n}.failure.txt"); diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index bbb4413632e..e8fbff8e6e3 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -128,7 +128,7 @@ internal static string GetCacheDirectory() { if (cacheDirectory == null) { - cacheDirectory = Path.Combine(TempFileDirectory, String.Format(CultureInfo.CurrentUICulture, "MSBuild{0}-{1}", Process.GetCurrentProcess().Id, AppDomain.CurrentDomain.Id)); + cacheDirectory = Path.Combine(TempFileDirectory, string.Format(CultureInfo.CurrentUICulture, "MSBuild{0}-{1}", EnvironmentUtilities.CurrentProcessId, AppDomain.CurrentDomain.Id)); } return cacheDirectory; @@ -182,7 +182,7 @@ internal static bool CanWriteToDirectory(string directory) string testFilePath = Path.Combine(directory, $"MSBuild_{Guid.NewGuid().ToString("N")}_testFile.txt"); FileInfo file = new(testFilePath); file.Directory.Create(); // If the directory already exists, this method does nothing. - File.WriteAllText(testFilePath, $"MSBuild process {Process.GetCurrentProcess().Id} successfully wrote to file."); + File.WriteAllText(testFilePath, $"MSBuild process {EnvironmentUtilities.CurrentProcessId} successfully wrote to file."); File.Delete(testFilePath); return true; } diff --git a/src/Shared/NamedPipeUtil.cs b/src/Shared/NamedPipeUtil.cs index 5c5290b40c8..bf31c0193f6 100644 --- a/src/Shared/NamedPipeUtil.cs +++ b/src/Shared/NamedPipeUtil.cs @@ -12,7 +12,7 @@ internal static string GetPlatformSpecificPipeName(int? processId = null) { if (processId is null) { - processId = Process.GetCurrentProcess().Id; + processId = EnvironmentUtilities.CurrentProcessId; } string pipeName = $"MSBuild{processId}"; diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index 9c4db39cb77..37d7cece260 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -37,9 +37,6 @@ - - EnvironmentUtilities.cs - AssemblyDependency\AssemblyFoldersEx.cs diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs index 2d97134b43c..50a26f7f5f2 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Runtime.InteropServices; using Microsoft.Build.Framework; +using Microsoft.Build.Shared; using Microsoft.Build.Utilities; #nullable disable @@ -52,7 +53,7 @@ protected RoslynCodeTaskFactoryCompilerBase() if (string.IsNullOrEmpty(_dotnetCliPath)) { // Fallback to get dotnet path from current process which might be dotnet executable. - _dotnetCliPath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; + _dotnetCliPath = EnvironmentUtilities.ProcessPath; } // If dotnet path is not found, rely on dotnet via the system's PATH diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index 6b8153dbc9c..e5a0a89b595 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -48,9 +48,6 @@ Shared\AssemblyFolders\Serialization\AssemblyFolderItem.cs - - Shared\EnvironmentUtilities.cs - Shared\BuildEnvironmentHelper.cs From 74153aa85e563b705fe9787e7301738b8ae37929 Mon Sep 17 00:00:00 2001 From: SimaTian Date: Mon, 20 Jan 2025 10:23:56 +0100 Subject: [PATCH 2/3] cherry pick re-added those long deleted filed. remedying that --- .../Engine/LocalProvider/LocalNode.cs | 684 ----------- .../Engine/LocalProvider/LocalNodeProvider.cs | 1056 ----------------- .../Engine/Microsoft.Build.Engine.csproj | 224 ---- 3 files changed, 1964 deletions(-) delete mode 100644 src/Deprecated/Engine/LocalProvider/LocalNode.cs delete mode 100644 src/Deprecated/Engine/LocalProvider/LocalNodeProvider.cs delete mode 100644 src/Deprecated/Engine/Microsoft.Build.Engine.csproj diff --git a/src/Deprecated/Engine/LocalProvider/LocalNode.cs b/src/Deprecated/Engine/LocalProvider/LocalNode.cs deleted file mode 100644 index d957cf840bb..00000000000 --- a/src/Deprecated/Engine/LocalProvider/LocalNode.cs +++ /dev/null @@ -1,684 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE -// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL -// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED. - -using System; -using System.Collections; -using System.Diagnostics; -using System.IO; -using System.Security.AccessControl; -using System.Threading; -using Microsoft.Build.BuildEngine.Shared; -using Microsoft.Build.Shared; - -namespace Microsoft.Build.BuildEngine -{ - /// - /// This class (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: - /// Microsoft.Build.Construction - /// Microsoft.Build.Evaluation - /// Microsoft.Build.Execution - /// - /// This class hosts a node class in the child process. It uses shared memory to communicate - /// with the local node provider. - /// Wraps a Node. - /// - /// - /// [!WARNING] - /// > This class (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: - /// > - /// > - /// > - /// ]]> - /// - public class LocalNode - { - #region Static Constructors - /// - /// This method (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: - /// Microsoft.Build.Construction - /// Microsoft.Build.Evaluation - /// Microsoft.Build.Execution - /// - /// Hook up an unhandled exception handler, in case our error handling paths are leaky - /// - /// - /// [!WARNING] - /// > This method (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: - /// > - /// > - /// > - /// ]]> - /// - static LocalNode() - { - AppDomain currentDomain = AppDomain.CurrentDomain; - currentDomain.UnhandledException += UnhandledExceptionHandler; - } - #endregion - - #region Static Methods - - /// - /// Dump any unhandled exceptions to a file so they can be diagnosed - /// - private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) - { - Exception ex = (Exception)e.ExceptionObject; - DumpExceptionToFile(ex); - } - - /// - /// Dump the exception information to a file - /// - internal static void DumpExceptionToFile(Exception ex) - { - // Lock as multiple threads may throw simultaneously - lock (dumpFileLocker) - { - if (dumpFileName == null) - { - Guid guid = Guid.NewGuid(); - string tempPath = Path.GetTempPath(); - - // For some reason we get Watson buckets because GetTempPath gives us a folder here that doesn't exist. - // Either because %TMP% is misdefined, or because they deleted the temp folder during the build. - if (!Directory.Exists(tempPath)) - { - // If this throws, no sense catching it, we can't log it now, and we're here - // because we're a child node with no console to log to, so die - Directory.CreateDirectory(tempPath); - } - - dumpFileName = Path.Combine(tempPath, "MSBuild_" + guid.ToString()); - - using (StreamWriter writer = new StreamWriter(dumpFileName, true /*append*/)) - { - writer.WriteLine("UNHANDLED EXCEPTIONS FROM CHILD NODE:"); - writer.WriteLine("==================="); - } - } - - using (StreamWriter writer = new StreamWriter(dumpFileName, true /*append*/)) - { - writer.WriteLine(DateTime.Now.ToLongTimeString()); - writer.WriteLine(ex.ToString()); - writer.WriteLine("==================="); - } - } - } - - #endregion - - #region Constructors - - /// - /// Creates an instance of this class. - /// - internal LocalNode(int nodeNumberIn) - { - this.nodeNumber = nodeNumberIn; - - engineCallback = new LocalNodeCallback(communicationThreadExitEvent, this); - } - - #endregion - - #region Communication Methods - - /// - /// This method causes the reader and writer threads to start and create the shared memory structures - /// - private 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(); - } - - /// - /// This method causes the reader and writer threads to exit and dispose of the shared memory structures - /// - private void StopCommunicationThreads() - { - communicationThreadExitEvent.Set(); - - // Wait for communication threads to exit - Thread writerThread = engineCallback.GetWriterThread(); - // The threads may not exist if the child has timed out before the parent has told the node - // to start up its communication threads. This can happen if the node is started with /nodemode:x - // and no parent is running, or if the parent node has spawned a new process and then crashed - // before establishing communication with the child node. - writerThread?.Join(); - - readerThread?.Join(); - - // Make sure the exit event is not set - communicationThreadExitEvent.Reset(); - } - - #endregion - - #region Startup Methods - - /// - /// Create global events necessary for handshaking with the parent - /// - /// - /// True if events created successfully and false otherwise - 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; - } - - /// - /// This method (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: - /// Microsoft.Build.Construction - /// Microsoft.Build.Evaluation - /// Microsoft.Build.Execution - /// - /// This function starts local node when process is launched and shuts it down on time out - /// Called by msbuild.exe. - /// - /// - /// [!WARNING] - /// > This method (and the whole namespace) is deprecated. Please use the classes in these namespaces instead: - /// > - /// > - /// > - /// ]]> - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Agreed not to touch entries from Deprecated folder")] - 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(); - } - - #endregion - - #region Methods - - /// - /// This method is run in its own thread, it is responsible for reading messages sent from the parent process - /// through the shared memory region. - /// - private void SharedMemoryReaderThread() - { - // Create an array of event to the node thread responds - WaitHandle[] waitHandles = new WaitHandle[2]; - waitHandles[0] = communicationThreadExitEvent; - waitHandles[1] = sharedMemory.ReadFlag; - - bool continueExecution = true; - - try - { - while (continueExecution) - { - // Wait for the next work item or an exit command - int eventType = WaitHandle.WaitAny(waitHandles); - - if (eventType == 0) - { - // Exit node event - continueExecution = false; - } - else - { - // Read the list of LocalCallDescriptors from sharedMemory, - // this will be null if a large object is being read from shared - // memory and will continue to be null until the large object has - // been completly sent. - IList localCallDescriptorList = sharedMemory.Read(); - - if (localCallDescriptorList != null) - { - foreach (LocalCallDescriptor callDescriptor in localCallDescriptorList) - { - // Execute the command method which relates to running on a child node - callDescriptor.NodeAction(node, this); - - if ((callDescriptor.IsReply) && (callDescriptor is LocalReplyCallDescriptor)) - { - // Process the reply from the parent so it can be looked in a hashtable based - // on the call descriptor who requested the reply. - engineCallback.PostReplyFromParent((LocalReplyCallDescriptor)callDescriptor); - } - } - } - } - } - } - catch (Exception e) - { - // Will rethrow the exception if necessary - ReportFatalCommunicationError(e); - } - - // Dispose of the shared memory buffer - if (sharedMemory != null) - { - sharedMemory.Dispose(); - sharedMemory = null; - } - } - - /// - /// This method will shutdown the node being hosted by the child process and notify the parent process if requested, - /// - /// What kind of shutdown is causing the child node to shutdown - /// should the child process exit as part of the shutdown process - /// Indicates if the parent process should be notified the child node is being shutdown - internal void ShutdownNode(Node.NodeShutdownLevel shutdownLevel, bool exitProcess, bool noParentNotification) - { - if (node != null) - { - try - { - node.ShutdownNode(shutdownLevel); - - if (!noParentNotification) - { - // Write the last event out directly - LocalCallDescriptorForShutdownComplete callDescriptor = - - new LocalCallDescriptorForShutdownComplete(shutdownLevel, node.TotalTaskTime); - // Post the message indicating that the shutdown is complete - engineCallback.PostMessageToParent(callDescriptor, true); - } - } - catch (Exception e) - { - if (shutdownLevel != Node.NodeShutdownLevel.ErrorShutdown) - { - ReportNonFatalCommunicationError(e); - } - } - } - - // If the shutdownLevel is not a build complete message, then this means there was a politeshutdown or an error shutdown, null the node out - // as either it is no longer needed due to the node goign idle or there was a error and it is now in a bad state. - if (shutdownLevel != Node.NodeShutdownLevel.BuildCompleteSuccess && - shutdownLevel != Node.NodeShutdownLevel.BuildCompleteFailure) - { - node = null; - notInUseEvent.Set(); - } - - if (exitProcess) - { - // Even if we completed a build, if we are goign to exit the process we need to null out the node and set the notInUseEvent, this is - // accomplished by calling this method again with the ErrorShutdown handle - if (shutdownLevel == Node.NodeShutdownLevel.BuildCompleteSuccess || shutdownLevel == Node.NodeShutdownLevel.BuildCompleteFailure) - { - ShutdownNode(Node.NodeShutdownLevel.ErrorShutdown, false, true); - } - // Signal all the communication threads to exit - shutdownEvent.Set(); - } - } - - /// - /// This methods activates the local node - /// - internal void Activate - ( - Hashtable environmentVariables, - LoggerDescription[] nodeLoggers, - int nodeId, - BuildPropertyGroup parentGlobalProperties, - ToolsetDefinitionLocations toolsetSearchLocations, - int parentId, - string parentStartupDirectory - ) - { - ErrorUtilities.VerifyThrow(node == null, "Expected node to be null on activation."); - - this.parentProcessId = parentId; - - engineCallback.Reset(); - - inUseEvent.Set(); - - // Clear the environment so that we dont have extra variables laying around, this - // may be a performance hog but needs to be done - IDictionary variableDictionary = Environment.GetEnvironmentVariables(); - foreach (string variableName in variableDictionary.Keys) - { - Environment.SetEnvironmentVariable(variableName, null); - } - - foreach (string key in environmentVariables.Keys) - { - Environment.SetEnvironmentVariable(key, (string)environmentVariables[key]); - } - - // Host the msbuild engine and system - node = new Node(nodeId, nodeLoggers, engineCallback, parentGlobalProperties, toolsetSearchLocations, parentStartupDirectory); - - // Write the initialization complete event out directly - LocalCallDescriptorForInitializationComplete callDescriptor = - new LocalCallDescriptorForInitializationComplete(EnvironmentUtilities.CurrentProcessId); - - // Post the message indicating that the initialization is complete - engineCallback.PostMessageToParent(callDescriptor, true); - } - - /// - /// This method checks is the parent process has not exited - /// - /// True if the parent process is still alive - private bool IsParentProcessAlive() - { - bool isParentAlive = true; - try - { - // Check if the parent is still there - using Process parentProcess = Process.GetProcessById(parentProcessId); - if (parentProcess.HasExited) - { - isParentAlive = false; - } - } - catch (ArgumentException) - { - isParentAlive = false; - } - - if (!isParentAlive) - { - // No logging's going to reach the parent at this point: - // indicate on the console what's going on - string message = ResourceUtilities.FormatResourceString("ParentProcessUnexpectedlyDied", node.NodeId); - Console.WriteLine(message); - } - - return isParentAlive; - } - - /// - /// Any error occuring in the shared memory transport is considered to be fatal - /// - /// - /// Re-throws exception passed in - internal void ReportFatalCommunicationError(Exception originalException) - { - try - { - DumpExceptionToFile(originalException); - } - finally - { - node?.ReportFatalCommunicationError(originalException, null); - } - } - - /// - /// This function is used to report exceptions which don't indicate breakdown - /// of communication with the parent - /// - /// - internal void ReportNonFatalCommunicationError(Exception originalException) - { - if (node != null) - { - try - { - DumpExceptionToFile(originalException); - } - finally - { - node.ReportUnhandledError(originalException); - } - } - else - { - // Since there is no node object report rethrow the exception - ReportFatalCommunicationError(originalException); - } - } - - #endregion - #region Properties - internal static string DumpFileName - { - get - { - return dumpFileName; - } - } - #endregion - - #region Member data - - private Node node; - private SharedMemory sharedMemory; - private LocalNodeCallback engineCallback; - private int parentProcessId; - private int nodeNumber; - private Thread readerThread; - private static object dumpFileLocker = new Object(); - - // Public named events - // If this event is set the node host process is currently running - private static EventWaitHandle globalNodeActive; - // If this event is set the node is currently running a build - private static EventWaitHandle globalNodeInUse; - // If this event exists the node is reserved for use by a particular parent engine - // the node keeps a handle to this event during builds to prevent it from being used - // by another parent engine if the original dies - private static EventWaitHandle globalNodeReserveHandle; - // If this event is set the node will immediatelly exit. The event is used by the - // parent engine to cause the node to exit if communication is lost. - private static EventWaitHandle globalNodeErrorShutdown; - // This event is used to cause the child to create the shared memory structures to start communication - // with the parent - private static EventWaitHandle globalInitiateActivationEvent; - // This event is used to indicate to the parent that shared memory buffers have been created and are ready for - // use - private static EventWaitHandle globalNodeActivate; - // Private local events - private static ManualResetEvent communicationThreadExitEvent = new ManualResetEvent(false); - private static ManualResetEvent shutdownEvent = new ManualResetEvent(false); - private static ManualResetEvent notInUseEvent = new ManualResetEvent(false); - - /// - /// Indicates the node is now in use. This means the node has recieved an activate command with initialization - /// data from the parent procss - /// - private static ManualResetEvent inUseEvent = new ManualResetEvent(false); - - /// - /// Randomly generated file name for all exceptions thrown by this node that need to be dumped to a file. - /// (There may be more than one exception, if they occur on different threads.) - /// - private static string dumpFileName = null; - - // Timeouts && Constants - private const int inactivityTimeout = 60 * 1000; // 60 seconds of inactivity to exit - private const int parentCheckInterval = 5 * 1000; // Check if the parent process is there every 5 seconds - - #endregion - - } -} - diff --git a/src/Deprecated/Engine/LocalProvider/LocalNodeProvider.cs b/src/Deprecated/Engine/LocalProvider/LocalNodeProvider.cs deleted file mode 100644 index b97ae16dad6..00000000000 --- a/src/Deprecated/Engine/LocalProvider/LocalNodeProvider.cs +++ /dev/null @@ -1,1056 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE -// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL -// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Globalization; -using System.IO; -using Microsoft.Build.BuildEngine.Shared; -using System.Runtime.InteropServices; -using Microsoft.Build.Shared; - -namespace Microsoft.Build.BuildEngine -{ - internal class LocalNodeProvider : INodeProvider - { - #region Methods implementing INodeProvider - public void Initialize - ( - string configuration, - IEngineCallback parentEngineCallback, - BuildPropertyGroup parentGlobalPropertyGroup, - ToolsetDefinitionLocations toolSetSearchLocations, - string startupDirectory - ) - { - // Get from the environment how long we should wait in seconds for shutdown to complete - string shutdownTimeoutFromEnvironment = Environment.GetEnvironmentVariable("MSBUILDNODESHUTDOWNTIMEOUT"); - int result; - if (int.TryParse(shutdownTimeoutFromEnvironment, out result) && result >= 0) - { - shutdownTimeout = result; - } - - this.cpuCount = 1; - - if (configuration != null) - { - // Split out the parameter sets based on ; - string[] parameters = configuration.Split(parameterDelimiters); - // Go through each of the parameter name value pairs and split them appart - for (int param = 0; param < parameters.Length; param++) - { - if (parameters[param].Length > 0) - { - string[] parameterComponents = parameters[param].Split(valueDelimiters); - // If there is a name and value associated with the parameter, apply the paramter to the provider - if (parameterComponents.Length == 2) - { - ApplyParameter(parameterComponents[0], parameterComponents[1]); - } - else // Only the parameter name is known, this could be for a boolean parameter - { - ApplyParameter(parameters[param], null); - } - } - } - } - - /* If we dont get a path passed in as a parameter, we can only assume that our path - is in the current appdomain basedirectory, this is the base directory - that the assembly resolver uses to probe for assemblies - */ - if (string.IsNullOrEmpty(this.locationOfMSBuildExe)) - { - this.locationOfMSBuildExe = AppDomain.CurrentDomain.BaseDirectory; - } - if ((cpuCount - 1) <= 0) - { - return; - } - - this.exitCommunicationThreads = new ManualResetEvent(false); - - this.activeNodeCount = 0; - this.responseCountChangeEvent = new ManualResetEvent(false); - - this.nodeStateLock = new object(); - this.nodesToLaunch = new Queue(); - this.nodeLoggers = new List(); - - nodeData = new LocalNodeInfo[cpuCount - 1]; - - // Initialize the internal state indicating that no nodes have been launched - int lastUsedNodeNumber = 0; - for (int i = 0; i < nodeData.Length; i++) - { - nodeData[i] = new LocalNodeInfo(lastUsedNodeNumber); - lastUsedNodeNumber = nodeData[i].NodeNumber + 1; - } - - // Set up the callback - this.engineCallback = parentEngineCallback; - this.parentGlobalProperties = parentGlobalPropertyGroup; - this.toolsetSearchLocations = toolSetSearchLocations; - this.startupDirectory = startupDirectory; - - // Default node settings - centralizedLogging = false; - onlyLogCriticalEvents = false; - useBreadthFirstTraversal = true; - shuttingDown = false; - - // Start the thread that will be processing the calls from the parent engine - ThreadStart threadState = new ThreadStart(this.SharedMemoryWriterThread); - Thread taskThread = new Thread(threadState); - taskThread.Name = "MSBuild Parent->Child Writer"; - taskThread.Start(); - threadState = new ThreadStart(this.SharedMemoryReaderThread); - taskThread = new Thread(threadState); - taskThread.Name = "MSBuild Parent<-Child Reader"; - taskThread.Start(); - } - - /// - /// Apply a parameter. - /// - public void ApplyParameter(string parameterName, string parameterValue) - { - ErrorUtilities.VerifyThrowArgumentNull(parameterName, nameof(parameterName)); - - if (String.Equals(parameterName, "MAXCPUCOUNT", StringComparison.OrdinalIgnoreCase)) - { - try - { - this.cpuCount = Convert.ToInt32(parameterValue, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - // - } - catch (OverflowException) - { - // - } - } - else if (String.Equals(parameterName, "MSBUILDLOCATION", StringComparison.OrdinalIgnoreCase)) - { - this.locationOfMSBuildExe = parameterValue; - } - else if (String.Equals(parameterName, "NODEREUSE", StringComparison.OrdinalIgnoreCase)) - { - try - { - // There does not seem to be a localizable function for this - if (bool.Parse(parameterValue)) - { - this.enableNodeReuse = true; - } - else - { - this.enableNodeReuse = false; - } - } - catch (FormatException) - { - // - } - catch (ArgumentNullException) - { - // - } - } - } - - public INodeDescription[] QueryNodeDescriptions() - { - return new INodeDescription[cpuCount - 1]; - } - - public void AssignNodeIdentifiers(int[] nodeIds) - { - if ((cpuCount - 1) <= 0) - { - return; - } - - ErrorUtilities.VerifyThrow(nodeIds.Length == nodeData.Length, "Expected an ID for each node"); - - for (int i = 0; i < nodeIds.Length; i++) - { - nodeData[i].NodeId = nodeIds[i]; - } - } - - public void RegisterNodeLogger(LoggerDescription loggerDescription) - { - ErrorUtilities.VerifyThrow(nodeLoggers != null, "Must call Initialize first"); - ErrorUtilities.VerifyThrow(loggerDescription != null, "Logger description should be non-null"); - - if (!nodeLoggers.Contains(loggerDescription)) - { - nodeLoggers.Add(loggerDescription); - } - } - - public void RequestNodeStatus(int nodeIndex, int requestId) - { - ErrorUtilities.VerifyThrow(nodeLoggers != null, "Must call Initialize first"); - ErrorUtilities.VerifyThrow(nodeIndex < nodeData.Length && nodeIndex >= 0, "Node index must be within array boundaries"); - - // If the node has not been launched we need to create a reply - // on its behalf - if (nodeData[nodeIndex].NodeState != NodeState.Launched) - { - NodeStatus nodeStatus = new NodeStatus(requestId, false, 0, 0, 0, nodeData[nodeIndex].NodeState == NodeState.LaunchInProgress); - engineCallback.PostStatus(nodeData[nodeIndex].NodeId, nodeStatus, false); - } - else if (!IsNodeProcessAliveOrUninitialized(nodeIndex)) - { - NodeStatus nodeStatus = new NodeStatus(requestId); // Indicate that the node has exited - engineCallback.PostStatus(nodeData[nodeIndex].NodeId, nodeStatus, false); - } - else - { - // Send the request to the node - LocalCallDescriptorForRequestStatus callDescriptor = - new LocalCallDescriptorForRequestStatus(requestId); - nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); - } - } - - public void PostBuildRequestToNode(int nodeIndex, BuildRequest buildRequest) - { - ErrorUtilities.VerifyThrow(nodeIndex < nodeData.Length && nodeIndex >= 0, "Node index must be within array boundaries"); - - if (nodeData[nodeIndex].NodeState != NodeState.Launched) - { - // Note that we have to check the node status again inside the mutex. This - // ensures that that after flipping the status to launched inside the mutex - // there will be no more writes to the queue of targets waiting to be sent - lock (nodeStateLock) - { - // Check if we didn't initialize this node - if (nodeData[nodeIndex].NodeState != NodeState.Launched && !shuttingDown) - { - // Check if launch is in progress - if (nodeData[nodeIndex].NodeState == NodeState.NotLaunched) - { - nodeData[nodeIndex].NodeState = NodeState.LaunchInProgress; - lock (nodesToLaunch) - { - nodesToLaunch.Enqueue(nodeIndex); - } - ThreadStart threadState = new ThreadStart(this.LaunchNodeAndPostBuildRequest); - Thread taskThread = new Thread(threadState); - taskThread.Name = "MSBuild Node Launcher"; - taskThread.Start(); - } - nodeData[nodeIndex].TargetList.AddFirst(new LinkedListNode(buildRequest)); - } - else - { - LocalCallDescriptorForPostBuildRequests callDescriptor = - new LocalCallDescriptorForPostBuildRequests(buildRequest); - nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); - } - } - } - else - { - LocalCallDescriptorForPostBuildRequests callDescriptor = - new LocalCallDescriptorForPostBuildRequests(buildRequest); - nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); - } - } - - public void PostBuildResultToNode(int nodeIndex, BuildResult buildResult) - { - ErrorUtilities.VerifyThrow(nodeIndex < nodeData.Length && nodeIndex >= 0, "Node index must be within array boundaries"); - ErrorUtilities.VerifyThrow(nodeData[nodeIndex].NodeState == NodeState.Launched, "Node must be launched before result can be posted"); - - LocalCallDescriptorForPostBuildResult callDescriptor = - new LocalCallDescriptorForPostBuildResult(buildResult); - nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); - } - - /// - /// Shutdown the nodes which are being tracked and managed by this localNodeProvider. - /// - public void ShutdownNodes(Node.NodeShutdownLevel nodeShutdownLevel) - { - // Indicate that nodes should no longer be launched - shuttingDown = true; - - // Send out shutdown requests to all active launched nodes - responseCount = activeNodeCount; - SendShutdownRequests(nodeShutdownLevel); - - DateTime startTime = DateTime.Now; - - // Wait for all nodes to shutdown - bool timeoutExpired = false; - - // Loop until we are ready to shutdown. We are ready to shutdown when - // all nodes either have sent their shutdown completed response or they are dead. - // Secondly, we will exit the loop if our shudtownTimeout has expired - TimeSpan shutdownTimeoutSpan = new TimeSpan(0, 0, shutdownTimeout); - while (!ReadyToShutdown() && !timeoutExpired) - { - responseCountChangeEvent.WaitOne(shutdownResponseTimeout, false); - responseCountChangeEvent.Reset(); - - // Timeout when the loop has been executing for more than shutdownTimeout seconds. - timeoutExpired = DateTime.Now.Subtract(startTime) >= shutdownTimeoutSpan; - } - - if (timeoutExpired) - { - foreach (LocalNodeInfo nodeInfo in nodeData) - { - //Terminate all of the nodes which have valid processId's but for which we - // have not recieved a shutdown response - if (nodeInfo.ProcessId > 0 && !nodeInfo.ShutdownResponseReceived) - { - TerminateChildNode(nodeInfo.ProcessId); - } - } - } - - // Reset the shutdown response received properties incase the nodes are going - // to be used for another build on the same engine. - foreach (LocalNodeInfo nodeInfo in nodeData) - { - nodeInfo.ShutdownResponseReceived = false; - } - - // If all nodes are exiting - exit the communication threads - if (nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteSuccess && - nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteFailure) - { - exitCommunicationThreads.Set(); - } - - shuttingDown = false; - } - - /// - /// Determine when the child node has either responsed with a shutdown complete event or the node has died - /// - internal bool ReadyToShutdown() - { - for (int i = 0; i < nodeData.Length; i++) - { - LocalNodeInfo nodeInfo = nodeData[i]; - // Determine if the node is alive or dead, this check will set the processId to invalid if - // the process is dead - IsNodeProcessAliveOrUninitialized(i); - // If any node is still alive and we have not recieved a shutdown response say we are not ready to shutdown - if (nodeInfo.ProcessId > 0 && !nodeInfo.ShutdownResponseReceived) - { - return false; - } - } - return true; - } - /// - /// TEMPORARY - /// - public void UpdateSettings - ( - bool enableCentralizedLogging, - bool enableOnlyLogCriticalEvents, - bool useBreadthFirstTraversalSetting - ) - { - this.centralizedLogging = enableCentralizedLogging; - this.onlyLogCriticalEvents = enableOnlyLogCriticalEvents; - this.useBreadthFirstTraversal = useBreadthFirstTraversalSetting; - - for (int i = 0; i < nodeData.Length; i++) - { - if (nodeData[i].NodeState == NodeState.Launched) - { - UpdateSettings(i); - } - } - } - - private void UpdateSettings(int nodeIndex) - { - // Send the updated settings once the node has initialized - LocalCallDescriptorForUpdateNodeSettings callDescriptor = - new LocalCallDescriptorForUpdateNodeSettings(onlyLogCriticalEvents, centralizedLogging, useBreadthFirstTraversal); - nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); - } - - public void PostIntrospectorCommand(int nodeIndex, TargetInProgessState child, TargetInProgessState parent) - { - // Send the updated settings once the node has initialized - LocalCallDescriptorForPostIntrospectorCommand callDescriptor = - new LocalCallDescriptorForPostIntrospectorCommand(child, parent); - nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); - } - - #endregion - - #region Private methods - - /// - /// Send shutdown message to the launched child nodes. - /// - private void SendShutdownRequests(Node.NodeShutdownLevel nodeShutdownLevel) - { - for (int i = 0; i < nodeData.Length; i++) - { - // If there is a node launch in progress wait for it complete or fail - // before shutting down the node - while (nodeData[i].NodeState == NodeState.LaunchInProgress && !nodeData[i].CommunicationFailed) - { - Thread.Sleep(500); - } - - if (nodeData[i].NodeState == NodeState.Launched) - { - if (!nodeData[i].CommunicationFailed) - { - bool exitProcess = !enableNodeReuse; - // If we are shutting down due to a BuildComplete then dont kill the nodes as this method will be called again in the engine shutdown method - if (nodeShutdownLevel == Node.NodeShutdownLevel.BuildCompleteSuccess || nodeShutdownLevel == Node.NodeShutdownLevel.BuildCompleteFailure) - { - exitProcess = false; - } - // Signal to the node to shutdown - LocalCallDescriptorForShutdownNode callDescriptor = - new LocalCallDescriptorForShutdownNode(nodeShutdownLevel, exitProcess); - nodeData[i].NodeCommandQueue.Enqueue(callDescriptor); - } - else - { - TerminateChildNode(nodeData[i].ProcessId); - } - - if (nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteSuccess && - nodeShutdownLevel != Node.NodeShutdownLevel.BuildCompleteFailure) - { - nodeData[i].NodeState = NodeState.NotLaunched; - } - } - } - } - - /// - /// Kill the child process directly if we can't communicate with it - /// - private void TerminateChildNode(int processId) - { - try - { - using Process process = Process.GetProcessById(processId); - if (!process.HasExited) - { - process.Kill(); - } - } - catch (ArgumentException) - { - // The exception indicates that the child process is no longer running - } - catch (System.ComponentModel.Win32Exception) - { - // The exception indicates that the child process is no longer running or - // the parent cannot access the child process information due to insufficent security permissions - } - } - - /// - /// Returns true if the process for the given node was started and has not exited - /// - private bool IsNodeProcessAliveOrUninitialized(int nodeId) - { - // If it's alive but not being communicated with anymore, that counts as not alive - if (nodeData[nodeId].CommunicationFailed) - { - return false; - } - - try - { - bool isUninitialized = nodeData[nodeId].ProcessId == LocalNodeInfo.unInitializedProcessId; - - if (isUninitialized) - { - return true; - } - - bool isInvalidProcessId = nodeData[nodeId].ProcessId == LocalNodeInfo.invalidProcessId; - - if (!isInvalidProcessId) - { - using Process process = Process.GetProcessById(nodeData[nodeId].ProcessId); - if (!process.HasExited) - { - return true; - } - - } - } - catch (ArgumentException) - { - // Process already exited - - } - - nodeData[nodeId].ProcessId = LocalNodeInfo.invalidProcessId; - nodeData[nodeId].CommunicationFailed = true; - - return false; - } - - private void DecreaseActiveNodeCount(int nodeId) - { - int i = 0; - for (; i < nodeData.Length; i++) - { - if (nodeData[i].NodeId == nodeId) - { - nodeData[i].ReleaseNode(); - Interlocked.Decrement(ref activeNodeCount); - break; - } - } - ErrorUtilities.VerifyThrow(i < nodeData.Length, "Expected to find a node to decrement count"); - } - - /// - /// This function is used to increment the count of active nodes - /// - private void IncreaseActiveNodeCount() - { - Interlocked.Increment(ref activeNodeCount); - } - - /// - /// This function is used to decrement the count of active nodes - /// - internal void RecordNodeResponse(int nodeId, Node.NodeShutdownLevel shutdownLevel, int totalTaskTime) - { - // If the node is shutting down - decrease the count of active nodes - if (shutdownLevel == Node.NodeShutdownLevel.ErrorShutdown || - shutdownLevel == Node.NodeShutdownLevel.PoliteShutdown) - { - DecreaseActiveNodeCount(nodeId); - } - - //Console.WriteLine("Node " + nodeId + " Task Time " + totalTaskTime); - - int i = 0; - for (; i < nodeData.Length; i++) - { - if (nodeData[i].NodeId == nodeId) - { - nodeData[i].ShutdownResponseReceived = true; - Interlocked.Decrement(ref responseCount); - responseCountChangeEvent.Set(); - break; - } - } - ErrorUtilities.VerifyThrow(i < nodeData.Length, "Expected to find a node to decrement count"); - } - - /// - /// This function is used by the node to set its own processId after it has been initialized - /// - internal void SetNodeProcessId(int processId, int nodeId) - { - for (int i = 0; i < nodeData.Length; i++) - { - if (nodeData[i].NodeId == nodeId) - { - nodeData[i].ProcessId = processId; - break; - } - } - } - - /// - /// This function will start a node and send requests to it - /// - private void LaunchNodeAndPostBuildRequest() - { - int nodeIndex = 0; - - // Find out what node to launch - lock (nodesToLaunch) - { - nodeIndex = nodesToLaunch.Dequeue(); - } - - // If the provider is shutting down - don't launch the node - if (shuttingDown) - { - nodeData[nodeIndex].NodeState = NodeState.NotLaunched; - return; - } - - try - { - // Either launch node or connect to an already running node - InitializeNode(nodeIndex); - - if (!nodeData[nodeIndex].CommunicationFailed) - { - // Change the state of the node to launched - lock (nodeStateLock) - { - nodeData[nodeIndex].NodeState = NodeState.Launched; - } - - // Send all the requests to the node. Note that the requests may end up in - // mixed order with the request currently being posted. - LinkedListNode current = nodeData[nodeIndex].TargetList.First; - BuildRequest[] buildRequests = new BuildRequest[nodeData[nodeIndex].TargetList.Count]; - int i = 0; - while (current != null) - { - buildRequests[i] = current.Value; - i++; - - current = current.Next; - } - LocalCallDescriptorForPostBuildRequests callDescriptor = - new LocalCallDescriptorForPostBuildRequests(buildRequests); - nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); - - nodeData[nodeIndex].TargetList = null; - } - else - { - // Allow the engine to decide how to proceed since the node failed to launch - string message = ResourceUtilities.FormatResourceString("NodeProviderFailure"); - ReportNodeCommunicationFailure(nodeIndex, new Exception(message), false); - } - } - catch (Exception e) - { - // Allow the engine to deal with the exception - ReportNodeCommunicationFailure(nodeIndex, e, false); - } - } - - /// - /// This function establishes communication with a node given an index. If a node - /// is not running it is launched. - /// - 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, EnvironmentUtilities.CurrentProcessId, 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) - { - DecreaseActiveNodeCount(nodeData[nodeIndex].NodeId); - nodeData[nodeIndex].CommunicationFailed = true; - } - } - } - - /// - /// 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 - /// - 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; - } - - /// - /// This function launches a new node given a node index - /// - 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 |= 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 - nodeReadyEvent?.Close(); - - if (exitedDueToError) - { - nodeData[nodeIndex].CommunicationFailed = true; - } - } - } - - /// - /// Report communication failure and update internal state - /// - private void ReportNodeCommunicationFailure - ( - int nodeIndex, - Exception innerException, - bool decreaseActiveNodeCount - ) - { - // Indicate that communication with a particular node has failed - if (nodeIndex >= 0 && nodeIndex < nodeData.Length) - { - if (decreaseActiveNodeCount && !nodeData[nodeIndex].CommunicationFailed) - { - DecreaseActiveNodeCount(nodeData[nodeIndex].NodeId); - } - - nodeData[nodeIndex].CommunicationFailed = true; - } - - string message = ResourceUtilities.FormatResourceString("NodeProviderFailure"); - RemoteErrorException wrappedException = new RemoteErrorException(message, innerException, null); - NodeStatus nodeStatus = new NodeStatus(wrappedException); - - if (nodeIndex < 0 || nodeIndex >= nodeData.Length) - { - // Bogus node index came out of the wait handle, perhaps due to memory pressure - // We can't really do anything except re-throw so this problem can be diagnosed. - throw wrappedException; - } - - engineCallback.PostStatus(nodeData[nodeIndex].NodeId, nodeStatus, false); - } - - /// - /// This thread writes out the messages to the shared memory, where the LocalNode class - /// reads it. - /// - private void SharedMemoryWriterThread() - { - // Create an array of event to the node thread responds - WaitHandle[] waitHandles = new WaitHandle[1 + nodeData.Length]; - waitHandles[0] = exitCommunicationThreads; - for (int i = 0; i < nodeData.Length; i++) - { - waitHandles[i + 1] = nodeData[i].NodeCommandQueue.QueueReadyEvent; - } - - bool continueExecution = true; - - while (continueExecution) - { - int nodeIndex = -1; - try - { - // Wait for the next work item or an exit command - int eventType = WaitHandle.WaitAny(waitHandles); - - if (eventType == 0) - { - // Exit node event - continueExecution = false; - } - else - { - nodeIndex = eventType - 1; - nodeData[nodeIndex].SharedMemoryToNode.Write(nodeData[nodeIndex].NodeCommandQueue, nodeData[nodeIndex].NodeHiPriCommandQueue, false); - } - } - catch (Exception e) - { - // Ignore the queue of commands to the node that failed - if (nodeIndex >= 0 && nodeIndex < nodeData.Length) - { - waitHandles[1 + nodeIndex] = new ManualResetEvent(false); - } - ReportNodeCommunicationFailure(nodeIndex, e, true); - } - } - - for (int i = 0; i < nodeData.Length; i++) - { - // Dispose of the shared memory buffer - if (nodeData[i].SharedMemoryToNode != null) - { - nodeData[i].SharedMemoryToNode.Dispose(); - nodeData[i].SharedMemoryToNode = null; - } - } - } - - /// - /// This thread is responsible for reading messages from the nodes. The messages are posted - /// to the shared memory by the LocalNodeCallback - /// - private void SharedMemoryReaderThread() - { - // Create an array of event to the node thread responds - WaitHandle[] waitHandles = new WaitHandle[1 + nodeData.Length]; - waitHandles[0] = exitCommunicationThreads; - for (int i = 0; i < nodeData.Length; i++) - { - waitHandles[i + 1] = nodeData[i].SharedMemoryFromNode.ReadFlag; - } - - bool continueExecution = true; - - while (continueExecution) - { - int nodeIndex = -1; - try - { - // Wait for the next work item or an exit command - int eventType = WaitHandle.WaitAny(waitHandles); - - if (eventType == 0) - { - // Exit node event - continueExecution = false; - } - else - { - nodeIndex = eventType - 1; - IList localCallDescriptorList = nodeData[nodeIndex].SharedMemoryFromNode.Read(); - - if (localCallDescriptorList != null) - { - foreach (LocalCallDescriptor callDescriptor in localCallDescriptorList) - { - // Act as requested by the call - callDescriptor.HostAction(engineCallback, this, nodeData[nodeIndex].NodeId); - // Check if there is a reply to this call - if (callDescriptor.NeedsReply) - { - nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor.ReplyFromHostAction()); - } - } - } - } - } - catch (Exception e) - { - ReportNodeCommunicationFailure(nodeIndex, e, true); - // Ignore the events reported from that node from now on - if (nodeIndex >= 0 && nodeIndex < nodeData.Length) - { - waitHandles[1 + nodeIndex] = new ManualResetEvent(false); - } - } - } - - // Dispose of shared memory when done - for (int i = 0; i < nodeData.Length; i++) - { - // Dispose of the shared memory buffer - if (nodeData[i].SharedMemoryFromNode != null) - { - nodeData[i].SharedMemoryFromNode.Dispose(); - nodeData[i].SharedMemoryFromNode = null; - } - } - } - - #endregion - - #region Data - private IEngineCallback engineCallback; - private ManualResetEvent exitCommunicationThreads; - - private ManualResetEvent responseCountChangeEvent; - private int activeNodeCount; - private int responseCount; - - private int cpuCount; - - private object nodeStateLock; - private Queue nodesToLaunch; - - private bool centralizedLogging; - - private bool onlyLogCriticalEvents; - - private bool useBreadthFirstTraversal; - - private bool enableNodeReuse = true; - - // True after shut down has been called, this flag prevents launching of new nodes after shutdown has been called - private bool shuttingDown; - - private List nodeLoggers; - - private string locationOfMSBuildExe = null; - - private BuildPropertyGroup parentGlobalProperties; - private ToolsetDefinitionLocations toolsetSearchLocations; - private string startupDirectory; - - private static readonly char[] parameterDelimiters = { ';' }; - private static readonly char[] valueDelimiters = { '=' }; - - private LocalNodeInfo[] nodeData; - - // Timeouts and contants - private const int initializationTimeout = 10 * 1000; // 10 seconds to process the init message - private const int launchTimeout = 60 * 1000; // 60 seconds to launch the process - private const int maximumNodeRestartCount = 2; // try twice to connect to the node - private const int shutdownResponseTimeout = 1000; // every second check if the children are still alive - private static int shutdownTimeout = 30; // Wait for 30 seconds for all nodes to shutdown. - #endregion - - #region Local enums - internal enum NodeState - { - /// - /// This node has not been launched - /// - NotLaunched = 0, - /// - /// This node is in progress of being launched - /// - LaunchInProgress = 1, - /// - /// This node is launched - /// - Launched = 2, - /// - /// This node has been shutdown - /// - Shutdown = 3 - } - #endregion - } -} diff --git a/src/Deprecated/Engine/Microsoft.Build.Engine.csproj b/src/Deprecated/Engine/Microsoft.Build.Engine.csproj deleted file mode 100644 index 5ba31507e2f..00000000000 --- a/src/Deprecated/Engine/Microsoft.Build.Engine.csproj +++ /dev/null @@ -1,224 +0,0 @@ - - - $(FullFrameworkTFM) - $(NoWarn);618 - true - false - - true - $(XMakeRefPath) - $(XMakeRefPath) - true - true - true - This package contains the $(MSBuildProjectName) assembly which contains the legacy compatibility shim for the MSBuild engine. NOTE: This assembly is deprecated. - false - $(NoWarn);1570;1572;1573;1587 - disable - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(AssemblyName).Strings.resources - Designer - - - - Resources\Strings.shared.resx - $(AssemblyName).Strings.shared.resources - Designer - - - - - From e99dc97b32924292881990124b7685dcb51edc6b Mon Sep 17 00:00:00 2001 From: Eric Arndt Date: Mon, 3 Feb 2025 09:55:53 -0800 Subject: [PATCH 3/3] Undo unnecessary addition of local function --- src/MSBuild/OutOfProcTaskHostNode.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index b71dddae1f3..f862ae2adca 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -810,7 +810,9 @@ private NodeEngineShutdownReason HandleShutdown() // Wait for the RunTask task runner thread before shutting down so that we can cleanly dispose all WaitHandles. _taskRunnerThread?.Join(); - using StreamWriter debugWriter = GetDebugWriter(_debugCommunications); + using StreamWriter debugWriter = _debugCommunications + ? File.CreateText(string.Format(CultureInfo.CurrentCulture, Path.Combine(FileUtilities.TempFileDirectory, @"MSBuild_NodeShutdown_{0}.txt"), EnvironmentUtilities.CurrentProcessId)) + : null; debugWriter?.WriteLine("Node shutting down with reason {0}.", _shutdownReason); @@ -858,14 +860,6 @@ private NodeEngineShutdownReason HandleShutdown() #endif return _shutdownReason; - - static StreamWriter GetDebugWriter(bool debugCommunications) - { - StreamWriter debugWriter = debugCommunications - ? File.CreateText(string.Format(CultureInfo.CurrentCulture, Path.Combine(FileUtilities.TempFileDirectory, @"MSBuild_NodeShutdown_{0}.txt"), EnvironmentUtilities.CurrentProcessId)) - : null; - return debugWriter; - } } ///