in src/common/IO/Platform.cs [75:215]
public int Execute(
string executable,
string command,
Action<string> stdOutCallback,
Action<string> stdErrCallback,
IDictionary<string, string> envVariables,
TimeSpan timeout,
CancellationToken cancellationToken,
out string stdOutOutput,
out string stdErrOutput)
{
cancellationToken.ThrowIfCancellationRequested();
ProcessStartInfo psi = new ProcessStartInfo()
{
FileName = executable,
Arguments = command,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var process = new ProcessEx(psi);
if (envVariables != null)
{
foreach (KeyValuePair<string, string> env in envVariables)
{
process.StartInfo.EnvironmentVariables[env.Key] = env.Value;
}
}
using (var outputWaitCountdown = new CountdownEvent(2)) // 2 for stdout and stderr
{
StringBuilder stdOutLines = new StringBuilder();
StringBuilder stdErrLines = new StringBuilder();
object stdOutLock = new object();
object stdErrLock = new object();
void outputHandler(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
lock (stdOutLock)
{
stdOutLines.AppendLine(e.Data);
}
stdOutCallback?.Invoke(e.Data);
}
else
{
try { process.OutputDataReceived -= outputHandler; } catch { }
try
{
// Output data has finished
outputWaitCountdown.Signal();
}
catch (ObjectDisposedException)
{ }
}
};
void errorHandler(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
lock (stdErrLock)
{
stdErrLines.AppendLine(e.Data);
}
stdErrCallback?.Invoke(e.Data);
}
else
{
try { process.ErrorDataReceived -= errorHandler; } catch { }
try
{
// Error data has finished
outputWaitCountdown.Signal();
}
catch (ObjectDisposedException)
{ }
}
}
process.OutputDataReceived += outputHandler;
process.ErrorDataReceived += errorHandler;
Action killProcess =
() =>
{
try
{
if (!process.HasExited)
{
process.Kill();
process.Dispose();
}
}
catch (Exception e)
{
stdErrLines.AppendLine(e.ToString());
}
};
int exitCode;
using (cancellationToken.Register(killProcess))
{
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
int timeoutMs = timeout.TotalMilliseconds > int.MaxValue ? int.MaxValue : (int)timeout.TotalMilliseconds;
exitCode = WaitForProcessExit(process, timeoutMs, stdOutCallback, stdErrCallback);
}
try
{
// Wait for all output to flush
// The process should already be exited at this point, so don't wait too long before giving up
if (!outputWaitCountdown.Wait(TimeSpan.FromSeconds(5)))
{
throw new IOException("Timed out waiting for process output to flush");
}
}
finally
{
lock (stdOutLock)
{
stdOutOutput = stdOutLines.ToString();
}
lock (stdErrLock)
{
stdErrOutput = stdErrLines.ToString();
}
process.Dispose();
}
return exitCode;
}
}