tgstation-server 6.0.1
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
29
33 readonly ILogger<Process> logger;
34
38 readonly global::System.Diagnostics.Process handle;
39
43 readonly CancellationTokenSource cancellationTokenSource;
44
49 readonly SafeProcessHandle safeHandle;
50
54 readonly Task<string?>? readTask;
55
59 volatile int disposed;
60
70 public Process(
72 global::System.Diagnostics.Process handle,
73 CancellationTokenSource? readerCts,
74 Task<string?>? readTask,
75 ILogger<Process> logger,
76 bool preExisting)
77 {
78 this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
79
80 // Do this fast because the runtime will bitch if we try to access it after it ends
81 safeHandle = handle.SafeHandle;
82 Id = handle.Id;
83
84 cancellationTokenSource = readerCts ?? new CancellationTokenSource();
85
86 this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
87
88 this.readTask = readTask;
89
90 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
91
93
94 if (preExisting)
95 {
96 Startup = Task.CompletedTask;
97 return;
98 }
99
100 Startup = Task.Factory.StartNew(
101 () =>
102 {
103 try
104 {
105 handle.WaitForInputIdle();
106 }
107 catch (Exception ex)
108 {
109 logger.LogTrace(ex, "WaitForInputIdle() failed, this is normal.");
110 }
111 },
112 CancellationToken.None, // DCT: None available
114 TaskScheduler.Current);
115
116 logger.LogTrace("Created process ID: {pid}", Id);
117 }
118
120 public async ValueTask DisposeAsync()
121 {
122 if (Interlocked.Exchange(ref disposed, 1) != 0)
123 return;
124
125 logger.LogTrace("Disposing PID {pid}...", Id);
127 cancellationTokenSource.Dispose();
128 if (readTask != null)
129 await readTask;
130
131 await Lifetime;
132
133 safeHandle.Dispose();
134 handle.Dispose();
135 }
136
138 public Task<string?> GetCombinedOutput(CancellationToken cancellationToken)
139 {
140 if (readTask == null)
141 throw new InvalidOperationException("Output/Error stream reading was not enabled!");
142
143 return readTask.WaitAsync(cancellationToken);
144 }
145
147 public void Terminate()
148 {
150 if (handle.HasExited)
151 {
152 logger.LogTrace("PID {pid} already exited", Id);
153 return;
154 }
155
156 try
157 {
158 logger.LogTrace("Terminating PID {pid}...", Id);
159 handle.Kill();
160 if (!handle.WaitForExit(5000))
161 logger.LogWarning("WaitForExit() on PID {pid} timed out!", Id);
162 }
163 catch (Exception e)
164 {
165 logger.LogDebug(e, "PID {pid} termination exception!", Id);
166 }
167 }
168
170 public void AdjustPriority(bool higher)
171 {
173 var targetPriority = higher ? ProcessPriorityClass.AboveNormal : ProcessPriorityClass.BelowNormal;
174 try
175 {
176 handle.PriorityClass = targetPriority;
177 logger.LogTrace("Set PID {pid} to {targetPriority} priority", Id, targetPriority);
178 }
179 catch (Exception ex)
180 {
181 logger.LogWarning(ex, "Unable to set priority for PID {id} to {targetPriority}!", Id, targetPriority);
182 }
183 }
184
186 public void SuspendProcess()
187 {
189 try
190 {
192 logger.LogTrace("Suspended PID {pid}", Id);
193 }
194 catch (Exception e)
195 {
196 logger.LogError(e, "Failed to suspend PID {pid}!", Id);
197 throw;
198 }
199 }
200
202 public void ResumeProcess()
203 {
205 try
206 {
208 logger.LogTrace("Resumed PID {pid}", Id);
209 }
210 catch (Exception e)
211 {
212 logger.LogError(e, "Failed to resume PID {pid}!", Id);
213 throw;
214 }
215 }
216
218 public string GetExecutingUsername()
219 {
222 logger.LogTrace("PID {pid} Username: {username}", Id, result);
223 return result;
224 }
225
227 public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken)
228 {
229 ArgumentNullException.ThrowIfNull(outputFile);
231
232 logger.LogTrace("Dumping PID {pid} to {dumpFilePath}...", Id, outputFile);
233 return processFeatures.CreateDump(handle, outputFile, cancellationToken);
234 }
235
240 async Task<int?> WrapLifetimeTask()
241 {
242 bool hasExited;
243 try
244 {
245 await handle.WaitForExitAsync(cancellationTokenSource.Token);
246 hasExited = true;
247 }
248 catch (OperationCanceledException ex)
249 {
250 logger.LogTrace(ex, "Process lifetime task cancelled!");
251 hasExited = handle.HasExited;
252 }
253
254 if (!hasExited)
255 return null;
256
257 var exitCode = handle.ExitCode;
258 logger.LogTrace("PID {pid} exited with code {exitCode}", Id, exitCode);
259 return exitCode;
260 }
261
265 void CheckDisposed() => ObjectDisposedException.ThrowIf(disposed != 0, this);
266 }
267}
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:70
ValueTask CreateDump(string outputFile, CancellationToken cancellationToken)
Create a dump file of the process. A ValueTask representing the running operation.
Definition: Process.cs:227
void CheckDisposed()
Throws an ObjectDisposedException if a method of the Process was called after DisposeAsync.
async ValueTask DisposeAsync()
Definition: Process.cs:120
int Id
The IProcess' ID.
Definition: Process.cs:17
readonly global::System.Diagnostics.Process handle
The global::System.Diagnostics.Process object.
Definition: Process.cs:38
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 ...
Definition: Process.cs:138
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
Definition: Process.cs:170
volatile int disposed
If the Process was disposed.
Definition: Process.cs:59
void Terminate()
Asycnhronously terminates the process. To ensure the IProcess has ended, use the IProcessBase....
Definition: Process.cs:147
readonly ILogger< Process > logger
The ILogger for the Process.
Definition: Process.cs:33
void SuspendProcess()
Suspends the process.
Definition: Process.cs:186
readonly CancellationTokenSource cancellationTokenSource
The CancellationTokenSource used to shutdown the readTask and Lifetime.
Definition: Process.cs:43
string GetExecutingUsername()
Get the name of the account executing the IProcess. The name of the account executing the IProcess.
Definition: Process.cs:218
void ResumeProcess()
Resumes the process.
Definition: Process.cs:202
readonly SafeProcessHandle safeHandle
The global::System.Diagnostics.Process.SafeHandle.
Definition: Process.cs:49
readonly IProcessFeatures processFeatures
The IProcessFeatures for the Process.
Definition: Process.cs:28
Task Startup
The Task representing the time until the IProcess becomes "idle".
Definition: Process.cs:20
readonly? Task< string?> readTask
The Task<TResult> resulting in the process' standard output/error text.
Definition: Process.cs:54
async Task< int?> WrapLifetimeTask()
Attaches a log message to the process' exit event.
Definition: Process.cs:240
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, 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 Process.
Abstraction over a global::System.Diagnostics.Process.
Definition: IProcess.cs:11