| | | 1 | | using System.Diagnostics; |
| | | 2 | | |
| | | 3 | | namespace Anichron.Worker.Ingestion.Proxy; |
| | | 4 | | |
| | | 5 | | internal interface IProcessLauncher |
| | | 6 | | { |
| | | 7 | | Task<ProcessResult> RunAsync(string fileName, IReadOnlyList<string> arguments, CancellationToken ct); |
| | | 8 | | } |
| | | 9 | | |
| | | 10 | | internal sealed record ProcessResult(int ExitCode, string StandardError); |
| | | 11 | | |
| | | 12 | | internal sealed class SystemProcessLauncher : IProcessLauncher |
| | | 13 | | { |
| | | 14 | | public async Task<ProcessResult> RunAsync(string fileName, IReadOnlyList<string> arguments, CancellationToken ct) |
| | 0 | 15 | | { |
| | 0 | 16 | | using var process = new Process(); |
| | 0 | 17 | | process.StartInfo = new ProcessStartInfo |
| | 0 | 18 | | { |
| | 0 | 19 | | FileName = fileName, |
| | 0 | 20 | | RedirectStandardOutput = true, |
| | 0 | 21 | | RedirectStandardError = true, |
| | 0 | 22 | | UseShellExecute = false, |
| | 0 | 23 | | CreateNoWindow = true, |
| | 0 | 24 | | }; |
| | 0 | 25 | | foreach (var arg in arguments) |
| | 0 | 26 | | process.StartInfo.ArgumentList.Add(arg); |
| | | 27 | | |
| | 0 | 28 | | process.Start(); |
| | | 29 | | // Start draining pipes immediately — must be consumed to prevent the child process |
| | | 30 | | // from blocking on a full pipe buffer. Cancellation is signalled only via WaitForExitAsync. |
| | 0 | 31 | | var stdoutTask = process.StandardOutput.ReadToEndAsync(CancellationToken.None); |
| | 0 | 32 | | var stderrTask = process.StandardError.ReadToEndAsync(CancellationToken.None); |
| | | 33 | | try |
| | 0 | 34 | | { |
| | 0 | 35 | | await process.WaitForExitAsync(ct); |
| | 0 | 36 | | await stdoutTask; |
| | 0 | 37 | | var standardError = await stderrTask; |
| | 0 | 38 | | return new ProcessResult(process.ExitCode, standardError); |
| | | 39 | | } |
| | 0 | 40 | | catch (OperationCanceledException) |
| | 0 | 41 | | { |
| | | 42 | | // Kill the process tree so GPU encoder children (e.g. vaenc) are not orphaned. |
| | 0 | 43 | | process.Kill(entireProcessTree: true); |
| | | 44 | | // Drain fully before returning — the killed process closes its pipes immediately. |
| | 0 | 45 | | await Task.WhenAll(stdoutTask, stderrTask); |
| | 0 | 46 | | throw; |
| | | 47 | | } |
| | 0 | 48 | | } |
| | | 49 | | } |