tgstation-server 6.17.0
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 DateTimeOffset? LaunchTime
21 {
22 get
23 {
24 try
25 {
26 return handle.StartTime;
27 }
28 catch (Exception ex)
29 {
30 logger.LogWarning(ex, "Failed to get PID {pid}'s memory usage!", Id);
31 return null;
32 }
33 }
34 }
35
37 public Task Startup { get; }
38
40 public Task<int?> Lifetime { get; }
41
43 public long? MemoryUsage
44 {
45 get
46 {
47 try
48 {
49 return handle.PrivateMemorySize64;
50 }
51 catch (Exception ex)
52 {
53 logger.LogWarning(ex, "Failed to get PID {pid}'s memory usage!", Id);
54 return null;
55 }
56 }
57 }
58
63
67 readonly ILogger<Process> logger;
68
72 readonly global::System.Diagnostics.Process handle;
73
77 readonly CancellationTokenSource cancellationTokenSource;
78
83 readonly SafeProcessHandle safeHandle;
84
88 readonly Task<string?>? readTask;
89
93 readonly object processTimeMeasureLock;
94
99
104
109
114
124 public Process(
126 global::System.Diagnostics.Process handle,
127 CancellationTokenSource? readerCts,
128 Task<string?>? readTask,
129 ILogger<Process> logger,
130 bool preExisting)
131 {
132 this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
133
134 // Do this fast because the runtime will bitch if we try to access it after it ends
135 safeHandle = handle.SafeHandle;
136 Id = handle.Id;
137
138 cancellationTokenSource = readerCts ?? new CancellationTokenSource();
139
140 this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
141
142 this.readTask = readTask;
143
144 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
145
146 processTimeMeasureLock = new object();
147
149
150 if (preExisting)
151 {
152 Startup = Task.CompletedTask;
153 return;
154 }
155
156 Startup = Task.Factory.StartNew(
157 () =>
158 {
159 try
160 {
161 handle.WaitForInputIdle();
162 }
163 catch (Exception ex)
164 {
165 logger.LogTrace(ex, "WaitForInputIdle() failed, this is normal.");
166 }
167 },
168 CancellationToken.None, // DCT: None available
170 TaskScheduler.Current);
171
172 try
173 {
174 lastProcessorUsageTime = handle.TotalProcessorTime;
175 }
176 catch (Exception ex)
177 {
178 logger.LogTrace(ex, "Failed to measure initial process time.");
179 lastProcessorUsageTime = TimeSpan.Zero;
180 }
181
182 lastProcessorMeasureTime = DateTimeOffset.UtcNow;
183
184 logger.LogTrace("Created process ID: {pid}", Id);
185 }
186
188 public async ValueTask DisposeAsync()
189 {
190 if (Interlocked.Exchange(ref disposed, 1) != 0)
191 return;
192
193 logger.LogTrace("Disposing PID {pid}...", Id);
195 cancellationTokenSource.Dispose();
196 if (readTask != null)
197 await readTask;
198
199 await Lifetime;
200
201 safeHandle.Dispose();
202 handle.Dispose();
203 }
204
206 public Task<string?> GetCombinedOutput(CancellationToken cancellationToken)
207 {
208 if (readTask == null)
209 throw new InvalidOperationException("Output/Error stream reading was not enabled!");
210
211 return readTask.WaitAsync(cancellationToken);
212 }
213
215 public void Terminate()
216 {
218 if (handle.HasExited)
219 {
220 logger.LogTrace("PID {pid} already exited", Id);
221 return;
222 }
223
224 try
225 {
226 logger.LogTrace("Terminating PID {pid}...", Id);
227 handle.Kill();
228 if (!handle.WaitForExit(5000))
229 logger.LogWarning("WaitForExit() on PID {pid} timed out!", Id);
230 }
231 catch (Exception e)
232 {
233 logger.LogDebug(e, "PID {pid} termination exception!", Id);
234 }
235 }
236
238 public void AdjustPriority(bool higher)
239 {
241 var targetPriority = higher ? ProcessPriorityClass.AboveNormal : ProcessPriorityClass.BelowNormal;
242 try
243 {
244 handle.PriorityClass = targetPriority;
245 logger.LogTrace("Set PID {pid} to {targetPriority} priority", Id, targetPriority);
246 }
247 catch (Exception ex)
248 {
249 logger.LogWarning(ex, "Unable to set priority for PID {id} to {targetPriority}!", Id, targetPriority);
250 }
251 }
252
254 public void SuspendProcess()
255 {
257 try
258 {
260 logger.LogTrace("Suspended PID {pid}", Id);
261 }
262 catch (Exception e)
263 {
264 logger.LogError(e, "Failed to suspend PID {pid}!", Id);
265 throw;
266 }
267 }
268
270 public void ResumeProcess()
271 {
273 try
274 {
276 logger.LogTrace("Resumed PID {pid}", Id);
277 }
278 catch (Exception e)
279 {
280 logger.LogError(e, "Failed to resume PID {pid}!", Id);
281 throw;
282 }
283 }
284
286 public string GetExecutingUsername()
287 {
290 logger.LogTrace("PID {pid} Username: {username}", Id, result);
291 return result;
292 }
293
295 public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
296 {
297 ArgumentNullException.ThrowIfNull(outputFile);
299
300 logger.LogTrace("Dumping PID {pid} to {dumpFilePath}...", Id, outputFile);
301 return processFeatures.CreateDump(handle, outputFile, minidump, cancellationToken);
302 }
303
306 {
308 {
309 try
310 {
311 var now = DateTimeOffset.UtcNow;
312 var newTime = handle.TotalProcessorTime;
313
314 var timeDelta = now - lastProcessorMeasureTime;
315 var newUsage = newTime - lastProcessorUsageTime;
316
318 lastProcessorUsageTime = newTime;
319
320 if (timeDelta != TimeSpan.Zero)
321 lastProcessorUsageEstimation = newUsage / (Environment.ProcessorCount * timeDelta);
322 }
323 catch (Exception ex)
324 {
325 logger.LogWarning(ex, "Error measuring processor time delta!");
326 }
327
329 }
330 }
331
336 async Task<int?> WrapLifetimeTask()
337 {
338 bool hasExited;
339 try
340 {
341 await handle.WaitForExitAsync(cancellationTokenSource.Token);
342 hasExited = true;
343 }
344 catch (OperationCanceledException ex)
345 {
346 logger.LogTrace(ex, "Process lifetime task cancelled!");
347 hasExited = handle.HasExited;
348 }
349
350 if (!hasExited)
351 return null;
352
353 var exitCode = handle.ExitCode;
354 logger.LogTrace("PID {pid} exited with code {exitCode}", Id, exitCode);
355 return exitCode;
356 }
357
361 void CheckDisposed() => ObjectDisposedException.ThrowIf(disposed != 0, this);
362 }
363}
IIOManager that resolves paths to Environment.CurrentDirectory.
const TaskCreationOptions BlockingTaskCreationOptions
The TaskCreationOptions used to spawn Tasks for potentially long running, blocking operations.
readonly object processTimeMeasureLock
lock object for measuring processor time usage.
Definition Process.cs:93
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:124
DateTimeOffset lastProcessorMeasureTime
The last time MeasureProcessorTimeDelta was called.
Definition Process.cs:98
void CheckDisposed()
Throws an ObjectDisposedException if a method of the Process was called after DisposeAsync.
DateTimeOffset? LaunchTime
When the process was started.
Definition Process.cs:21
readonly global::System.Diagnostics.Process handle
The global::System.Diagnostics.Process object.
Definition Process.cs:72
Task< int?> Lifetime
The Task<TResult> resulting in the exit code of the process or null if the process was detached.
Definition Process.cs:40
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:206
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
Definition Process.cs:238
ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
Create a dump file of the process.A ValueTask representing the running operation.
Definition Process.cs:295
double MeasureProcessorTimeDelta()
Gets the estimated CPU usage fraction of the process based on the last time this was called....
Definition Process.cs:305
double lastProcessorUsageEstimation
The last valid return value of MeasureProcessorTimeDelta.
Definition Process.cs:108
TimeSpan lastProcessorUsageTime
The last value of global::System.Diagnostics.Process.TotalProcessorTime.
Definition Process.cs:103
void Terminate()
Asycnhronously terminates the process.To ensure the IProcess has ended, use the IProcessBase....
Definition Process.cs:215
readonly ILogger< Process > logger
The ILogger for the Process.
Definition Process.cs:67
void SuspendProcess()
Suspends the process.
Definition Process.cs:254
readonly CancellationTokenSource cancellationTokenSource
The CancellationTokenSource used to shutdown the readTask and Lifetime.
Definition Process.cs:77
string GetExecutingUsername()
Get the name of the account executing the IProcess.The name of the account executing the IProcess.
Definition Process.cs:286
void ResumeProcess()
Resumes the process.
Definition Process.cs:270
readonly SafeProcessHandle safeHandle
The global::System.Diagnostics.Process.SafeHandle.
Definition Process.cs:83
readonly IProcessFeatures processFeatures
The IProcessFeatures for the Process.
Definition Process.cs:62
Task Startup
The Task representing the time until the IProcess becomes "idle".
Definition Process.cs:37
long? MemoryUsage
Gets the process' memory usage in bytes.
Definition Process.cs:44
readonly? Task< string?> readTask
The Task<TResult> resulting in the process' standard output/error text.
Definition Process.cs:88
int disposed
If the Process was disposed.
Definition Process.cs:113
async Task< int?> WrapLifetimeTask()
Attaches a log message to the process' exit event.
Definition Process.cs:336
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