forked from modelcontextprotocol/csharp-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProcessHelper.cs
More file actions
130 lines (116 loc) · 4.52 KB
/
ProcessHelper.cs
File metadata and controls
130 lines (116 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ModelContextProtocol;
/// <summary>
/// Helper class for working with processes.
/// </summary>
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>
/// Kills a process and all of its child processes (entire process tree) with a specified timeout.
/// </summary>
/// <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>
/// <remarks>
/// 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.
/// 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.
/// </remarks>
public static void KillTree(this Process process, TimeSpan timeout)
{
var pid = process.Id;
if (_isWindows)
{
RunProcessAndWaitForExit(
"taskkill",
$"/T /F /PID {pid}",
timeout,
out var _);
}
else
{
var children = new HashSet<int>();
GetAllChildIdsUnix(pid, children, timeout);
foreach (var childId in children)
{
KillProcessUnix(childId, timeout);
}
KillProcessUnix(pid, timeout);
}
// 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
// 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.
process.WaitForExit((int)timeout.TotalMilliseconds);
}
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
{
var exitcode = RunProcessAndWaitForExit(
"pgrep",
$"-P {parentId}",
timeout,
out var stdout);
if (exitcode == 0 && !string.IsNullOrEmpty(stdout))
{
using var reader = new StringReader(stdout);
while (true)
{
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);
}
}
}
}
private static void KillProcessUnix(int processId, TimeSpan timeout)
{
RunProcessAndWaitForExit(
"kill",
$"-TERM {processId}",
timeout,
out var _);
}
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string? stdout)
{
var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
stdout = null;
var process = Process.Start(startInfo);
if (process == null)
return -1;
if (process.WaitForExit((int)timeout.TotalMilliseconds))
{
stdout = process.StandardOutput.ReadToEnd();
}
else
{
process.Kill();
}
return process.ExitCode;
}
}