From 2fe3aef56b807d479ec097e5abc21cbbb2463bf0 Mon Sep 17 00:00:00 2001 From: Liri Sokol Date: Wed, 6 Oct 2021 14:26:15 +0300 Subject: [PATCH 1/3] Safer exit --- src/RunProcessAsTask/ProcessEx.cs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/RunProcessAsTask/ProcessEx.cs b/src/RunProcessAsTask/ProcessEx.cs index bcd90de..1fb1303 100644 --- a/src/RunProcessAsTask/ProcessEx.cs +++ b/src/RunProcessAsTask/ProcessEx.cs @@ -8,6 +8,8 @@ namespace RunProcessAsTask { public static partial class ProcessEx { + private static readonly TimeSpan _processExitGraceTime = TimeSpan.FromSeconds(30); + /// /// Runs asynchronous process. /// @@ -64,11 +66,25 @@ await standardErrorResults.Task.ConfigureAwait(false) using (cancellationToken.Register( () => { - tcs.TrySetCanceled(); - try { + try + { if (!process.HasExited) + { process.Kill(); - } catch (InvalidOperationException) { } + if (!process.WaitForExit(_processExitGraceTime.Milliseconds)) + { + throw new TimeoutException($"Timed out after {_processExitGraceTime.TotalSeconds:N2} seconds waiting for cancelled process to exit: {process}"); + } + } + tcs.TrySetCanceled(); + } + catch (InvalidOperationException) + { + } + catch (Exception exception) + { + tcs.SetException(new Exception($"Failed to kill process '{process}' upon cancellation", exception)); + } })) { cancellationToken.ThrowIfCancellationRequested(); From 5d913f3bcf4540a9a5253a3058e296cd382a4bf8 Mon Sep 17 00:00:00 2001 From: Liri Sokol Date: Wed, 13 Oct 2021 12:58:18 +0300 Subject: [PATCH 2/3] remove event handlers --- src/RunProcessAsTask/ProcessEx.cs | 33 +++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/RunProcessAsTask/ProcessEx.cs b/src/RunProcessAsTask/ProcessEx.cs index 1fb1303..4f94d4a 100644 --- a/src/RunProcessAsTask/ProcessEx.cs +++ b/src/RunProcessAsTask/ProcessEx.cs @@ -32,24 +32,33 @@ public static async Task RunAsync(ProcessStartInfo processStartI }; var standardOutputResults = new TaskCompletionSource(); - process.OutputDataReceived += (sender, args) => { + + void OutputDataReceived(object sender, DataReceivedEventArgs args) + { if (args.Data != null) standardOutput.Add(args.Data); else standardOutputResults.SetResult(standardOutput.ToArray()); - }; + } + + process.OutputDataReceived += OutputDataReceived; var standardErrorResults = new TaskCompletionSource(); - process.ErrorDataReceived += (sender, args) => { + + void ErrorDataReceived(object sender, DataReceivedEventArgs args) + { if (args.Data != null) standardError.Add(args.Data); else standardErrorResults.SetResult(standardError.ToArray()); - }; + } + + process.ErrorDataReceived += ErrorDataReceived; var processStartTime = new TaskCompletionSource(); - process.Exited += async (sender, args) => { + async void OnExited(object sender, EventArgs args) + { // Since the Exited event can happen asynchronously to the output and error events, // we await the task results for stdout/stderr to ensure they both closed. We must await // the stdout/stderr tasks instead of just accessing the Result property due to behavior on MacOS. @@ -62,7 +71,9 @@ await standardOutputResults.Task.ConfigureAwait(false), await standardErrorResults.Task.ConfigureAwait(false) ) ); - }; + } + + process.Exited += OnExited; using (cancellationToken.Register( () => { @@ -70,10 +81,16 @@ await standardErrorResults.Task.ConfigureAwait(false) { if (!process.HasExited) { + process.OutputDataReceived -= OutputDataReceived; + process.ErrorDataReceived -= ErrorDataReceived; + process.Exited -= OnExited; process.Kill(); if (!process.WaitForExit(_processExitGraceTime.Milliseconds)) { - throw new TimeoutException($"Timed out after {_processExitGraceTime.TotalSeconds:N2} seconds waiting for cancelled process to exit: {process}"); + if (!process.HasExited) + { + throw new TimeoutException($"Timed out after {_processExitGraceTime.TotalSeconds:N2} seconds waiting for cancelled process to exit: {process}"); + } } } tcs.TrySetCanceled(); @@ -83,7 +100,7 @@ await standardErrorResults.Task.ConfigureAwait(false) } catch (Exception exception) { - tcs.SetException(new Exception($"Failed to kill process '{process}' upon cancellation", exception)); + tcs.SetException(new Exception($"Failed to kill process '{process.StartInfo.FileName}' ({process.Id}) upon cancellation", exception)); } })) { cancellationToken.ThrowIfCancellationRequested(); From 76eb0635378f1676a812055f6ce0043fccbd3503 Mon Sep 17 00:00:00 2001 From: Liri Sokol Date: Thu, 4 Nov 2021 13:44:04 +0200 Subject: [PATCH 3/3] need to sleep --- src/RunProcessAsTask/ProcessEx.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/RunProcessAsTask/ProcessEx.cs b/src/RunProcessAsTask/ProcessEx.cs index 4f94d4a..664331b 100644 --- a/src/RunProcessAsTask/ProcessEx.cs +++ b/src/RunProcessAsTask/ProcessEx.cs @@ -76,7 +76,7 @@ await standardErrorResults.Task.ConfigureAwait(false) process.Exited += OnExited; using (cancellationToken.Register( - () => { + async () => { try { if (!process.HasExited) @@ -85,9 +85,10 @@ await standardErrorResults.Task.ConfigureAwait(false) process.ErrorDataReceived -= ErrorDataReceived; process.Exited -= OnExited; process.Kill(); - if (!process.WaitForExit(_processExitGraceTime.Milliseconds)) + await Task.Delay(TimeSpan.FromSeconds(1)); + if (!process.HasExited) { - if (!process.HasExited) + if (!process.WaitForExit(_processExitGraceTime.Milliseconds)) { throw new TimeoutException($"Timed out after {_processExitGraceTime.TotalSeconds:N2} seconds waiting for cancelled process to exit: {process}"); }