tgstation-server 6.9.2
The /tg/station 13 server suite
Loading...
Searching...
No Matches
Process.cs
Go to the documentation of this file.
1using System;
2using System.Diagnostics;
3using System.Threading;
4using System.Threading.Tasks;
5
6using Microsoft.Extensions.Logging;
7using Microsoft.Win32.SafeHandles;
8
10
12{
14 sealed class Process : IProcess
15 {
17 public int Id { get; }
18
20 public Task Startup { get; }
21
23 public Task<int?> Lifetime { get; }
24
26 public long? MemoryUsage
27 {
28 get
29 {
30 try
31 {
32 return handle.PrivateMemorySize64;
33 }
34 catch (Exception ex)
35 {
36 logger.LogWarning(ex, "Failed to get PID {pid}'s memory usage!", Id);
37 return null;
38 }
39 }
40 }
41
46
50 readonly ILogger<Process> logger;
51
55 readonly global::System.Diagnostics.Process handle;
56
60 readonly CancellationTokenSource cancellationTokenSource;
61
66 readonly SafeProcessHandle safeHandle;
67
71 readonly Task<string?>? readTask;
72
76 volatile int disposed;
77
87 public Process(
89 global::System.Diagnostics.Process handle,
90 CancellationTokenSource? readerCts,
91 Task<string?>? readTask,
92 ILogger<Process> logger,
93 bool preExisting)
94 {
95 this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
96
97 // Do this fast because the runtime will bitch if we try to access it after it ends
98 safeHandle = handle.SafeHandle;
99 Id = handle.Id;
100
101 cancellationTokenSource = readerCts ?? new CancellationTokenSource();
102
103 this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
104
105 this.readTask = readTask;
106
107 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
108
110
111 if (preExisting)
112 {
113 Startup = Task.CompletedTask;
114 return;
115 }
116
117 Startup = Task.Factory.StartNew(
118 () =>
119 {
120 try
121 {
122 handle.WaitForInputIdle();
123 }
124 catch (Exception ex)
125 {
126 logger.LogTrace(ex, "WaitForInputIdle() failed, this is normal.");
127 }
128 },
129 CancellationToken.None, // DCT: None available
131 TaskScheduler.Current);
132
133 logger.LogTrace("Created process ID: {pid}", Id);
134 }
135
137 public async ValueTask DisposeAsync()
138 {
139 if (Interlocked.Exchange(ref disposed, 1) != 0)
140 return;
141
142 logger.LogTrace("Disposing PID {pid}...", Id);
144 cancellationTokenSource.Dispose();
145 if (readTask != null)
146 await readTask;
147
148 await Lifetime;
149
150 safeHandle.Dispose();
151 handle.Dispose();
152 }
153
155 public Task<string?> GetCombinedOutput(CancellationToken cancellationToken)
156 {
157 if (readTask == null)
158 throw new InvalidOperationException("Output/Error stream reading was not enabled!");
159
160 return readTask.WaitAsync(cancellationToken);
161 }
162
164 public void Terminate()
165 {
167 if (handle.HasExited)
168 {
169 logger.LogTrace("PID {pid} already exited", Id);
170 return;
171 }
172
173 try
174 {
175 logger.LogTrace("Terminating PID {pid}...", Id);
176 handle.Kill();
177 if (!handle.WaitForExit(5000))
178 logger.LogWarning("WaitForExit() on PID {pid} timed out!", Id);
179 }
180 catch (Exception e)
181 {
182 logger.LogDebug(e, "PID {pid} termination exception!", Id);
183 }
184 }
185
187 public void AdjustPriority(bool higher)
188 {
190 var targetPriority = higher ? ProcessPriorityClass.AboveNormal : ProcessPriorityClass.BelowNormal;
191 try
192 {
193 handle.PriorityClass = targetPriority;
194 logger.LogTrace("Set PID {pid} to {targetPriority} priority", Id, targetPriority);
195 }
196 catch (Exception ex)
197 {
198 logger.LogWarning(ex, "Unable to set priority for PID {id} to {targetPriority}!", Id, targetPriority);
199 }
200 }
201
203 public void SuspendProcess()
204 {
206 try
207 {
209 logger.LogTrace("Suspended PID {pid}", Id);
210 }
211 catch (Exception e)
212 {
213 logger.LogError(e, "Failed to suspend PID {pid}!", Id);
214 throw;
215 }
216 }
217
219 public void ResumeProcess()
220 {
222 try
223 {
225 logger.LogTrace("Resumed PID {pid}", Id);
226 }
227 catch (Exception e)
228 {
229 logger.LogError(e, "Failed to resume PID {pid}!", Id);
230 throw;
231 }
232 }
233
235 public string GetExecutingUsername()
236 {
239 logger.LogTrace("PID {pid} Username: {username}", Id, result);
240 return result;
241 }
242
244 public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
245 {
246 ArgumentNullException.ThrowIfNull(outputFile);
248
249 logger.LogTrace("Dumping PID {pid} to {dumpFilePath}...", Id, outputFile);
250 return processFeatures.CreateDump(handle, outputFile, minidump, cancellationToken);
251 }
252
257 async Task<int?> WrapLifetimeTask()
258 {
259 bool hasExited;
260 try
261 {
262 await handle.WaitForExitAsync(cancellationTokenSource.Token);
263 hasExited = true;
264 }
265 catch (OperationCanceledException ex)
266 {
267 logger.LogTrace(ex, "Process lifetime task cancelled!");
268 hasExited = handle.HasExited;
269 }
270
271 if (!hasExited)
272 return null;
273
274 var exitCode = handle.ExitCode;
275 logger.LogTrace("PID {pid} exited with code {exitCode}", Id, exitCode);
276 return exitCode;
277 }
278
282 void CheckDisposed() => ObjectDisposedException.ThrowIf(disposed != 0, this);
283 }
284}
IIOManager that resolves paths to Environment.CurrentDirectory.
const TaskCreationOptions BlockingTaskCreationOptions
The TaskCreationOptions used to spawn Tasks for potentially long running, blocking operations.
Process(IProcessFeatures processFeatures, global::System.Diagnostics.Process handle, CancellationTokenSource? readerCts, Task< string?>? readTask, ILogger< Process > logger, bool preExisting)
Initializes a new instance of the Process class.
Definition Process.cs:87
void CheckDisposed()
Throws an ObjectDisposedException if a method of the Process was called after DisposeAsync.
readonly global::System.Diagnostics.Process handle
The global::System.Diagnostics.Process object.
Definition Process.cs:55
Task< int?> Lifetime
The Task<TResult> resulting in the exit code of the process or null if the process was detached.
Definition Process.cs:23
Task< string?> GetCombinedOutput(CancellationToken cancellationToken)
Get the stderr and stdout output of the IProcess.A Task<TResult> resulting in the stderr and stdout o...
Definition Process.cs:155
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
Definition Process.cs:187
ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
Create a dump file of the process.A ValueTask representing the running operation.
Definition Process.cs:244
volatile int disposed
If the Process was disposed.
Definition Process.cs:76
void Terminate()
Asycnhronously terminates the process.To ensure the IProcess has ended, use the IProcessBase....
Definition Process.cs:164
readonly ILogger< Process > logger
The ILogger for the Process.
Definition Process.cs:50
void SuspendProcess()
Suspends the process.
Definition Process.cs:203
readonly CancellationTokenSource cancellationTokenSource
The CancellationTokenSource used to shutdown the readTask and Lifetime.
Definition Process.cs:60
string GetExecutingUsername()
Get the name of the account executing the IProcess.The name of the account executing the IProcess.
Definition Process.cs:235
void ResumeProcess()
Resumes the process.
Definition Process.cs:219
readonly SafeProcessHandle safeHandle
The global::System.Diagnostics.Process.SafeHandle.
Definition Process.cs:66
readonly IProcessFeatures processFeatures
The IProcessFeatures for the Process.
Definition Process.cs:45
Task Startup
The Task representing the time until the IProcess becomes "idle".
Definition Process.cs:20
long? MemoryUsage
Gets the process' memory usage in bytes.
Definition Process.cs:27
readonly? Task< string?> readTask
The Task<TResult> resulting in the process' standard output/error text.
Definition Process.cs:71
async Task< int?> WrapLifetimeTask()
Attaches a log message to the process' exit event.
Definition Process.cs:257
Abstraction for suspending and resuming processes.
string GetExecutingUsername(global::System.Diagnostics.Process process)
Get the name of the user executing a given process .
ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken)
Create a dump file for a given process .
void SuspendProcess(global::System.Diagnostics.Process process)
Suspend a given process .
void ResumeProcess(global::System.Diagnostics.Process process)
Resume a given suspended global::System.Diagnostics.Process.
Abstraction over a global::System.Diagnostics.Process.
Definition IProcess.cs:11