I learned a new thing in C#, so it’s time to write a (small) blog again. This time it is about starting other programs.
Starting an executable
Sometimes, you need to start an external executable on your server. This can be a PowerShell script, some Python code, or an .exe file. Let’s not dwell on the security implications, lets assume that we know what we are doing. Usually in C#, you can use
System.Diagnostics.Process for starting and monitoring processes. An example:
var psi = new ProcessStartInfo
{
FileName = "D:/apps/scripts/StartUpdate.ps1",
Argument = "--force --silent",
WorkingDirectory = "D:/apps/scripts/",
UseShellExecute = false,
RedirectStandardOutput = true
}
var process = Process.Start(psi);
var exitCode = 0;
if (process != null)
{
await process.WaitForExitCode();
}
exitCode = process.ExitCode;
The point of this piece of code is to start a script and wait until it is done. No fire-and-forget, because we might need to know the exit code, log the run time or process the results.
Having this running in production, we would sometimes come up with the situation that the program above never finishes. It keeps on waiting in the
process.WaitForExitCode() statement. This was strange, because running the process itself one a local machine with all the same parameters would work flawless every time. Adding some logging did not pinpoint on a specific spot in the process, except that it always happened when an
async statement was hit.
Reading the documentation on
RedirectStandardOutput showed a hint halfway through:
The example avoids a deadlock condition by calling p.StandardOutput.ReadToEnd before p.WaitForExit. 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. The parent process would wait indefinitely for the child process to exit. The child process would wait indefinitely for the parent to read from the full StandardOutput stream.Microsoft Learn
So, to solve the deadlock problem, we need to add one extra line:
// ...
if (process != null)
{
_ = await process.StandardOutput.ReadToEndAsync();
await process.WaitForExitCode();
}
// ...
And now we don’t have any more deadlocks.