@@ -22,8 +22,7 @@ private sealed class ProcessState
2222 /// <summary>
2323 /// Launches a process.
2424 /// </summary>
25- /// <param name="isUserApplication">True if the process is a user application, false if it is a helper process (e.g. msbuild).</param>
26- public async Task < int > RunAsync ( ProcessSpec processSpec , IReporter reporter , bool isUserApplication , ProcessLaunchResult ? launchResult , CancellationToken processTerminationToken )
25+ public async Task < int > RunAsync ( ProcessSpec processSpec , IReporter reporter , ProcessLaunchResult ? launchResult , CancellationToken processTerminationToken )
2726 {
2827 var state = new ProcessState ( ) ;
2928 var stopwatch = new Stopwatch ( ) ;
@@ -90,12 +89,12 @@ public async Task<int> RunAsync(ProcessSpec processSpec, IReporter reporter, boo
9089 // Either Ctrl+C was pressed or the process is being restarted.
9190
9291 // Non-cancellable to not leave orphaned processes around blocking resources:
93- await TerminateProcessAsync ( process , state , reporter , CancellationToken . None ) ;
92+ await TerminateProcessAsync ( process , processSpec , state , reporter , CancellationToken . None ) ;
9493 }
9594 }
9695 catch ( Exception e )
9796 {
98- if ( isUserApplication )
97+ if ( processSpec . IsUserApplication )
9998 {
10099 reporter . Error ( $ "Application failed: { e . Message } ") ;
101100 }
@@ -117,7 +116,7 @@ public async Task<int> RunAsync(ProcessSpec processSpec, IReporter reporter, boo
117116
118117 reporter . Verbose ( $ "Process id { process . Id } ran for { stopwatch . ElapsedMilliseconds } ms and exited with exit code { exitCode } .") ;
119118
120- if ( isUserApplication )
119+ if ( processSpec . IsUserApplication )
121120 {
122121 if ( exitCode == 0 )
123122 {
@@ -154,6 +153,7 @@ private static Process CreateProcess(ProcessSpec processSpec, Action<OutputLine>
154153 WorkingDirectory = processSpec . WorkingDirectory ,
155154 RedirectStandardOutput = onOutput != null ,
156155 RedirectStandardError = onOutput != null ,
156+ CreateNewProcessGroup = processSpec . IsUserApplication && RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ,
157157 }
158158 } ;
159159
@@ -210,24 +210,20 @@ private static Process CreateProcess(ProcessSpec processSpec, Action<OutputLine>
210210 return process ;
211211 }
212212
213- private async ValueTask TerminateProcessAsync ( Process process , ProcessState state , IReporter reporter , CancellationToken cancellationToken )
213+ private async ValueTask TerminateProcessAsync ( Process process , ProcessSpec processSpec , ProcessState state , IReporter reporter , CancellationToken cancellationToken )
214214 {
215215 if ( ! shutdownCancellationToken . IsCancellationRequested )
216216 {
217- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
217+ var forceOnly = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) && ! processSpec . IsUserApplication ;
218+
219+ // Ctrl+C hasn't been sent.
220+ TerminateProcess ( process , state , reporter , forceOnly ) ;
221+
222+ if ( forceOnly )
218223 {
219- // Ctrl+C hasn't been sent, force termination.
220- // We don't have means to terminate gracefully on Windows (https://github.com/dotnet/runtime/issues/109432)
221- TerminateProcess ( process , state , reporter , force : true ) ;
222224 _ = await WaitForExitAsync ( process , state , timeout : null , reporter , cancellationToken ) ;
223-
224225 return ;
225226 }
226- else
227- {
228- // Ctrl+C hasn't been sent, send SIGTERM now:
229- TerminateProcess ( process , state , reporter , force : false ) ;
230- }
231227 }
232228
233229 // Ctlr+C/SIGTERM has been sent, wait for the process to exit gracefully.
@@ -327,40 +323,28 @@ private static void TerminateProcess(Process process, ProcessState state, IRepor
327323
328324 private static void TerminateWindowsProcess ( Process process , ProcessState state , IReporter reporter , bool force )
329325 {
330- // Needs API: https://github.com/dotnet/runtime/issues/109432
331- // Code below does not work because the process creation needs CREATE_NEW_PROCESS_GROUP flag.
332-
333- reporter . Verbose ( $ "Terminating process { state . ProcessId } .") ;
326+ reporter . Verbose ( $ "Terminating process { state . ProcessId } ({ ( force ? "Kill" : "Ctrl+C" ) } ).") ;
334327
335328 if ( force )
336329 {
337330 process . Kill ( ) ;
338331 }
339- #if TODO
340332 else
341333 {
342334 const uint CTRL_C_EVENT = 0 ;
343335
344336 [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
337+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
345338 static extern bool GenerateConsoleCtrlEvent ( uint dwCtrlEvent , uint dwProcessGroupId ) ;
346339
347- [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
348- static extern bool AttachConsole ( uint dwProcessId ) ;
349-
350- [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
351- static extern bool FreeConsole ( ) ;
352-
353- if ( AttachConsole ( ( uint ) state . ProcessId ) &&
354- GenerateConsoleCtrlEvent ( CTRL_C_EVENT , 0 ) &&
355- FreeConsole ( ) )
340+ if ( GenerateConsoleCtrlEvent ( CTRL_C_EVENT , 0 ) )
356341 {
357342 return ;
358343 }
359344
360345 var error = Marshal . GetLastPInvokeError ( ) ;
361346 reporter . Verbose ( $ "Failed to send Ctrl+C to process { state . ProcessId } : { Marshal . GetPInvokeErrorMessage ( error ) } (code { error } )") ;
362347 }
363- #endif
364348 }
365349
366350 private static void TerminateUnixProcess ( ProcessState state , IReporter reporter , bool force )
0 commit comments