Use Process.Kill(entireProcessTree: true) on .NET for faster process termination (#1187)
This commit is contained in:
parent
ee8c485d41
commit
aaf2246d53
1 changed files with 37 additions and 46 deletions
|
|
@ -8,36 +8,25 @@ namespace ModelContextProtocol;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class ProcessHelper
|
internal static class ProcessHelper
|
||||||
{
|
{
|
||||||
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
||||||
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kills a process and all of its child processes (entire process tree).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="process">The process to terminate along with its child processes.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method uses a default timeout of 30 seconds when waiting for processes to exit.
|
|
||||||
/// On Windows, this uses the "taskkill" command with the /T flag.
|
|
||||||
/// On non-Windows platforms, it recursively identifies and terminates child processes.
|
|
||||||
/// </remarks>
|
|
||||||
public static void KillTree(this Process process) => process.KillTree(_defaultTimeout);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kills a process and all of its child processes (entire process tree) with a specified timeout.
|
/// Kills a process and all of its child processes (entire process tree) with a specified timeout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="process">The process to terminate along with its child processes.</param>
|
/// <param name="process">The process to terminate along with its child processes.</param>
|
||||||
/// <param name="timeout">The maximum time to wait for the processes to exit.</param>
|
/// <param name="timeout">The maximum time to wait for the processes to exit.</param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// On Windows, this uses the "taskkill" command with the /T flag to terminate the process tree.
|
/// On .NET Core 3.0+ this uses <c>Process.Kill(entireProcessTree: true)</c>.
|
||||||
/// On non-Windows platforms, it recursively identifies and terminates child processes.
|
/// On .NET Standard 2.0, it uses platform-specific commands (taskkill on Windows, pgrep/kill on Unix).
|
||||||
/// The method waits for the specified timeout for processes to exit before continuing.
|
/// The method waits for the specified timeout for processes to exit before continuing.
|
||||||
/// This is particularly useful for applications that spawn child processes (like Node.js)
|
/// This is particularly useful for applications that spawn child processes (like Node.js)
|
||||||
/// that wouldn't be terminated automatically when the parent process exits.
|
/// that wouldn't be terminated automatically when the parent process exits.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static void KillTree(this Process process, TimeSpan timeout)
|
public static void KillTree(this Process process, TimeSpan timeout)
|
||||||
{
|
{
|
||||||
|
#if NETSTANDARD2_0
|
||||||
|
// Process.Kill(entireProcessTree) is not available on .NET Standard 2.0.
|
||||||
|
// Use platform-specific commands to kill the process tree.
|
||||||
var pid = process.Id;
|
var pid = process.Id;
|
||||||
if (_isWindows)
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
RunProcessAndWaitForExit(
|
RunProcessAndWaitForExit(
|
||||||
"taskkill",
|
"taskkill",
|
||||||
|
|
@ -53,8 +42,20 @@ internal static class ProcessHelper
|
||||||
{
|
{
|
||||||
KillProcessUnix(childId, timeout);
|
KillProcessUnix(childId, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
KillProcessUnix(pid, timeout);
|
KillProcessUnix(pid, timeout);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Kill(entireProcessTree: true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Process has already exited
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// wait until the process finishes exiting/getting killed.
|
// wait until the process finishes exiting/getting killed.
|
||||||
// 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
|
// 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
|
||||||
|
|
@ -62,26 +63,20 @@ internal static class ProcessHelper
|
||||||
process.WaitForExit((int)timeout.TotalMilliseconds);
|
process.WaitForExit((int)timeout.TotalMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NETSTANDARD2_0
|
||||||
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
|
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
|
||||||
{
|
{
|
||||||
var exitcode = RunProcessAndWaitForExit(
|
int exitcode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
|
||||||
"pgrep",
|
|
||||||
$"-P {parentId}",
|
|
||||||
timeout,
|
|
||||||
out var stdout);
|
|
||||||
|
|
||||||
if (exitcode == 0 && !string.IsNullOrEmpty(stdout))
|
if (exitcode == 0 && !string.IsNullOrEmpty(stdout))
|
||||||
{
|
{
|
||||||
using var reader = new StringReader(stdout);
|
using var reader = new StringReader(stdout);
|
||||||
while (true)
|
while (reader.ReadLine() is string text)
|
||||||
{
|
{
|
||||||
var text = reader.ReadLine();
|
|
||||||
if (text == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (int.TryParse(text, out var id))
|
if (int.TryParse(text, out var id))
|
||||||
{
|
{
|
||||||
children.Add(id);
|
children.Add(id);
|
||||||
|
|
||||||
// Recursively get the children
|
// Recursively get the children
|
||||||
GetAllChildIdsUnix(id, children, timeout);
|
GetAllChildIdsUnix(id, children, timeout);
|
||||||
}
|
}
|
||||||
|
|
@ -89,14 +84,8 @@ internal static class ProcessHelper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void KillProcessUnix(int processId, TimeSpan timeout)
|
private static void KillProcessUnix(int processId, TimeSpan timeout) =>
|
||||||
{
|
RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
|
||||||
RunProcessAndWaitForExit(
|
|
||||||
"kill",
|
|
||||||
$"-TERM {processId}",
|
|
||||||
timeout,
|
|
||||||
out var _);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string? stdout)
|
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string? stdout)
|
||||||
{
|
{
|
||||||
|
|
@ -112,19 +101,21 @@ internal static class ProcessHelper
|
||||||
|
|
||||||
stdout = null;
|
stdout = null;
|
||||||
|
|
||||||
var process = Process.Start(startInfo);
|
if (Process.Start(startInfo) is { } process)
|
||||||
if (process == null)
|
{
|
||||||
return -1;
|
if (process.WaitForExit((int)timeout.TotalMilliseconds))
|
||||||
|
{
|
||||||
|
stdout = process.StandardOutput.ReadToEnd();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
|
||||||
if (process.WaitForExit((int)timeout.TotalMilliseconds))
|
return process.ExitCode;
|
||||||
{
|
|
||||||
stdout = process.StandardOutput.ReadToEnd();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
process.Kill();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return process.ExitCode;
|
return -1;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue