0

I have this class which runs a process:

public static async Task<ProcessResult> ExecuteShellCommand(string command, string arguments="", int timeout=1000, bool insertWait=false)
{
    var result = new ProcessResult();

    using (var process = new Process())
    {
        process.StartInfo.FileName = command;
        process.StartInfo.Arguments = arguments;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.CreateNoWindow = true;

        var outputBuilder = new StringBuilder();
        var outputCloseEvent = new TaskCompletionSource<bool>();

        process.OutputDataReceived += (s, e) =>
        {
            // The output stream has been closed i.e. the process has terminated
            if (e.Data == null)
            {
                outputCloseEvent.SetResult(true);
            }
            else
            {
                outputBuilder.AppendLine(e.Data);
            }
        };

        var errorBuilder = new StringBuilder();
        var errorCloseEvent = new TaskCompletionSource<bool>();

        process.ErrorDataReceived += (s, e) =>
        {
            // The error stream has been closed i.e. the process has terminated
            if (e.Data == null)
            {
                errorCloseEvent.SetResult(true);
            }
            else
            {
                errorBuilder.AppendLine(e.Data);
            }
        };

        bool isStarted;

        try
        {
            process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
            process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
            isStarted = process.Start(); 
            StreamReader reader = process.StandardOutput;
            string output = reader.ReadToEnd();
            result.Output = output;
        }
        catch (Exception error)
        {
            // Usually it occurs when an executable file is not found or is not executable

            result.Completed = true;
            result.ExitCode = -1;
            result.Output = error.Message;

            isStarted = false;
        }

        if (isStarted)
        {
            // Reads the output stream first and then waits because deadlocks are possible
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            if (insertWait)
            {
                await Task.Delay(150000);
            }

            // Creates task to wait for process exit using timeout
            var waitForExit = WaitForExitAsync(process, timeout);

            // Create task to wait for process exit and closing all output streams
            var processTask = Task.WhenAll(waitForExit, outputCloseEvent.Task, errorCloseEvent.Task);

            // Waits process completion and then checks it was not completed by timeout
            if (await Task.WhenAny(Task.Delay(timeout), processTask) == processTask && waitForExit.Result)
            {
                result.Completed = true;
                result.ExitCode = process.ExitCode;

                // Adds process output if it was completed with error
                if (process.ExitCode != 0)
                {
                    result.Output = $"{outputBuilder}{errorBuilder}";
                }
            }
            else
            {
                try
                {
                    // Kill hung process
                    process.Kill();
                }
                catch
                {
                }
            }
        }
    }

    return result;
}

This line calls the ExecuteShellCommand method:

var result = TestHelper.ExecuteShellCommand(MessageInjectorOptions.MessageInjectorFilename, MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition, 300000, true);

My logging shows that this is the command that gets run:

C:\Program Files\Java\jre1.8.0_281\bin\java.exe -jar C:\Users\Administrator\Downloads\test_tool\Jar\Injector\Injector-1.0.jar FILE TOPIC 2

This should push messages contained in FILE to a Kafka topic but the messages don't appear on the topic so I assume the jar doesn't run. If I copy and paste the command to a dos terminal and run it I can see the messages on the topic.

Is there anything wrong with my code that might cause Process to not run correctly?

runnerpaul
  • 5,942
  • 8
  • 49
  • 118
  • 1
    I think if you redirect both StandardInput and StandardOutput you have to [open at least one asynchronously to avoid deadlocks](https://stackoverflow.com/a/1044189/22437). If you're not using StandardInput you shouldn't be redirecting it. – Dour High Arch May 11 '21 at 17:33
  • From https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.redirectstandardoutput?view=net-5.0: *A deadlock condition can result if the parent process calls `p.WaitForExit` before `p.StandardOutput.ReadToEnd` and the child process writes enough text to fill the redirected stream.* – dbc May 11 '21 at 19:01

0 Answers0