tgstation-server
The /tg/station 13 server suite
ProcessExecutor.cs
Go to the documentation of this file.
1 using Microsoft.Extensions.Logging;
2 using System;
3 using System.Text;
4 using System.Threading.Tasks;
5 
6 namespace Tgstation.Server.Host.Core
7 {
10  {
14  readonly ILogger<ProcessExecutor> logger;
15 
19  readonly ILoggerFactory loggerFactory;
20 
26  static Task<int> AttachExitHandler(System.Diagnostics.Process handle)
27  {
28  handle.EnableRaisingEvents = true;
29  var tcs = new TaskCompletionSource<int>();
30  handle.Exited += (a, b) =>
31  {
32  int exitCode;
33  try
34  {
35  exitCode = handle.ExitCode;
36  }
37  catch (InvalidOperationException)
38  {
39  return;
40  }
41  tcs.SetResult(exitCode);
42  };
43  return tcs.Task;
44  }
45 
51  public ProcessExecutor(ILogger<ProcessExecutor> logger, ILoggerFactory loggerFactory)
52  {
53  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
54  this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
55  }
56 
58  public IProcess GetProcess(int id)
59  {
60  logger.LogDebug("Attaching to process {0}...", id);
61  System.Diagnostics.Process handle;
62  try
63  {
64  handle = System.Diagnostics.Process.GetProcessById(id);
65  }
66  catch(Exception e)
67  {
68  logger.LogDebug("Unable to get process {0}! Exception: {1}", id, e);
69  return null;
70  }
71  try
72  {
73  return new Process(handle, AttachExitHandler(handle), null, null, null, loggerFactory.CreateLogger<Process>(), true);
74  }
75  catch
76  {
77  handle.Dispose();
78  throw;
79  }
80  }
81 
83  public IProcess LaunchProcess(string fileName, string workingDirectory, string arguments, bool readOutput, bool readError, bool noShellExecute)
84  {
85  logger.LogDebug("Launching process in {0}: {1} {2}", workingDirectory, fileName, arguments);
86  var handle = new System.Diagnostics.Process();
87  try
88  {
89  handle.StartInfo.FileName = fileName;
90  handle.StartInfo.Arguments = arguments;
91  handle.StartInfo.WorkingDirectory = workingDirectory;
92 
93  handle.StartInfo.UseShellExecute = !(noShellExecute || readOutput || readError);
94 
95  StringBuilder outputStringBuilder = null, errorStringBuilder = null, combinedStringBuilder = null;
96  if (readOutput || readError)
97  {
98  combinedStringBuilder = new StringBuilder();
99  if (readOutput)
100  {
101  outputStringBuilder = new StringBuilder();
102  handle.StartInfo.RedirectStandardOutput = true;
103  handle.OutputDataReceived += (sender, e) =>
104  {
105  combinedStringBuilder.Append(Environment.NewLine);
106  combinedStringBuilder.Append(e.Data);
107  outputStringBuilder.Append(Environment.NewLine);
108  outputStringBuilder.Append(e.Data);
109  };
110  }
111  if (readError)
112  {
113  errorStringBuilder = new StringBuilder();
114  handle.StartInfo.RedirectStandardError = true;
115  handle.ErrorDataReceived += (sender, e) =>
116  {
117  combinedStringBuilder.Append(Environment.NewLine);
118  combinedStringBuilder.Append(e.Data);
119  errorStringBuilder.Append(Environment.NewLine);
120  errorStringBuilder.Append(e.Data);
121  };
122  }
123  }
124 
125  var lifetimeTask = AttachExitHandler(handle);
126 
127  handle.Start();
128  try
129  {
130  if (readOutput)
131  handle.BeginOutputReadLine();
132  }
133  catch (InvalidOperationException) { }
134  try
135  {
136  if (readError)
137  handle.BeginErrorReadLine();
138  }
139  catch (InvalidOperationException) { }
140 
141  return new Process(handle, lifetimeTask, outputStringBuilder, errorStringBuilder, combinedStringBuilder, loggerFactory.CreateLogger<Process>(), false);
142  }
143  catch
144  {
145  handle.Dispose();
146  throw;
147  }
148  }
149  }
150 }
readonly ILoggerFactory loggerFactory
The ILoggerFactory for the ProcessExecutor
IProcess LaunchProcess(string fileName, string workingDirectory, string arguments, bool readOutput, bool readError, bool noShellExecute)
Launch a IProcess
IProcess GetProcess(int id)
Get a IProcess by id
static Task< int > AttachExitHandler(System.Diagnostics.Process handle)
Create a Task<TResult> resulting in the exit code of a given handle
Abstraction over a System.Diagnostics.Process
Definition: IProcess.cs:9
ProcessExecutor(ILogger< ProcessExecutor > logger, ILoggerFactory loggerFactory)
Construct a ProcessExecutor
readonly ILogger< ProcessExecutor > logger
The ILogger for the ProcessExecutor