@@ -5,10 +5,12 @@ namespace VirtualClient
5
5
{
6
6
using System ;
7
7
using System . Collections . Generic ;
8
+ using System . Diagnostics ;
8
9
using System . Diagnostics . CodeAnalysis ;
9
10
using System . IO ;
10
11
using System . Linq ;
11
12
using System . Text . RegularExpressions ;
13
+ using System . Threading ;
12
14
using Microsoft . Extensions . Logging ;
13
15
using VirtualClient . Common ;
14
16
using VirtualClient . Common . Extensions ;
@@ -86,18 +88,19 @@ public static bool IsErrored(this IProcessProxy process, IEnumerable<int> succes
86
88
}
87
89
88
90
/// <summary>
89
- /// Kills the process if it is still running and handles any errors that
90
- /// can occurs if the process has gone out of scope.
91
+ /// Kills the associated process and/or it's child/dependent processes if it is still running and
92
+ /// handles any errors that can occurs if the process has gone out of scope.
91
93
/// </summary>
92
94
/// <param name="process">The process to kill.</param>
95
+ /// <param name="entireProcessTree">true to kill asociated process and it's descendents, false to only kill the process.</param>
93
96
/// <param name="logger">The logger to use to write trace information.</param>
94
- public static void SafeKill ( this IProcessProxy process , ILogger logger = null )
97
+ public static void SafeKill ( this IProcessProxy process , ILogger logger = null , bool entireProcessTree = false )
95
98
{
96
99
if ( process != null )
97
100
{
98
101
try
99
102
{
100
- process . Kill ( ) ;
103
+ process . Kill ( entireProcessTree ) ;
101
104
}
102
105
catch ( Exception exc )
103
106
{
@@ -108,26 +111,46 @@ public static void SafeKill(this IProcessProxy process, ILogger logger = null)
108
111
}
109
112
110
113
/// <summary>
111
- /// Kills the associated process and it's child/dependent processes if it is still running and
112
- /// handles any errors that can occurs if the process has gone out of scope.
114
+ /// Kills the associated process and/or its child/dependent processes if it is still running.
115
+ /// Retries up to 5 times using exponential backoff with a total wait limit of 3 minutes.
116
+ /// Logs error when failed to kill.
113
117
/// </summary>
114
118
/// <param name="process">The process to kill.</param>
115
- /// <param name="entireProcessTree">true to kill asociated process and it's descendents, false to only kill the process.</param>
119
+ /// <param name="entireProcessTree">true to kill associated process and its descendants; false to only kill the process.</param>
120
+ /// <param name="timeout">Max duration to wait for exit, default to 3 minutes.</param>
116
121
/// <param name="logger">The logger to use to write trace information.</param>
117
- public static void SafeKill ( this IProcessProxy process , bool entireProcessTree , ILogger logger = null )
122
+ public static void Kill ( this IProcessProxy process , ILogger logger = null , bool entireProcessTree = false , TimeSpan timeout = default )
118
123
{
119
- if ( process != null )
124
+ timeout = timeout == default ? TimeSpan . FromMinutes ( 3 ) : timeout ;
125
+ const int maxRetries = 5 ;
126
+
127
+ Stopwatch stopWatch = Stopwatch . StartNew ( ) ;
128
+
129
+ for ( int attempt = 1 ; attempt <= maxRetries ; attempt ++ )
120
130
{
131
+ int delaySeconds = Math . Min ( ( int ) Math . Pow ( 2 , attempt ) , ( int ) ( timeout - stopWatch . Elapsed ) . TotalSeconds ) ;
132
+ if ( process . HasExited || delaySeconds <= 0 )
133
+ {
134
+ break ; // Exit if timeout is reached or process exited.
135
+ }
136
+
121
137
try
122
138
{
123
139
process . Kill ( entireProcessTree ) ;
140
+ var cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( delaySeconds ) ) ;
141
+ // CancellationToken should cancel first, the timeout here is precaution.
142
+ process . WaitForExitAsync ( cts . Token , timeout ) . GetAwaiter ( ) . GetResult ( ) ;
124
143
}
125
- catch ( Exception exc )
144
+ catch ( Exception ex )
126
145
{
127
- // Best effort here.
128
- logger ? . LogTraceMessage ( $ "Kill Process Failure. Error = { exc . Message } ") ;
146
+ logger ? . LogTraceMessage ( $ "Attempt { attempt } : Kill failed. { ex . Message } ") ;
129
147
}
130
148
}
149
+
150
+ if ( ! process . HasExited )
151
+ {
152
+ logger ? . LogError ( "Failed to kill process after retries." ) ;
153
+ }
131
154
}
132
155
133
156
/// <summary>
@@ -137,7 +160,7 @@ public static void SafeKill(this IProcessProxy process, bool entireProcessTree,
137
160
/// <param name="processManager">The process manager used to find the processes.</param>
138
161
/// <param name="processNames">The names/paths of the processes to kill.</param>
139
162
/// <param name="logger">The logger to use to write trace information.</param>
140
- public static void SafeKill ( this ProcessManager processManager , IEnumerable < string > processNames , ILogger logger = null )
163
+ public static void Kill ( this ProcessManager processManager , IEnumerable < string > processNames , ILogger logger = null )
141
164
{
142
165
processManager . ThrowIfNull ( nameof ( processManager ) ) ;
143
166
@@ -155,7 +178,7 @@ public static void SafeKill(this ProcessManager processManager, IEnumerable<stri
155
178
{
156
179
foreach ( IProcessProxy sideProcess in processes )
157
180
{
158
- sideProcess . SafeKill ( logger ) ;
181
+ sideProcess . Kill ( logger ) ;
159
182
}
160
183
}
161
184
}
0 commit comments