Skip to content

Commit aaf2246

Browse files
authored
Use Process.Kill(entireProcessTree: true) on .NET for faster process termination (#1187)
1 parent ee8c485 commit aaf2246

File tree

1 file changed

+37
-46
lines changed

1 file changed

+37
-46
lines changed

src/ModelContextProtocol.Core/ProcessHelper.cs

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,25 @@ namespace ModelContextProtocol;
88
/// </summary>
99
internal static class ProcessHelper
1010
{
11-
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
12-
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
13-
14-
/// <summary>
15-
/// Kills a process and all of its child processes (entire process tree).
16-
/// </summary>
17-
/// <param name="process">The process to terminate along with its child processes.</param>
18-
/// <remarks>
19-
/// This method uses a default timeout of 30 seconds when waiting for processes to exit.
20-
/// On Windows, this uses the "taskkill" command with the /T flag.
21-
/// On non-Windows platforms, it recursively identifies and terminates child processes.
22-
/// </remarks>
23-
public static void KillTree(this Process process) => process.KillTree(_defaultTimeout);
24-
2511
/// <summary>
2612
/// Kills a process and all of its child processes (entire process tree) with a specified timeout.
2713
/// </summary>
2814
/// <param name="process">The process to terminate along with its child processes.</param>
2915
/// <param name="timeout">The maximum time to wait for the processes to exit.</param>
3016
/// <remarks>
31-
/// On Windows, this uses the "taskkill" command with the /T flag to terminate the process tree.
32-
/// On non-Windows platforms, it recursively identifies and terminates child processes.
17+
/// On .NET Core 3.0+ this uses <c>Process.Kill(entireProcessTree: true)</c>.
18+
/// On .NET Standard 2.0, it uses platform-specific commands (taskkill on Windows, pgrep/kill on Unix).
3319
/// The method waits for the specified timeout for processes to exit before continuing.
3420
/// This is particularly useful for applications that spawn child processes (like Node.js)
3521
/// that wouldn't be terminated automatically when the parent process exits.
3622
/// </remarks>
3723
public static void KillTree(this Process process, TimeSpan timeout)
3824
{
25+
#if NETSTANDARD2_0
26+
// Process.Kill(entireProcessTree) is not available on .NET Standard 2.0.
27+
// Use platform-specific commands to kill the process tree.
3928
var pid = process.Id;
40-
if (_isWindows)
29+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
4130
{
4231
RunProcessAndWaitForExit(
4332
"taskkill",
@@ -53,50 +42,50 @@ public static void KillTree(this Process process, TimeSpan timeout)
5342
{
5443
KillProcessUnix(childId, timeout);
5544
}
45+
5646
KillProcessUnix(pid, timeout);
5747
}
48+
#else
49+
try
50+
{
51+
process.Kill(entireProcessTree: true);
52+
}
53+
catch
54+
{
55+
// Process has already exited
56+
return;
57+
}
58+
#endif
5859

5960
// wait until the process finishes exiting/getting killed.
6061
// We don't want to wait forever here because the task is already supposed to be dieing, we just want to give it long enough
6162
// to try and flush what it can and stop. If it cannot do that in a reasonable time frame then we will just ignore it.
6263
process.WaitForExit((int)timeout.TotalMilliseconds);
6364
}
6465

66+
#if NETSTANDARD2_0
6567
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
6668
{
67-
var exitcode = RunProcessAndWaitForExit(
68-
"pgrep",
69-
$"-P {parentId}",
70-
timeout,
71-
out var stdout);
69+
int exitcode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
7270

7371
if (exitcode == 0 && !string.IsNullOrEmpty(stdout))
7472
{
7573
using var reader = new StringReader(stdout);
76-
while (true)
74+
while (reader.ReadLine() is string text)
7775
{
78-
var text = reader.ReadLine();
79-
if (text == null)
80-
return;
81-
8276
if (int.TryParse(text, out var id))
8377
{
8478
children.Add(id);
79+
8580
// Recursively get the children
8681
GetAllChildIdsUnix(id, children, timeout);
8782
}
8883
}
8984
}
9085
}
9186

92-
private static void KillProcessUnix(int processId, TimeSpan timeout)
93-
{
94-
RunProcessAndWaitForExit(
95-
"kill",
96-
$"-TERM {processId}",
97-
timeout,
98-
out var _);
99-
}
87+
private static void KillProcessUnix(int processId, TimeSpan timeout) =>
88+
RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
10089

10190
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string? stdout)
10291
{
@@ -112,19 +101,21 @@ private static int RunProcessAndWaitForExit(string fileName, string arguments, T
112101

113102
stdout = null;
114103

115-
var process = Process.Start(startInfo);
116-
if (process == null)
117-
return -1;
118-
119-
if (process.WaitForExit((int)timeout.TotalMilliseconds))
120-
{
121-
stdout = process.StandardOutput.ReadToEnd();
122-
}
123-
else
104+
if (Process.Start(startInfo) is { } process)
124105
{
125-
process.Kill();
106+
if (process.WaitForExit((int)timeout.TotalMilliseconds))
107+
{
108+
stdout = process.StandardOutput.ReadToEnd();
109+
}
110+
else
111+
{
112+
process.Kill();
113+
}
114+
115+
return process.ExitCode;
126116
}
127117

128-
return process.ExitCode;
118+
return -1;
129119
}
120+
#endif
130121
}

0 commit comments

Comments
 (0)