tgstation-server 6.12.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 volatile int disposed;
94
104 public Process(
106 global::System.Diagnostics.Process handle,
107 CancellationTokenSource? readerCts,
108 Task<string?>? readTask,
109 ILogger<Process> logger,
110 bool preExisting)
111 {
112 this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
113
114 // Do this fast because the runtime will bitch if we try to access it after it ends
115 safeHandle = handle.SafeHandle;
116 Id = handle.Id;
117
118 cancellationTokenSource = readerCts ?? new CancellationTokenSource();
119
120 this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
121
122 this.readTask = readTask;
123
124 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
125
127
128 if (preExisting)
129 {
130 Startup = Task.CompletedTask;
131 return;
132 }
133
134 Startup = Task.Factory.StartNew(
135 () =>
136 {
137 try
138 {
139 handle.WaitForInputIdle();
140 }
141 catch (Exception ex)
142 {
143 logger.LogTrace(ex, "WaitForInputIdle() failed, this is normal.");
144 }
145 },
146 CancellationToken.None, // DCT: None available
148 TaskScheduler.Current);
149
150 logger.LogTrace("Created process ID: {pid}", Id);
151 }
152
154 public async ValueTask DisposeAsync()
155 {
156 if (Interlocked.Exchange(ref disposed, 1) != 0)
157 return;
158
159 logger.LogTrace("Disposing PID {pid}...", Id);
161 cancellationTokenSource.Dispose();
162 if (readTask != null)
163 await readTask;
164
165 await Lifetime;
166
167 safeHandle.Dispose();
168 handle.Dispose();
169 }
170
172 public Task<string?> GetCombinedOutput(CancellationToken cancellationToken)
173 {
174 if (readTask == null)
175 throw new InvalidOperationException("Output/Error stream reading was not enabled!");
176
177 return readTask.WaitAsync(cancellationToken);
178 }
179
181 public void Terminate()
182 {
184 if (handle.HasExited)
185 {
186 logger.LogTrace("PID {pid} already exited", Id);
187 return;
188 }
189
190 try
191 {
192 logger.LogTrace("Terminating PID {pid}...", Id);
193 handle.Kill();
194 if (!handle.WaitForExit(5000))
195 logger.LogWarning("WaitForExit() on PID {pid} timed out!", Id);
196 }
197 catch (Exception e)
198 {
199 logger.LogDebug(e, "PID {pid} termination exception!", Id);
200 }
201 }
202
204 public void AdjustPriority(bool higher)
205 {
207 var targetPriority = higher ? ProcessPriorityClass.AboveNormal : ProcessPriorityClass.BelowNormal;
208 try
209 {
210 handle.PriorityClass = targetPriority;
211 logger.LogTrace("Set PID {pid} to {targetPriority} priority", Id, targetPriority);
212 }
213 catch (Exception ex)
214 {
215 logger.LogWarning(ex, "Unable to set priority for PID {id} to {targetPriority}!", Id, targetPriority);
216 }
217 }
218
220 public void SuspendProcess()
221 {
223 try
224 {
226 logger.LogTrace("Suspended PID {pid}", Id);
227 }
228 catch (Exception e)
229 {
230 logger.LogError(e, "Failed to suspend PID {pid}!", Id);
231 throw;
232 }
233 }
234
236 public void ResumeProcess()
237 {
239 try
240 {
242 logger.LogTrace("Resumed PID {pid}", Id);
243 }
244 catch (Exception e)
245 {
246 logger.LogError(e, "Failed to resume PID {pid}!", Id);
247 throw;
248 }
249 }
250
252 public string GetExecutingUsername()
253 {
256 logger.LogTrace("PID {pid} Username: {username}", Id, result);
257 return result;
258 }
259
261 public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
262 {
263 ArgumentNullException.ThrowIfNull(outputFile);
265
266 logger.LogTrace("Dumping PID {pid} to {dumpFilePath}...", Id, outputFile);
267 return processFeatures.CreateDump(handle, outputFile, minidump, cancellationToken);
268 }
269
274 async Task<int?> WrapLifetimeTask()
275 {
276 bool hasExited;
277 try
278 {
279 await handle.WaitForExitAsync(cancellationTokenSource.Token);
280 hasExited = true;
281 }
282 catch (OperationCanceledException ex)
283 {
284 logger.LogTrace(ex, "Process lifetime task cancelled!");
285 hasExited = handle.HasExited;
286 }
287
288 if (!hasExited)
289 return null;
290
291 var exitCode = handle.ExitCode;
292 logger.LogTrace("PID {pid} exited with code {exitCode}", Id, exitCode);
293 return exitCode;
294 }
295
299 void CheckDisposed() => ObjectDisposedException.ThrowIf(disposed != 0, this);
300 }
301}
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:104
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:172
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
Definition Process.cs:204
ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
Create a dump file of the process.A ValueTask representing the running operation.
Definition Process.cs:261
volatile int disposed
If the Process was disposed.
Definition Process.cs:93
void Terminate()
Asycnhronously terminates the process.To ensure the IProcess has ended, use the IProcessBase....
Definition Process.cs:181
readonly ILogger< Process > logger
The ILogger for the Process.
Definition Process.cs:67
void SuspendProcess()
Suspends the process.
Definition Process.cs:220
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:252
void ResumeProcess()
Resumes the process.
Definition Process.cs:236
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
async Task< int?> WrapLifetimeTask()
Attaches a log message to the process' exit event.
Definition Process.cs:274
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