Skip to content

Commit 92787fb

Browse files
committed
fix(tools): kill child process on timeout
1 parent 30aa4a8 commit 92787fb

File tree

3 files changed

+131
-3
lines changed

3 files changed

+131
-3
lines changed

source/Nuke.Tooling/IProcess.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,16 @@ public interface IProcess : IDisposable
5757
void Kill();
5858

5959
/// <summary>
60-
/// Waits for the process to exit. If the process is not exiting within a given timeout, <see cref="Kill"/> is called.
60+
/// Calls platform specific code to kill entire process tree.
61+
/// </summary>
62+
/// <remarks>
63+
/// For Windows it call "taskkill /T /F /PID {process.Id}"
64+
/// For Unix it call combination of "pgrep and kill commands"
65+
/// </remarks>
66+
void KillTree();
67+
68+
/// <summary>
69+
/// Waits for the process to exit. If the process is not exiting within a given timeout, <see cref="KillTree"/> is called.
6170
/// </summary>
6271
/// <returns>
6372
/// Returns <c>true</c>, if the process exited on its own.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2023 Maintainers of NUKE.
2+
// Distributed under the MIT License.
3+
// https://github.com/nuke-build/nuke/blob/master/LICENSE
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Diagnostics;
8+
using System.IO;
9+
using System.Runtime.InteropServices;
10+
11+
namespace Nuke.Common.Tooling;
12+
13+
// https://raw.githubusercontent.com/dotnet/cli/master/test/Microsoft.DotNet.Tools.Tests.Utilities/Extensions/ProcessExtensions.cs
14+
internal static class NativeProcessExtension
15+
{
16+
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
17+
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
18+
19+
internal static void KillTree(this Process process)
20+
{
21+
process.KillTree(_defaultTimeout);
22+
}
23+
24+
internal static void KillTree(this Process process, TimeSpan timeout)
25+
{
26+
string stdout;
27+
if (_isWindows)
28+
{
29+
RunProcessAndWaitForExit(
30+
"taskkill",
31+
$"/T /F /PID {process.Id}",
32+
timeout,
33+
out stdout);
34+
}
35+
else
36+
{
37+
var children = new HashSet<int>();
38+
GetAllChildIdsUnix(process.Id, children, timeout);
39+
foreach (var childId in children)
40+
{
41+
KillProcessUnix(childId, timeout);
42+
}
43+
KillProcessUnix(process.Id, timeout);
44+
}
45+
}
46+
47+
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
48+
{
49+
string stdout;
50+
var exitCode = RunProcessAndWaitForExit(
51+
"pgrep",
52+
$"-P {parentId}",
53+
timeout,
54+
out stdout);
55+
56+
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
57+
{
58+
using (var reader = new StringReader(stdout))
59+
{
60+
while (true)
61+
{
62+
var text = reader.ReadLine();
63+
if (text == null)
64+
{
65+
return;
66+
}
67+
68+
int id;
69+
if (int.TryParse(text, out id))
70+
{
71+
children.Add(id);
72+
// Recursively get the children
73+
GetAllChildIdsUnix(id, children, timeout);
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
private static void KillProcessUnix(int processId, TimeSpan timeout)
81+
{
82+
string stdout;
83+
RunProcessAndWaitForExit(
84+
"kill",
85+
$"-TERM {processId}",
86+
timeout,
87+
out stdout);
88+
}
89+
90+
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
91+
{
92+
var startInfo = new ProcessStartInfo
93+
{
94+
FileName = fileName,
95+
Arguments = arguments,
96+
RedirectStandardOutput = true,
97+
UseShellExecute = false
98+
};
99+
100+
var process = Process.Start(startInfo);
101+
102+
stdout = null;
103+
if (process.WaitForExit((int)timeout.TotalMilliseconds))
104+
{
105+
stdout = process.StandardOutput.ReadToEnd();
106+
}
107+
else
108+
{
109+
process.Kill();
110+
}
111+
112+
return process.ExitCode;
113+
}
114+
}

source/Nuke.Tooling/Process2.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ public void Dispose()
4444

4545
public void Kill()
4646
{
47-
_process.Kill();
47+
_process.Kill();
48+
}
49+
50+
public void KillTree()
51+
{
52+
_process.KillTree();
4853
}
4954

5055
public bool WaitForExit()
@@ -53,7 +58,7 @@ public bool WaitForExit()
5358
// use _process.StartTime
5459
var hasExited = _process.WaitForExit(_timeout ?? -1);
5560
if (!hasExited)
56-
_process.Kill();
61+
_process.KillTree();
5762
return hasExited;
5863
}
5964
}

0 commit comments

Comments
 (0)