@@ -8,36 +8,25 @@ namespace ModelContextProtocol;
88/// </summary>
99internal 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