diff --git a/scripts/Deploy-MSBuild.ps1 b/scripts/Deploy-MSBuild.ps1
index b7bf08ced01..c9bc90217fe 100644
--- a/scripts/Deploy-MSBuild.ps1
+++ b/scripts/Deploy-MSBuild.ps1
@@ -74,11 +74,11 @@ $filesToCopyToBin = @(
FileToCopy "$bootstrapBinDirectory\Microsoft.Managed.targets"
FileToCopy "$bootstrapBinDirectory\Microsoft.Managed.Before.targets"
FileToCopy "$bootstrapBinDirectory\Microsoft.Managed.After.targets"
- FileToCopy "$bootstrapBinDirectory\Microsoft.Net.props"
- FileToCopy "$bootstrapBinDirectory\Microsoft.NetFramework.CurrentVersion.props"
- FileToCopy "$bootstrapBinDirectory\Microsoft.NetFramework.CurrentVersion.targets"
- FileToCopy "$bootstrapBinDirectory\Microsoft.NetFramework.props"
- FileToCopy "$bootstrapBinDirectory\Microsoft.NetFramework.targets"
+ FileToCopy "$bootstrapBinDirectory\Microsoft.NET.props"
+ FileToCopy "$bootstrapBinDirectory\Microsoft.NETFramework.CurrentVersion.props"
+ FileToCopy "$bootstrapBinDirectory\Microsoft.NETFramework.CurrentVersion.targets"
+ FileToCopy "$bootstrapBinDirectory\Microsoft.NETFramework.props"
+ FileToCopy "$bootstrapBinDirectory\Microsoft.NETFramework.targets"
FileToCopy "$bootstrapBinDirectory\Microsoft.VisualBasic.CrossTargeting.targets"
FileToCopy "$bootstrapBinDirectory\Microsoft.VisualBasic.CurrentVersion.targets"
FileToCopy "$bootstrapBinDirectory\Microsoft.VisualBasic.targets"
diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs
index e071160c912..271454fb2a9 100644
--- a/src/Build/BackEnd/BuildManager/BuildManager.cs
+++ b/src/Build/BackEnd/BuildManager/BuildManager.cs
@@ -629,7 +629,7 @@ void Callback(object state)
}
}
- ShutdownConnectedNodesAsync(true /* abort */);
+ ShutdownConnectedNodes(true /* abort */);
CheckForActiveNodesAndCleanUpSubmissions();
}
}
@@ -774,7 +774,7 @@ public void EndBuild()
try
{
_noActiveSubmissionsEvent.WaitOne();
- ShutdownConnectedNodesAsync(false /* normal termination */);
+ ShutdownConnectedNodes(false /* normal termination */);
_noNodesActiveEvent.WaitOne();
// Wait for all of the actions in the work queue to drain. Wait() could throw here if there was an unhandled exception
@@ -1955,7 +1955,7 @@ public void Dispose()
/// Asks the nodeManager to tell the currently connected nodes to shut down and sets a flag preventing all non-shutdown-related packets from
/// being processed.
///
- private void ShutdownConnectedNodesAsync(bool abort)
+ private void ShutdownConnectedNodes(bool abort)
{
_shuttingDown = true;
diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
index 501d9ddbbc8..0889994b493 100644
--- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
+++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
@@ -25,6 +25,10 @@
using Microsoft.Build.Utilities;
using BackendNativeMethods = Microsoft.Build.BackEnd.NativeMethods;
+using Task = System.Threading.Tasks.Task;
+using DotNetFrameworkArchitecture = Microsoft.Build.Shared.DotNetFrameworkArchitecture;
+using Microsoft.Build.Framework;
+using Microsoft.Build.BackEnd.Logging;
namespace Microsoft.Build.BackEnd
{
@@ -49,6 +53,11 @@ internal abstract class NodeProviderOutOfProcBase
///
private const int TimeoutForNewNodeCreation = 30000;
+ ///
+ /// The amount of time to wait for an out-of-proc node to exit.
+ ///
+ private const int TimeoutForWaitForExit = 30000;
+
///
/// The build component host.
///
@@ -95,9 +104,30 @@ protected void ShutdownConnectedNodes(List contextsToShutDown, bool
// Send the build completion message to the nodes, causing them to shutdown or reset.
_processesToIgnore.Clear();
+ // We wait for child nodes to exit to avoid them changing the terminal
+ // after this process terminates.
+ bool waitForExit = !enableReuse &&
+ !Console.IsInputRedirected &&
+ Traits.Instance.EscapeHatches.EnsureStdOutForChildNodesIsPrimaryStdout;
+
+ Task[] waitForExitTasks = waitForExit && contextsToShutDown.Count > 0 ? new Task[contextsToShutDown.Count] : null;
+ int i = 0;
+ var loggingService = _componentHost.LoggingService;
foreach (NodeContext nodeContext in contextsToShutDown)
{
- nodeContext?.SendData(new NodeBuildComplete(enableReuse));
+ if (nodeContext is null)
+ {
+ continue;
+ }
+ nodeContext.SendData(new NodeBuildComplete(enableReuse));
+ if (waitForExit)
+ {
+ waitForExitTasks[i++] = nodeContext.WaitForExitAsync(loggingService);
+ }
+ }
+ if (waitForExitTasks != null)
+ {
+ Task.WaitAll(waitForExitTasks);
}
}
@@ -138,7 +168,7 @@ protected void ShutdownAllNodes(bool nodeReuse, NodeContextTerminateDelegate ter
{
// If we're able to connect to such a process, send a packet requesting its termination
CommunicationsUtilities.Trace("Shutting down node with pid = {0}", nodeProcess.Id);
- NodeContext nodeContext = new NodeContext(0, nodeProcess.Id, nodeStream, factory, terminateNode);
+ NodeContext nodeContext = new NodeContext(0, nodeProcess, nodeStream, factory, terminateNode);
nodeContext.SendData(new NodeBuildComplete(false /* no node reuse */));
nodeStream.Dispose();
}
@@ -204,7 +234,7 @@ protected NodeContext GetNode(string msbuildLocation, string commandLineArgs, in
{
// Connection successful, use this node.
CommunicationsUtilities.Trace("Successfully connected to existed node {0} which is PID {1}", nodeId, nodeProcess.Id);
- return new NodeContext(nodeId, nodeProcess.Id, nodeStream, factory, terminateNode);
+ return new NodeContext(nodeId, nodeProcess, nodeStream, factory, terminateNode);
}
}
}
@@ -242,20 +272,20 @@ protected NodeContext GetNode(string msbuildLocation, string commandLineArgs, in
#endif
// Create the node process
- int msbuildProcessId = LaunchNode(msbuildLocation, commandLineArgs);
- _processesToIgnore.Add(GetProcessesToIgnoreKey(hostHandshake, msbuildProcessId));
+ Process msbuildProcess = LaunchNode(msbuildLocation, commandLineArgs);
+ _processesToIgnore.Add(GetProcessesToIgnoreKey(hostHandshake, msbuildProcess.Id));
// Note, when running under IMAGEFILEEXECUTIONOPTIONS registry key to debug, the process ID
// gotten back from CreateProcess is that of the debugger, which causes this to try to connect
// to the debugger process. Instead, use MSBUILDDEBUGONSTART=1
// Now try to connect to it.
- Stream nodeStream = TryConnectToProcess(msbuildProcessId, TimeoutForNewNodeCreation, hostHandshake);
+ Stream nodeStream = TryConnectToProcess(msbuildProcess.Id, TimeoutForNewNodeCreation, hostHandshake);
if (nodeStream != null)
{
// Connection successful, use this node.
- CommunicationsUtilities.Trace("Successfully connected to created node {0} which is PID {1}", nodeId, msbuildProcessId);
- return new NodeContext(nodeId, msbuildProcessId, nodeStream, factory, terminateNode);
+ CommunicationsUtilities.Trace("Successfully connected to created node {0} which is PID {1}", nodeId, msbuildProcess.Id);
+ return new NodeContext(nodeId, msbuildProcess, nodeStream, factory, terminateNode);
}
}
@@ -391,7 +421,7 @@ private Stream TryConnectToProcess(int nodeProcessId, int timeout, Handshake han
///
/// Creates a new MSBuild process
///
- private int LaunchNode(string msbuildLocation, string commandLineArgs)
+ private Process LaunchNode(string msbuildLocation, string commandLineArgs)
{
// Should always have been set already.
ErrorUtilities.VerifyThrowInternalLength(msbuildLocation, nameof(msbuildLocation));
@@ -490,7 +520,7 @@ private int LaunchNode(string msbuildLocation, string commandLineArgs)
}
CommunicationsUtilities.Trace("Successfully launched {1} node with PID {0}", process.Id, exeName);
- return process.Id;
+ return process;
}
else
{
@@ -548,7 +578,7 @@ out processInfo
}
CommunicationsUtilities.Trace("Successfully launched {1} node with PID {0}", childProcessId, exeName);
- return childProcessId;
+ return Process.GetProcessById(childProcessId);
}
}
@@ -582,6 +612,13 @@ private static string GetCurrentHost()
///
internal class NodeContext
{
+ enum ExitPacketState
+ {
+ None,
+ ExitPacketQueued,
+ ExitPacketSent
+ }
+
// The pipe(s) used to communicate with the node.
private Stream _clientToServerStream;
private Stream _serverToClientStream;
@@ -597,9 +634,9 @@ internal class NodeContext
private int _nodeId;
///
- /// The process id
+ /// The node process.
///
- private int _processId;
+ private readonly Process _process;
///
/// An array used to store the header byte for each packet when read.
@@ -631,14 +668,14 @@ internal class NodeContext
private Task _packetWriteDrainTask = Task.CompletedTask;
///
- /// Event indicating the node has terminated.
+ /// Delegate called when the context terminates.
///
- private ManualResetEvent _nodeTerminated;
+ private NodeContextTerminateDelegate _terminateDelegate;
///
- /// Delegate called when the context terminates.
+ /// Tracks the state of the packet sent to terminate the node.
///
- private NodeContextTerminateDelegate _terminateDelegate;
+ private ExitPacketState _exitPacketState;
///
/// Per node read buffers
@@ -648,20 +685,18 @@ internal class NodeContext
///
/// Constructor.
///
- public NodeContext(int nodeId, int processId,
+ public NodeContext(int nodeId, Process process,
Stream nodePipe,
INodePacketFactory factory, NodeContextTerminateDelegate terminateDelegate)
{
_nodeId = nodeId;
- _processId = processId;
+ _process = process;
_clientToServerStream = nodePipe;
_serverToClientStream = nodePipe;
_packetFactory = factory;
_headerByte = new byte[5]; // 1 for the packet type, 4 for the body length
-
_readBufferMemoryStream = new MemoryStream();
_writeBufferMemoryStream = new MemoryStream();
- _nodeTerminated = new ManualResetEvent(false);
_terminateDelegate = terminateDelegate;
_sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer();
}
@@ -749,6 +784,10 @@ public async Task RunPacketReadLoopAsync()
/// The packet to send.
public void SendData(INodePacket packet)
{
+ if (IsExitPacket(packet))
+ {
+ _exitPacketState = ExitPacketState.ExitPacketQueued;
+ }
_packetWriteQueue.Add(packet);
DrainPacketQueue();
}
@@ -816,6 +855,10 @@ private void SendDataCore(INodePacket packet)
int lengthToWrite = Math.Min(writeStreamLength - i, MaxPacketWriteSize);
_serverToClientStream.Write(writeStreamBuffer, i, lengthToWrite);
}
+ if (IsExitPacket(packet))
+ {
+ _exitPacketState = ExitPacketState.ExitPacketSent;
+ }
}
catch (IOException e)
{
@@ -828,6 +871,11 @@ private void SendDataCore(INodePacket packet)
}
}
+ private static bool IsExitPacket(INodePacket packet)
+ {
+ return packet is NodeBuildComplete buildCompletePacket && !buildCompletePacket.PrepareForReuse;
+ }
+
///
/// Avoid having a BinaryWriter just to write a 4-byte int
///
@@ -842,7 +890,7 @@ private void WriteInt32(MemoryStream stream, int value)
///
/// Closes the node's context, disconnecting it from the node.
///
- public void Close()
+ private void Close()
{
_clientToServerStream.Dispose();
if (!object.ReferenceEquals(_clientToServerStream, _serverToClientStream))
@@ -852,6 +900,52 @@ public void Close()
_terminateDelegate(_nodeId);
}
+ ///
+ /// Waits for the child node process to exit.
+ ///
+ public async Task WaitForExitAsync(ILoggingService loggingService)
+ {
+ if (_exitPacketState == ExitPacketState.ExitPacketQueued)
+ {
+ // Wait up to 100ms until all remaining packets are sent.
+ // We don't need to wait long, just long enough for the Task to start running on the ThreadPool.
+ await Task.WhenAny(_packetWriteDrainTask, Task.Delay(100));
+ }
+ if (_exitPacketState == ExitPacketState.ExitPacketSent)
+ {
+ CommunicationsUtilities.Trace("Waiting for node with pid = {0} to exit", _process.Id);
+
+ // .NET 5 introduces a real WaitForExitAsyc.
+ // This is a poor man's implementation that uses polling.
+ int timeout = TimeoutForWaitForExit;
+ int delay = 5;
+ while (timeout > 0)
+ {
+ bool exited = _process.WaitForExit(milliseconds: 0);
+ if (exited)
+ {
+ return;
+ }
+ timeout -= delay;
+ await Task.Delay(delay).ConfigureAwait(false);
+
+ // Double delay up to 500ms.
+ delay = Math.Min(delay * 2, 500);
+ }
+ }
+
+ // Kill the child and do a blocking wait.
+ loggingService?.LogWarning(
+ BuildEventContext.Invalid,
+ null,
+ BuildEventFileInfo.Empty,
+ "KillingProcessWithPid",
+ _process.Id);
+ CommunicationsUtilities.Trace("Killing node with pid = {0}", _process.Id);
+
+ _process.KillTree(timeout: 5000);
+ }
+
#if FEATURE_APM
///
/// Completes the asynchronous packet write to the node.
@@ -873,17 +967,16 @@ private bool ProcessHeaderBytesRead(int bytesRead)
{
if (bytesRead != _headerByte.Length)
{
- CommunicationsUtilities.Trace(_nodeId, "COMMUNICATIONS ERROR (HRC) Node: {0} Process: {1} Bytes Read: {2} Expected: {3}", _nodeId, _processId, bytesRead, _headerByte.Length);
+ CommunicationsUtilities.Trace(_nodeId, "COMMUNICATIONS ERROR (HRC) Node: {0} Process: {1} Bytes Read: {2} Expected: {3}", _nodeId, _process.Id, bytesRead, _headerByte.Length);
try
{
- Process childProcess = Process.GetProcessById(_processId);
- if (childProcess?.HasExited != false)
+ if (_process.HasExited)
{
- CommunicationsUtilities.Trace(_nodeId, " Child Process {0} has exited.", _processId);
+ CommunicationsUtilities.Trace(_nodeId, " Child Process {0} has exited.", _process.Id);
}
else
{
- CommunicationsUtilities.Trace(_nodeId, " Child Process {0} is still running.", _processId);
+ CommunicationsUtilities.Trace(_nodeId, " Child Process {0} is still running.", _process.Id);
}
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
diff --git a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs
index 9a329a707b5..7bf9a69aebc 100644
--- a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs
+++ b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs
@@ -24,6 +24,7 @@
using Microsoft.Build.Utilities;
using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
+using Task = System.Threading.Tasks.Task;
namespace Microsoft.Build.BackEnd
{
diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj
index 6989f8cc24d..d71f127c265 100644
--- a/src/Build/Microsoft.Build.csproj
+++ b/src/Build/Microsoft.Build.csproj
@@ -127,6 +127,7 @@
+
BackEnd\Components\RequestBuilder\IntrinsicTasks\TaskLoggingHelper.cs
True
diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx
index 5b737e6d5f0..9946e751fed 100644
--- a/src/Build/Resources/Strings.resx
+++ b/src/Build/Resources/Strings.resx
@@ -1876,4 +1876,7 @@ Utilization: {0} Average Utilization: {1:###.0}
MSB4270: No project cache plugins found in assembly "{0}". Expected one.
+
+ Killing process with pid = {0}.
+
diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf
index 157443afc16..2153f0e22a1 100644
--- a/src/Build/Resources/xlf/Strings.cs.xlf
+++ b/src/Build/Resources/xlf/Strings.cs.xlf
@@ -132,6 +132,11 @@
Objekty EvaluationContext vytvořené pomocí SharingPolicy.Isolated nepodporují předávání souborového systému MSBuildFileSystemBase.
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf
index 895d6839645..d864f0a20f2 100644
--- a/src/Build/Resources/xlf/Strings.de.xlf
+++ b/src/Build/Resources/xlf/Strings.de.xlf
@@ -132,6 +132,11 @@
"Die Übergabe eines MSBuildFileSystemBase-Dateisystems wird von EvaluationContext-Objekten, die mit SharingPolicy.Isolated erstellt wurden, nicht unterstützt."
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf
index 444ca3a5542..1254d414831 100644
--- a/src/Build/Resources/xlf/Strings.en.xlf
+++ b/src/Build/Resources/xlf/Strings.en.xlf
@@ -132,6 +132,11 @@
EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system.
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf
index 36a2729aa8b..99441e2fb4f 100644
--- a/src/Build/Resources/xlf/Strings.es.xlf
+++ b/src/Build/Resources/xlf/Strings.es.xlf
@@ -132,6 +132,11 @@
"Los objetos EvaluationContext creados con SharingPolicy.Isolated no admiten que se les pase un sistema de archivos MSBuildFileSystemBase".
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf
index 26c0b149b57..a19de6cd10b 100644
--- a/src/Build/Resources/xlf/Strings.fr.xlf
+++ b/src/Build/Resources/xlf/Strings.fr.xlf
@@ -132,6 +132,11 @@
"Les objets EvaluationContext créés avec SharingPolicy.Isolated ne prennent pas en charge le passage d'un système de fichiers MSBuildFileSystemBase."
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf
index 86a00974a5f..eddf911a5ef 100644
--- a/src/Build/Resources/xlf/Strings.it.xlf
+++ b/src/Build/Resources/xlf/Strings.it.xlf
@@ -132,6 +132,11 @@
"Agli oggetti EvaluationContext creati con SharingPolicy.Isolated non è possibile passare un file system MSBuildFileSystemBase."
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf
index 27c3d3f6130..ee8252b9dcd 100644
--- a/src/Build/Resources/xlf/Strings.ja.xlf
+++ b/src/Build/Resources/xlf/Strings.ja.xlf
@@ -132,6 +132,11 @@
"SharingPolicy.Isolated を指定して作成された EvaluationContext オブジェクトに MSBuildFileSystemBase ファイル システムを渡すことはサポートされていません。"
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf
index 367cf54895e..14e55b6f38f 100644
--- a/src/Build/Resources/xlf/Strings.ko.xlf
+++ b/src/Build/Resources/xlf/Strings.ko.xlf
@@ -132,6 +132,11 @@
"SharingPolicy.Isolated로 만든 EvaluationContext 개체는 MSBuildFileSystemBase 파일 시스템 전달을 지원하지 않습니다."
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf
index b13eb7e424f..4b42a435adb 100644
--- a/src/Build/Resources/xlf/Strings.pl.xlf
+++ b/src/Build/Resources/xlf/Strings.pl.xlf
@@ -132,6 +132,11 @@
„Obiekty EvaluationContext utworzone za pomocą elementu SharingPolicy.Isolated nie obsługują przekazywania za pomocą systemu plików MSBuildFileSystemBase.”
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf
index 188df5f9cf3..edf8687b3cf 100644
--- a/src/Build/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf
@@ -132,6 +132,11 @@
"Os objetos EvaluationContext criados com SharingPolicy.Isolable não são compatíveis com o recebimento de um sistema de arquivos MSBuildFileSystemBase."
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf
index 508d9a08b28..5e51ed494ea 100644
--- a/src/Build/Resources/xlf/Strings.ru.xlf
+++ b/src/Build/Resources/xlf/Strings.ru.xlf
@@ -132,6 +132,11 @@
"Объекты EvaluationContext, созданные с помощью SharingPolicy.Isolated, не поддерживают передачу в файловую систему MSBuildFileSystemBase."
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf
index 9bcd9bde59b..9e3d3c7c50c 100644
--- a/src/Build/Resources/xlf/Strings.tr.xlf
+++ b/src/Build/Resources/xlf/Strings.tr.xlf
@@ -132,6 +132,11 @@
"SharingPolicy.Isolated ile oluşturulan EvaluationContext nesneleri bir MSBuildFileSystemBase dosya sisteminin geçirilmesini desteklemez."
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
index 942ffe8d692..9a69ab3846e 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
@@ -132,6 +132,11 @@
“使用 SharingPolicy.Isolated 创建的 EvaluationContext 对象不支持通过 MSBuildFileSystemBase 文件系统传递。”
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
index eadd2f2e6cc..e6a19c0a9c3 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
@@ -132,6 +132,11 @@
"使用 SharingPolicy.Isolated 建立的 EvaluationContext 物件不支援以 MSBuildFileSystemBase 檔案系統傳遞。"
+
+
+ Killing process with pid = {0}.
+
+
diff --git a/src/Shared/NativeMethodsShared.cs b/src/Shared/NativeMethodsShared.cs
index 4818d7eda9c..42e8a3ead07 100644
--- a/src/Shared/NativeMethodsShared.cs
+++ b/src/Shared/NativeMethodsShared.cs
@@ -465,10 +465,8 @@ public SystemInformationData()
string arch = null;
if (proc != null)
{
- // Since uname -m simply returns kernel property, it should be quick.
- // 1 second is the best guess for a safe timeout.
- proc.WaitForExit(1000);
arch = proc.StandardOutput.ReadLine();
+ proc.WaitForExit();
}
if (!string.IsNullOrEmpty(arch))
diff --git a/src/Utilities/ProcessExtensions.cs b/src/Shared/ProcessExtensions.cs
similarity index 78%
rename from src/Utilities/ProcessExtensions.cs
rename to src/Shared/ProcessExtensions.cs
index 04d6afb3f36..9504440d124 100644
--- a/src/Utilities/ProcessExtensions.cs
+++ b/src/Shared/ProcessExtensions.cs
@@ -7,7 +7,7 @@
using System.IO;
using Microsoft.Build.Shared;
-namespace Microsoft.Build.Utilities
+namespace Microsoft.Build.Shared
{
internal static class ProcessExtensions
{
@@ -46,12 +46,12 @@ public static void KillTree(this Process process, int timeout)
private static void GetAllChildIdsUnix(int parentId, ISet children)
{
- var exitCode = RunProcessAndWaitForExit(
+ RunProcessAndWaitForExit(
"pgrep",
$"-P {parentId}",
out string stdout);
- if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
+ if (!string.IsNullOrEmpty(stdout))
{
using (var reader = new StringReader(stdout))
{
@@ -77,13 +77,24 @@ private static void GetAllChildIdsUnix(int parentId, ISet children)
private static void KillProcessUnix(int processId)
{
- RunProcessAndWaitForExit(
- "kill",
- $"-TERM {processId}",
- out string _);
+ try
+ {
+ using Process process = Process.GetProcessById(processId);
+ process.Kill();
+ }
+ catch (ArgumentException)
+ {
+ // Process already terminated.
+ return;
+ }
+ catch (InvalidOperationException)
+ {
+ // Process already terminated.
+ return;
+ }
}
- private static int RunProcessAndWaitForExit(string fileName, string arguments, out string stdout)
+ private static void RunProcessAndWaitForExit(string fileName, string arguments, out string stdout)
{
var startInfo = new ProcessStartInfo
{
@@ -94,22 +105,8 @@ private static int RunProcessAndWaitForExit(string fileName, string arguments, o
};
var process = Process.Start(startInfo);
-
- stdout = null;
- if (process.WaitForExit((int) TimeSpan.FromSeconds(30).TotalMilliseconds))
- {
- stdout = process.StandardOutput.ReadToEnd();
- }
- else
- {
- process.Kill();
-
- // Kill is asynchronous so we should still wait a little
- //
- process.WaitForExit((int) TimeSpan.FromSeconds(1).TotalMilliseconds);
- }
-
- return process.HasExited ? process.ExitCode : -1;
+ stdout = process.StandardOutput.ReadToEnd();
+ process.WaitForExit();
}
}
}
diff --git a/src/Tasks.UnitTests/Exec_Tests.cs b/src/Tasks.UnitTests/Exec_Tests.cs
index df8ac22edce..3bc0657cab2 100644
--- a/src/Tasks.UnitTests/Exec_Tests.cs
+++ b/src/Tasks.UnitTests/Exec_Tests.cs
@@ -103,8 +103,8 @@ public void ExitCodeCausesFailure()
[Fact]
public void Timeout()
{
- // On non-Windows the exit code of a killed process is SIGTERM (143)
- int expectedExitCode = NativeMethodsShared.IsWindows ? -1 : 143;
+ // On non-Windows the exit code of a killed process is SIGKILL (137)
+ int expectedExitCode = NativeMethodsShared.IsWindows ? -1 : 137;
Exec exec = PrepareExec(NativeMethodsShared.IsWindows ? ":foo \n goto foo" : "while true; do sleep 1; done");
exec.Timeout = 5;
@@ -122,7 +122,6 @@ public void Timeout()
[Fact]
public void TimeoutFailsEvenWhenExitCodeIsIgnored()
{
-
Exec exec = PrepareExec(NativeMethodsShared.IsWindows ? ":foo \n goto foo" : "while true; do sleep 1; done");
exec.Timeout = 5;
exec.IgnoreExitCode = true;
@@ -138,16 +137,13 @@ public void TimeoutFailsEvenWhenExitCodeIsIgnored()
if (NativeMethodsShared.IsMono)
{
- // The standard check for SIGTERM fails intermittently on macOS Mono
- // https://github.com/dotnet/msbuild/issues/5506
- // To avoid test flakiness, allow 259 even though I can't justify it.
- exec.ExitCode.ShouldBeOneOf(143, 259);
+ const int STILL_ACTIVE = 259; // When Process.WaitForExit times out.
+ exec.ExitCode.ShouldBeOneOf(137, STILL_ACTIVE);
}
else
{
- // On non-Windows the exit code of a killed process is generally 128 + SIGTERM = 143
- // though this isn't 100% guaranteed, see https://unix.stackexchange.com/a/99134
- exec.ExitCode.ShouldBe(NativeMethodsShared.IsWindows ? -1 : 143);
+ // On non-Windows the exit code of a killed process is 128 + SIGKILL = 137
+ exec.ExitCode.ShouldBe(NativeMethodsShared.IsWindows ? -1 : 137);
}
}
diff --git a/src/Utilities.UnitTests/ProcessExtensions_Tests.cs b/src/Utilities.UnitTests/ProcessExtensions_Tests.cs
new file mode 100644
index 00000000000..e24dca74ec4
--- /dev/null
+++ b/src/Utilities.UnitTests/ProcessExtensions_Tests.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Shouldly;
+using Xunit;
+
+using Microsoft.Build.Shared;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace Microsoft.Build.UnitTests
+{
+ public class ProcessExtensions_Tests
+ {
+ [Fact]
+ public async Task KillTree()
+ {
+ Process p = Process.Start("sleep", "600"); // sleep 10m.
+
+ // Verify the process is running.
+ await Task.Delay(500);
+ p.HasExited.ShouldBe(false);
+
+ // Kill the process.
+ p.KillTree(timeout: 5000);
+ p.HasExited.ShouldBe(true);
+ p.ExitCode.ShouldNotBe(0);
+ }
+ }
+}
diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj
index 2fdd06afdd6..f42d087cc9f 100644
--- a/src/Utilities/Microsoft.Build.Utilities.csproj
+++ b/src/Utilities/Microsoft.Build.Utilities.csproj
@@ -125,6 +125,9 @@
Shared\InprocTrackingNativeMethods.cs
+
+ Shared\ProcessExtensions.cs
+
Shared\ReadOnlyEmptyCollection.cs
diff --git a/src/Utilities/ToolTask.cs b/src/Utilities/ToolTask.cs
index 23f7abc7e67..5ccd30763e2 100644
--- a/src/Utilities/ToolTask.cs
+++ b/src/Utilities/ToolTask.cs
@@ -949,7 +949,6 @@ private void KillToolProcessOnTimeout(Process proc, bool isBeingCancelled)
timeout = result;
}
}
-
proc.KillTree(timeout);
}
}