110            string workingDirectory,
 
  112            CancellationToken cancellationToken,
 
  113            IReadOnlyDictionary<string, string>? environment,
 
  114            string? fileRedirect,
 
  115            bool readStandardHandles,
 
  118            ArgumentNullException.ThrowIfNull(fileName);
 
  119            ArgumentNullException.ThrowIfNull(workingDirectory);
 
  120            ArgumentNullException.ThrowIfNull(arguments);
 
  122            var enviromentLogLines = environment == 
null 
  124                : String.Concat(environment.Select(kvp => $
"{Environment.NewLine}\t- {kvp.Key}={kvp.Value}"));
 
  127                "Launching process in {workingDirectory}: {exe} {arguments}{environment}",
 
  134                "Shell launching process in {workingDirectory}: {exe} {arguments}{environment}",
 
  140            var handle = 
new global::System.Diagnostics.Process();
 
  143                handle.StartInfo.FileName = fileName;
 
  144                handle.StartInfo.Arguments = arguments;
 
  145                if (environment != 
null)
 
  146                    foreach (var kvp 
in environment)
 
  147                        handle.StartInfo.Environment.Add(kvp!);
 
  149                handle.StartInfo.WorkingDirectory = workingDirectory;
 
  151                handle.StartInfo.UseShellExecute = !noShellExecute;
 
  153                Task<string?>? readTask = 
null;
 
  154                CancellationTokenSource? disposeCts = 
null;
 
  157                    TaskCompletionSource<int>? processStartTcs = 
null;
 
  158                    if (readStandardHandles)
 
  160                        processStartTcs = 
new TaskCompletionSource<int>();
 
  161                        disposeCts = 
new CancellationTokenSource();
 
  162                        readTask = 
ConsumeReaders(handle, processStartTcs.Task, fileRedirect, disposeCts.Token);
 
  188                        processStartTcs?.SetResult(pid);
 
  192                        processStartTcs?.SetException(ex);
 
  208                    disposeCts?.Dispose();
 
 
  248        async Task<string?> 
ConsumeReaders(global::System.Diagnostics.Process handle, Task<int> startupAndPid, 
string? fileRedirect, CancellationToken cancellationToken)
 
  250            handle.StartInfo.RedirectStandardOutput = 
true;
 
  251            handle.StartInfo.RedirectStandardError = 
true;
 
  255            await 
using var fileWriter = fileStream != 
null ? 
new StreamWriter(fileStream) : 
null;
 
  257            var stringBuilder = fileStream == 
null ? 
new StringBuilder() : 
null;
 
  259            var dataChannel = Channel.CreateUnbounded<
string>(
 
  260                new UnboundedChannelOptions
 
  262                    AllowSynchronousContinuations = !writingToFile,
 
  264                    SingleWriter = 
false,
 
  268            async 
void DataReceivedHandler(
object sender, DataReceivedEventArgs eventArgs)
 
  270                var line = eventArgs.Data;
 
  273                    var handlesRemaining = Interlocked.Decrement(ref handlesOpen);
 
  274                    if (handlesRemaining == 0)
 
  275                        dataChannel.Writer.Complete();
 
  282                    await dataChannel.Writer.WriteAsync(line, cancellationToken);
 
  284                catch (OperationCanceledException ex)
 
  286                    logger.LogWarning(ex, 
"Handle channel write interrupted!");
 
  290            handle.OutputDataReceived += DataReceivedHandler;
 
  291            handle.ErrorDataReceived += DataReceivedHandler;
 
  293            async ValueTask OutputWriter()
 
  295                var enumerable = dataChannel.Reader.ReadAllAsync(cancellationToken);
 
  298                    var enumerator = enumerable.GetAsyncEnumerator(cancellationToken);
 
  299                    var nextEnumeration = enumerator.MoveNextAsync();
 
  300                    while (await nextEnumeration)
 
  302                        var text = enumerator.Current;
 
  303                        nextEnumeration = enumerator.MoveNextAsync();
 
  304                        await fileWriter!.WriteLineAsync(text.AsMemory(), cancellationToken);
 
  306                        if (!nextEnumeration.IsCompleted)
 
  307                            await fileWriter.FlushAsync(cancellationToken);
 
  311                    await 
foreach (var text 
in enumerable)
 
  312                        stringBuilder!.AppendLine(text);
 
  315            var pid = await startupAndPid;
 
  316            logger.LogTrace(
"Starting read for PID {pid}...", pid);
 
  318            using (cancellationToken.Register(() => dataChannel.Writer.TryComplete()))
 
  320                handle.BeginOutputReadLine();
 
  321                using (cancellationToken.Register(handle.CancelOutputRead))
 
  323                    handle.BeginErrorReadLine();
 
  324                    using (cancellationToken.Register(handle.CancelErrorRead))
 
  328                            await OutputWriter();
 
  330                            logger.LogTrace(
"Finished read for PID {pid}", pid);
 
  332                        catch (OperationCanceledException ex)
 
  334                            logger.LogWarning(ex, 
"PID {pid} stream reading interrupted!", pid);
 
  336                                await fileWriter!.WriteLineAsync(
"-- Process detached, log truncated. This is likely due a to TGS restart --");
 
  342            return stringBuilder?.ToString();