@@ -5,10 +5,12 @@ namespace VirtualClient
55{
66 using System ;
77 using System . Collections . Generic ;
8+ using System . Diagnostics ;
89 using System . Diagnostics . CodeAnalysis ;
910 using System . IO ;
1011 using System . Linq ;
1112 using System . Text . RegularExpressions ;
13+ using System . Threading ;
1214 using Microsoft . Extensions . Logging ;
1315 using VirtualClient . Common ;
1416 using VirtualClient . Common . Extensions ;
@@ -86,18 +88,19 @@ public static bool IsErrored(this IProcessProxy process, IEnumerable<int> succes
8688 }
8789
8890 /// <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.
9193 /// </summary>
9294 /// <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>
9396 /// <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 )
9598 {
9699 if ( process != null )
97100 {
98101 try
99102 {
100- process . Kill ( ) ;
103+ process . Kill ( entireProcessTree ) ;
101104 }
102105 catch ( Exception exc )
103106 {
@@ -108,26 +111,46 @@ public static void SafeKill(this IProcessProxy process, ILogger logger = null)
108111 }
109112
110113 /// <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.
113117 /// </summary>
114118 /// <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>
116121 /// <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 )
118123 {
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 ++ )
120130 {
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+
121137 try
122138 {
123139 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 ( ) ;
124143 }
125- catch ( Exception exc )
144+ catch ( Exception ex )
126145 {
127- // Best effort here.
128- logger ? . LogTraceMessage ( $ "Kill Process Failure. Error = { exc . Message } ") ;
146+ logger ? . LogTraceMessage ( $ "Attempt { attempt } : Kill failed. { ex . Message } ") ;
129147 }
130148 }
149+
150+ if ( ! process . HasExited )
151+ {
152+ logger ? . LogError ( "Failed to kill process after retries." ) ;
153+ }
131154 }
132155
133156 /// <summary>
@@ -137,7 +160,7 @@ public static void SafeKill(this IProcessProxy process, bool entireProcessTree,
137160 /// <param name="processManager">The process manager used to find the processes.</param>
138161 /// <param name="processNames">The names/paths of the processes to kill.</param>
139162 /// <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 )
141164 {
142165 processManager . ThrowIfNull ( nameof ( processManager ) ) ;
143166
@@ -155,7 +178,7 @@ public static void SafeKill(this ProcessManager processManager, IEnumerable<stri
155178 {
156179 foreach ( IProcessProxy sideProcess in processes )
157180 {
158- sideProcess . SafeKill ( logger ) ;
181+ sideProcess . Kill ( logger ) ;
159182 }
160183 }
161184 }
0 commit comments