@@ -51,6 +51,8 @@ public sealed class ProcessInvoker : AgentService, IProcessInvoker
51
51
private readonly TaskCompletionSource < bool > _processExitedCompletionSource = new TaskCompletionSource < bool > ( ) ;
52
52
private readonly ConcurrentQueue < string > _errorData = new ConcurrentQueue < string > ( ) ;
53
53
private readonly ConcurrentQueue < string > _outputData = new ConcurrentQueue < string > ( ) ;
54
+ private readonly TimeSpan _sigintTimeout = TimeSpan . FromSeconds ( 10 ) ;
55
+ private readonly TimeSpan _sigtermTimeout = TimeSpan . FromSeconds ( 5 ) ;
54
56
55
57
public event EventHandler < ProcessDataReceivedEventArgs > OutputDataReceived ;
56
58
public event EventHandler < ProcessDataReceivedEventArgs > ErrorDataReceived ;
@@ -234,11 +236,40 @@ private void ProcessOutput()
234
236
private async Task CancelAndKillProcessTree ( )
235
237
{
236
238
ArgUtil . NotNull ( _proc , nameof ( _proc ) ) ;
239
+ bool sigint_succeed = await SendSIGINT ( _sigintTimeout ) ;
240
+ if ( sigint_succeed )
241
+ {
242
+ Trace . Info ( "Process cancelled successfully through Ctrl+C/SIGINT." ) ;
243
+ return ;
244
+ }
245
+
246
+ bool sigterm_succeed = await SendSIGTERM ( _sigtermTimeout ) ;
247
+ if ( sigterm_succeed )
248
+ {
249
+ Trace . Info ( "Process terminate successfully through Ctrl+Break/SIGTERM." ) ;
250
+ return ;
251
+ }
252
+
253
+ Trace . Info ( "Kill entire process tree since both cancel and terminate signal has been ignored by the target process." ) ;
254
+ KillProcessTree ( ) ;
255
+ }
256
+
257
+ private async Task < bool > SendSIGINT ( TimeSpan timeout )
258
+ {
237
259
#if OS_WINDOWS
238
- await WindowsCancelAndKillProcessTree ( ) ;
260
+ return await SendCtrlSignal ( ConsoleCtrlEvent . CTRL_C , timeout ) ;
239
261
#else
240
- await NixCancelAndKillProcessTree ( ) ;
241
- #endif
262
+ return await SendSignal ( Signals . SIGINT , timeout ) ;
263
+ #endif
264
+ }
265
+
266
+ private async Task < bool > SendSIGTERM ( TimeSpan timeout )
267
+ {
268
+ #if OS_WINDOWS
269
+ return await SendCtrlSignal ( ConsoleCtrlEvent . CTRL_BREAK , timeout ) ;
270
+ #else
271
+ return await SendSignal ( Signals . SIGTERM , timeout ) ;
272
+ #endif
242
273
}
243
274
244
275
private void ProcessExitedHandler ( object sender , EventArgs e )
@@ -293,26 +324,6 @@ private void KillProcessTree()
293
324
}
294
325
295
326
#if OS_WINDOWS
296
- private async Task WindowsCancelAndKillProcessTree ( )
297
- {
298
- bool ctrl_c_succeed = await SendCtrlSignal ( ConsoleCtrlEvent . CTRL_C , TimeSpan . FromSeconds ( 10 ) ) ;
299
- if ( ctrl_c_succeed )
300
- {
301
- Trace . Info ( "Process cancelled successfully through Ctrl+C." ) ;
302
- return ;
303
- }
304
-
305
- bool ctrl_break_succeed = await SendCtrlSignal ( ConsoleCtrlEvent . CTRL_BREAK , TimeSpan . FromSeconds ( 5 ) ) ;
306
- if ( ctrl_break_succeed )
307
- {
308
- Trace . Info ( "Process terminate successfully through Ctrl+Break." ) ;
309
- return ;
310
- }
311
-
312
- Trace . Info ( "Kill entire process tree since both cancel and terminate signal has been ignored by the target process." ) ;
313
- KillProcessTree ( ) ;
314
- }
315
-
316
327
private async Task < bool > SendCtrlSignal ( ConsoleCtrlEvent signal , TimeSpan timeout )
317
328
{
318
329
Trace . Info ( $ "Sending { signal } to process { _proc . Id } .") ;
@@ -545,31 +556,57 @@ public uint Size
545
556
// Delegate type to be used as the Handler Routine for SetConsoleCtrlHandler
546
557
private delegate Boolean ConsoleCtrlDelegate ( ConsoleCtrlEvent CtrlType ) ;
547
558
#else
548
- private async Task NixCancelAndKillProcessTree ( )
559
+ private async Task < bool > SendSignal ( Signals signal , TimeSpan timeout )
549
560
{
550
- // TODO: replace Task.Delay(1) with Send SIGINT/SIGTERM/SIGKILL
551
- await Task . Delay ( 1 ) ;
552
- KillProcessTree ( ) ;
561
+ Trace . Info ( $ "Sending { signal } to process { _proc . Id } .") ;
562
+ int errorCode = kill ( _proc . Id , ( int ) signal ) ;
563
+ if ( errorCode != 0 )
564
+ {
565
+ Trace . Info ( $ "{ signal } signal doesn't fire successfully.") ;
566
+ Trace . Error ( $ "Error code: { errorCode } .") ;
567
+ return false ;
568
+ }
569
+
570
+ Trace . Info ( $ "Successfully send { signal } to process { _proc . Id } .") ;
571
+ Trace . Info ( $ "Waiting for process exit or { timeout . TotalSeconds } seconds after { signal } signal fired.") ;
572
+ var completedTask = await Task . WhenAny ( Task . Delay ( timeout ) , _processExitedCompletionSource . Task ) ;
573
+ if ( completedTask == _processExitedCompletionSource . Task )
574
+ {
575
+ Trace . Info ( "Process exit successfully." ) ;
576
+ return true ;
577
+ }
578
+ else
579
+ {
580
+ Trace . Info ( $ "Process did not honor { signal } signal within { timeout . TotalSeconds } seconds.") ;
581
+ return false ;
582
+ }
553
583
}
554
584
555
585
private void NixKillProcessTree ( )
556
586
{
557
587
try
558
588
{
559
- // TODO: Send Ctrl+C/Break to process group.
560
589
if ( ! _proc . HasExited )
561
590
{
562
591
_proc . Kill ( ) ;
563
592
}
564
593
}
565
- catch ( InvalidOperationException )
594
+ catch ( InvalidOperationException ex )
566
595
{
567
- // InvalidOperationException can occur if process got terminated by itself between
568
- // HasExited and Kill() calls above.
596
+ Trace . Error ( "Ignore InvalidOperationException during Process.Kill()." ) ;
597
+ Trace . Error ( ex ) ;
569
598
}
570
599
}
571
- #endif
572
600
601
+ private enum Signals : int
602
+ {
603
+ SIGINT = 2 ,
604
+ SIGTERM = 15
605
+ }
606
+
607
+ [ DllImport ( "libc" , SetLastError = true ) ]
608
+ private static extern int kill ( int pid , int sig ) ;
609
+ #endif
573
610
}
574
611
575
612
public sealed class ProcessExitCodeException : Exception
0 commit comments