Beispiel #1
0
 private async Task PipeStandardInputAsync(ProcessEx process, CancellationToken cancellationToken = default)
 {
     try
     {
         // Some streams do not support cancellation, so we add a fallback that
         // drops the task and returns early.
         // Doing so does leave the original piping task still alive, which is
         // unfortunate, but still better than having everything freeze up.
         // This is important with stdin because the process might finish before
         // the pipe completes, and in case with infinite input stream it would
         // normally result in a deadlock.
         await StandardInputPipe.CopyToAsync(process.StdIn, cancellationToken)
         .WithDangerousCancellation(cancellationToken);
     }
     catch (IOException)
     {
         // IOException: The pipe has been ended.
         // This may happen if the process terminated before the pipe could complete.
         // It's not an exceptional situation because the process may not need
         // the entire stdin to complete successfully.
     }
     finally
     {
         await process.StdIn.DisposeAsync();
     }
 }
Beispiel #2
0
        private async Task <CommandResult> ExecuteAsync(ProcessEx process, CancellationToken cancellationToken = default)
        {
            using var _ = process;

            process.Start();

            // Register cancellation
            using var cancellation = cancellationToken.Register(() => process.TryKill());

            // Handle stdin pipe
            using (process.StdIn)
                await StandardInputPipe.CopyToAsync(process.StdIn, cancellationToken);

            // Handle stdout/stderr pipes and wait for exit
            await Task.WhenAll(
                StandardOutputPipe.CopyFromAsync(process.StdOut, cancellationToken),
                StandardErrorPipe.CopyFromAsync(process.StdErr, cancellationToken),
                process.WaitUntilExitAsync());

            if (process.ExitCode != 0 && Validation.IsZeroExitCodeValidationEnabled())
            {
                throw CommandExecutionException.ExitCodeValidation(TargetFilePath, Arguments, process.ExitCode);
            }

            return(new CommandResult(process.ExitCode, process.StartTime, process.ExitTime));
        }
Beispiel #3
0
        private async Task <CommandResult> ExecuteAsync(ProcessEx process, CancellationToken cancellationToken = default)
        {
            using var _ = process;

            process.Start();

            // Register cancellation
            using var cancellation = cancellationToken.Register(() => process.TryKill());

            // Stdin must be closed after it finished to avoid deadlock if the process reads the stream to end
            async Task HandleStdInAsync()
            {
                using (process.StdIn)
                    await StandardInputPipe.CopyToAsync(process.StdIn, cancellationToken);
            }

            // Stdout doesn't need to be closed but we do it for good measure
            async Task HandleStdOutAsync()
            {
                using (process.StdOut)
                    await StandardOutputPipe.CopyFromAsync(process.StdOut, cancellationToken);
            }

            // Stderr doesn't need to be closed but we do it for good measure
            async Task HandleStdErrAsync()
            {
                using (process.StdErr)
                    await StandardErrorPipe.CopyFromAsync(process.StdErr, cancellationToken);
            }

            // Handle pipes in parallel to avoid deadlocks
            await Task.WhenAll(
                HandleStdInAsync(),
                HandleStdOutAsync(),
                HandleStdErrAsync(),
                process.WaitUntilExitAsync()
                );

            if (process.ExitCode != 0 && Validation.IsZeroExitCodeValidationEnabled())
            {
                throw CommandExecutionException.ExitCodeValidation(TargetFilePath, Arguments, process.ExitCode);
            }

            return(new CommandResult(process.ExitCode, process.StartTime, process.ExitTime));
        }
Beispiel #4
0
        private async Task <CommandResult> ExecuteAsync(ProcessEx process, CancellationToken cancellationToken = default)
        {
            using var _ = process;
            process.Start();

            // Stdin pipe may need to be canceled early if the process terminates before it finishes
            using var stdInCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

            // Register early process termination
            using var cancellation = cancellationToken.Register(() => process.TryKill());

            // Stdin must be closed after it finished to avoid deadlock if the process reads the stream to end
            async Task HandleStdInAsync()
            {
                try
                {
                    // Some streams don't support cancellation, in which case we need a fallback mechanism to avoid deadlocks.
                    // For example, WindowsConsoleStream (from Console.OpenStandardInput()) in particular doesn't support cancellation.
                    // In the following case the operation will terminate but a rogue Task will leak and might cause problems.
                    // This is a non-issue, however, if the user closes the stream at the earliest opportunity.
                    // Otherwise we enter an indeterminate state and tell ourselves we did everything we could to avoid it.
                    await Task.WhenAny(
                        StandardInputPipe.CopyToAsync(process.StdIn, stdInCts.Token),
                        Task.Delay(-1, stdInCts.Token)
                        );
                }
                // Ignore cancellation here, will propagate later
                catch (OperationCanceledException)
                {
                }
                // We want to ignore I/O exceptions that happen when the output stream has already closed.
                // This can happen when the process reads only a portion of stdin and then exits.
                // Unfortunately we can't catch a specific exception for this exact event so we have no choice but to catch all of them.
                catch (IOException)
                {
                }
                finally
                {
                    await process.StdIn.DisposeAsync();
                }
            }

            // Stdout doesn't need to be closed but we do it for good measure
            async Task HandleStdOutAsync()
            {
                try
                {
                    await StandardOutputPipe.CopyFromAsync(process.StdOut, cancellationToken);
                }
                // Ignore cancellation here, will propagate later
                catch (OperationCanceledException)
                {
                }
                finally
                {
                    await process.StdOut.DisposeAsync();
                }
            }

            // Stderr doesn't need to be closed but we do it for good measure
            async Task HandleStdErrAsync()
            {
                try
                {
                    await StandardErrorPipe.CopyFromAsync(process.StdErr, cancellationToken);
                }
                // Ignore cancellation here, will propagate later
                catch (OperationCanceledException)
                {
                }
                finally
                {
                    await process.StdErr.DisposeAsync();
                }
            }

            // Handle pipes in background and in parallel to avoid deadlocks
            var pipingTasks = new[]
            {
                HandleStdInAsync(),
                HandleStdOutAsync(),
                HandleStdErrAsync()
            };

            // Wait until the process terminates or gets killed
            await process.WaitUntilExitAsync();

            // Stop piping stdin if the process has already exited (can happen if not all of stdin is read)
            stdInCts.Cancel();

            // Ensure all pipes are finished
            await Task.WhenAll(pipingTasks);

            // Propagate cancellation to the user
            cancellationToken.ThrowIfCancellationRequested();

            // Validate exit code
            if (process.ExitCode != 0 && Validation.IsZeroExitCodeValidationEnabled())
            {
                throw CommandExecutionException.ExitCodeValidation(TargetFilePath, Arguments, process.ExitCode);
            }

            return(new CommandResult(process.ExitCode, process.StartTime, process.ExitTime));
        }