From afc81fa6113af03a8e1cd7bd90500e7e9ac01c09 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Sat, 24 Mar 2018 22:57:47 -0500 Subject: [PATCH] Escape hatch: share stdout with node children (#3130) The VSTest runner emits log messages to its stdout. So far, in .NET Core, that has resulted in them getting piped all the way up through the worker MSBuild that launched the task, the entry-point MSBuild coordinating the build, and the `dotnet test` invocation that started it all. The introduction of node reuse for .NET Core makes this untenable: worker nodes should not share stdout with the process that happened to launch them, because they will be long-lived and may do entirely unrelated builds. But for now, this breaks the `dotnet test` scenario. This commit creates an escape hatch environment variable `MSBUILDENSURESTDOUTFORTASKPROCESSES` that can be used in combination with `/nodereuse:false` to create a cone of MSBuild processes that all share std handles. Enables workaround for Microsoft/vstest#1503. --- .../NodeProviderOutOfProcBase.cs | 37 +++++++++++++------ src/Shared/Traits.cs | 6 +++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs index c3a0bda0b0a..773aed1af7b 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs @@ -23,6 +23,7 @@ using BackendNativeMethods = Microsoft.Build.BackEnd.NativeMethods; using System.Threading.Tasks; +using Microsoft.Build.Utilities; namespace Microsoft.Build.BackEnd { @@ -470,14 +471,21 @@ private int LaunchNode(string msbuildLocation, string commandLineArgs) // Null out the process handles so that the parent process does not wait for the child process // to exit before it can exit. uint creationFlags = 0; - startInfo.dwFlags = BackendNativeMethods.STARTFUSESTDHANDLES; + if (Traits.Instance.EscapeHatches.EnsureStdOutForChildNodesIsPrimaryStdout) + { + creationFlags = BackendNativeMethods.NORMALPRIORITYCLASS; + } if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDNODEWINDOW"))) { - startInfo.hStdError = BackendNativeMethods.InvalidHandle; - startInfo.hStdInput = BackendNativeMethods.InvalidHandle; - startInfo.hStdOutput = BackendNativeMethods.InvalidHandle; - creationFlags = creationFlags | BackendNativeMethods.CREATENOWINDOW; + if (!Traits.Instance.EscapeHatches.EnsureStdOutForChildNodesIsPrimaryStdout) + { + startInfo.hStdError = BackendNativeMethods.InvalidHandle; + startInfo.hStdInput = BackendNativeMethods.InvalidHandle; + startInfo.hStdOutput = BackendNativeMethods.InvalidHandle; + startInfo.dwFlags = BackendNativeMethods.STARTFUSESTDHANDLES; + creationFlags = creationFlags | BackendNativeMethods.CREATENOWINDOW; + } } else { @@ -497,12 +505,6 @@ private int LaunchNode(string msbuildLocation, string commandLineArgs) // Run the child process with the same host as the currently-running process. exeName = GetCurrentHost(); commandLineArgs = "\"" + msbuildLocation + "\" " + commandLineArgs; - - if (NativeMethodsShared.IsWindows) - { - // Repeat the executable name _again_ because Core MSBuild expects it - commandLineArgs = exeName + " " + commandLineArgs; - } #endif if (!NativeMethodsShared.IsWindows) @@ -511,7 +513,10 @@ private int LaunchNode(string msbuildLocation, string commandLineArgs) ProcessStartInfo processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = exeName; processStartInfo.Arguments = commandLineArgs; - processStartInfo.CreateNoWindow = (creationFlags | BackendNativeMethods.CREATENOWINDOW) == BackendNativeMethods.CREATENOWINDOW; + if (!Traits.Instance.EscapeHatches.EnsureStdOutForChildNodesIsPrimaryStdout) + { + processStartInfo.CreateNoWindow = (creationFlags | BackendNativeMethods.CREATENOWINDOW) == BackendNativeMethods.CREATENOWINDOW; + } processStartInfo.UseShellExecute = false; Process process; @@ -537,6 +542,14 @@ private int LaunchNode(string msbuildLocation, string commandLineArgs) } else { +#if RUNTIME_TYPE_NETCORE + if (NativeMethodsShared.IsWindows) + { + // Repeat the executable name in the args to suit CreateProcess + commandLineArgs = exeName + " " + commandLineArgs; + } +#endif + BackendNativeMethods.PROCESS_INFORMATION processInfo = new BackendNativeMethods.PROCESS_INFORMATION(); bool result = BackendNativeMethods.CreateProcess diff --git a/src/Shared/Traits.cs b/src/Shared/Traits.cs index 56e86f8b5e5..82cc2e10027 100644 --- a/src/Shared/Traits.cs +++ b/src/Shared/Traits.cs @@ -127,6 +127,12 @@ internal class EscapeHatches /// public readonly bool UseAutoRunWhenLaunchingProcessUnderCmd = Environment.GetEnvironmentVariable("MSBUILDUSERAUTORUNINCMD") == "1"; + /// + /// Workaround for https://github.com/Microsoft/vstest/issues/1503. + /// + public readonly bool EnsureStdOutForChildNodesIsPrimaryStdout = Environment.GetEnvironmentVariable("MSBUILDENSURESTDOUTFORTASKPROCESSES") == "1"; + + private static bool? ParseNullableBoolFromEnvironmentVariable(string environmentVariable) { var value = Environment.GetEnvironmentVariable(environmentVariable);