diff --git a/src/ModelContextProtocol.Core/ProcessHelper.cs b/src/ModelContextProtocol.Core/ProcessHelper.cs index c8bae0c48..a0e2498e6 100644 --- a/src/ModelContextProtocol.Core/ProcessHelper.cs +++ b/src/ModelContextProtocol.Core/ProcessHelper.cs @@ -8,36 +8,25 @@ namespace ModelContextProtocol; /// internal static class ProcessHelper { - private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30); - - /// - /// Kills a process and all of its child processes (entire process tree). - /// - /// The process to terminate along with its child processes. - /// - /// 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. - /// - public static void KillTree(this Process process) => process.KillTree(_defaultTimeout); - /// /// Kills a process and all of its child processes (entire process tree) with a specified timeout. /// /// The process to terminate along with its child processes. /// The maximum time to wait for the processes to exit. /// - /// On Windows, this uses the "taskkill" command with the /T flag to terminate the process tree. - /// On non-Windows platforms, it recursively identifies and terminates child processes. + /// On .NET Core 3.0+ this uses Process.Kill(entireProcessTree: true). + /// 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. /// This is particularly useful for applications that spawn child processes (like Node.js) /// that wouldn't be terminated automatically when the parent process exits. /// 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; - if (_isWindows) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { RunProcessAndWaitForExit( "taskkill", @@ -53,8 +42,20 @@ public static void KillTree(this Process process, TimeSpan timeout) { KillProcessUnix(childId, 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. // 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 @@ public static void KillTree(this Process process, TimeSpan timeout) process.WaitForExit((int)timeout.TotalMilliseconds); } +#if NETSTANDARD2_0 private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpan timeout) { - var exitcode = RunProcessAndWaitForExit( - "pgrep", - $"-P {parentId}", - timeout, - out var stdout); + int exitcode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout); if (exitcode == 0 && !string.IsNullOrEmpty(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)) { children.Add(id); + // Recursively get the children GetAllChildIdsUnix(id, children, timeout); } @@ -89,14 +84,8 @@ private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpa } } - private static void KillProcessUnix(int processId, TimeSpan timeout) - { - RunProcessAndWaitForExit( - "kill", - $"-TERM {processId}", - timeout, - out var _); - } + private static void KillProcessUnix(int processId, TimeSpan timeout) => + RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _); private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string? stdout) { @@ -112,19 +101,21 @@ private static int RunProcessAndWaitForExit(string fileName, string arguments, T stdout = null; - var process = Process.Start(startInfo); - if (process == null) - return -1; - - if (process.WaitForExit((int)timeout.TotalMilliseconds)) - { - stdout = process.StandardOutput.ReadToEnd(); - } - else + if (Process.Start(startInfo) is { } process) { - process.Kill(); + if (process.WaitForExit((int)timeout.TotalMilliseconds)) + { + stdout = process.StandardOutput.ReadToEnd(); + } + else + { + process.Kill(); + } + + return process.ExitCode; } - return process.ExitCode; + return -1; } +#endif }