private static async Task <List <string> > runDockerCommandWithOutputAsync(string args, int timeout = 10000, CancellationToken cancellationToken = default) { var command = CommandInfo.DockerCommand(args); var processPayload = new ProcessPayload(); var final = new List <string>(); var processTask = OperationSystemService.RunCommandAsync(command, processPayload, tempLogDir(), null, log: false, outputReceived: line => final.Add(line), cancellationToken: cancellationToken); await Task.WhenAny(processTask, Task.Delay(timeout, cancellationToken)); processPayload.Kill(); return(final.ToListThreadSafe()); }
public static async Task RunCommandAsync( CommandInfo commandInfo, ProcessPayload processPayload, DirectoryInfo logDir, string workingDirectory, bool log = true, Action <string> outputReceived = null, Action <string> errorReceived = null, Action exited = null, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); var process = new Process { StartInfo = { CreateNoWindow = true, RedirectStandardError = true, RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false, WorkingDirectory = workingDirectory, FileName = commandInfo.FileName, Arguments = commandInfo.Args } }; var logCommandInfoSemaphore = new SemaphoreSlim(1, 1); async Task <(string stdOut, string stdErr)> logFilePathAsync() { var commandInfoFilePath = Path.Combine(logDir.FullName, "command.info"); await logCommandInfoSemaphore.WaitAsync(cancellationToken); try { if (!File.Exists(commandInfoFilePath)) { await File.WriteAllTextAsync(commandInfoFilePath, JsonConvert.SerializeObject(commandInfo, Formatting.Indented), cancellationToken); } } finally { logCommandInfoSemaphore.Release(); } var stdOutFile = Path.Combine(logDir.FullName, "out.txt"); var stdErrFile = Path.Combine(logDir.FullName, "err.txt"); return(stdOutFile, stdErrFile); } #region EventListeners outputReceived = outputReceived ?? (x => { }); errorReceived = errorReceived ?? (x => { }); exited = exited ?? (() => { }); var processEndLocks = new HashSet <object>(); process.OutputDataReceived += async(sender, data) => { var endLock = new object(); processEndLocks.AddThreadSafe(endLock); try { if (data.Data.IsNullOrWhiteSpace().Not()) { await writeCommandLogAsync((await logFilePathAsync()).stdOut, $"{DateTime.Now:s}: {data.Data}", log, cancellationToken); outputReceived(data.Data); } } finally { processEndLocks.RemoveThreadSafe(endLock); } }; process.ErrorDataReceived += async(sender, data) => { var endLock = new object(); processEndLocks.AddThreadSafe(endLock); try { if (data.Data.IsNullOrWhiteSpace().Not()) { await writeCommandLogAsync((await logFilePathAsync()).stdErr, $"{DateTime.Now:s}: {data.Data}", log, cancellationToken); errorReceived(data.Data); } } finally { processEndLocks.RemoveThreadSafe(endLock); } }; process.Exited += async(_, __) => { var endLock = new object(); processEndLocks.AddThreadSafe(endLock); try { await writeCommandLogAsync((await logFilePathAsync()).stdOut, $"{DateTime.Now:s}: exited.", log, cancellationToken); exited(); } finally { processEndLocks.RemoveThreadSafe(endLock); } }; #endregion cancellationToken.Register(() => { if (processPayload.IsRunning()) { processPayload.Kill(); } }); process.EnableRaisingEvents = true; _processes.AddThreadSafe(process); if (process.Start()) { processPayload.ProcessId = process.Id; await writeCommandLogAsync((await logFilePathAsync()).stdOut, $"{DateTime.Now:s}: started.", log, cancellationToken); process.BeginErrorReadLine(); process.BeginOutputReadLine(); } else { throw new Exception("could not start process"); } await process.WaitForExitAsync(cancellationToken); if (cancellationToken.IsCancellationRequested) { process.Kill(); } await Task.Run(async() => { while (processPayload.IsRunning()) { await Task.Delay(100, cancellationToken); } }, cancellationToken); await Task.Run(async() => { while (processEndLocks.ToList().Any()) { await Task.Delay(100, cancellationToken); } }, cancellationToken); _processes.RemoveThreadSafe(process); }