tgstation-server 5.12.7
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 readerCts;
44
49 readonly SafeProcessHandle safeHandle;
50
54 readonly Task<string> readTask;
55
66 public Process(
68 global::System.Diagnostics.Process handle,
69 CancellationTokenSource readerCts,
70 Task<int> lifetime,
71 Task<string> readTask,
72 ILogger<Process> logger,
73 bool preExisting)
74 {
75 this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
76
77 // Do this fast because the runtime will bitch if we try to access it after it ends
78 safeHandle = handle.SafeHandle;
79 Id = handle.Id;
80
81 this.readerCts = readerCts;
82
83 this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
84
85 this.readTask = readTask;
86
87 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
88
89 Lifetime = WrapLifetimeTask(lifetime ?? throw new ArgumentNullException(nameof(lifetime)));
90
91 if (preExisting)
92 {
93 Startup = Task.CompletedTask;
94 return;
95 }
96
97 Startup = Task.Factory.StartNew(
98 () =>
99 {
100 try
101 {
102 handle.WaitForInputIdle();
103 }
104 catch (Exception ex)
105 {
106 logger.LogTrace(ex, "WaitForInputIdle() failed, this is normal.");
107 }
108 },
109 CancellationToken.None, // DCT: None available
111 TaskScheduler.Current);
112
113 logger.LogTrace("Created process ID: {pid}", Id);
114 }
115
117 public async ValueTask DisposeAsync()
118 {
119 logger.LogTrace("Disposing PID {pid}...", Id);
120 readerCts?.Cancel();
121 readerCts?.Dispose();
122 if (readTask != null)
123 await readTask;
124
125 safeHandle.Dispose();
126 handle.Dispose();
127 }
128
130 public Task<string> GetCombinedOutput(CancellationToken cancellationToken)
131 {
132 if (readTask == null)
133 throw new InvalidOperationException("Output/Error stream reading was not enabled!");
134 return readTask;
135 }
136
138 public void Terminate()
139 {
140 if (handle.HasExited)
141 {
142 logger.LogTrace("PID {pid} already exited", Id);
143 return;
144 }
145
146 try
147 {
148 logger.LogTrace("Terminating PID {pid}...", Id);
149 handle.Kill();
150 if (!handle.WaitForExit(5000))
151 logger.LogWarning("WaitForExit() on PID {pid} timed out!", Id);
152 }
153 catch (Exception e)
154 {
155 logger.LogDebug(e, "PID {pid} termination exception!", Id);
156 }
157 }
158
160 public void AdjustPriority(bool higher)
161 {
162 var targetPriority = higher ? ProcessPriorityClass.AboveNormal : ProcessPriorityClass.BelowNormal;
163 try
164 {
165 handle.PriorityClass = targetPriority;
166 logger.LogTrace("Set PID {pid} to {targetPriority} priority", Id, targetPriority);
167 }
168 catch (Exception ex)
169 {
170 logger.LogWarning(ex, "Unable to set priority for PID {id} to {targetPriority}!", Id, targetPriority);
171 }
172 }
173
175 public void Suspend()
176 {
177 try
178 {
180 logger.LogTrace("Suspended PID {pid}", Id);
181 }
182 catch (Exception e)
183 {
184 logger.LogError(e, "Failed to suspend PID {pid}!", Id);
185 throw;
186 }
187 }
188
190 public void Resume()
191 {
192 try
193 {
195 logger.LogTrace("Resumed PID {pid}", Id);
196 }
197 catch (Exception e)
198 {
199 logger.LogError(e, "Failed to resume PID {pid}!", Id);
200 throw;
201 }
202 }
203
205 public async Task<string> GetExecutingUsername(CancellationToken cancellationToken)
206 {
207 var result = await processFeatures.GetExecutingUsername(handle, cancellationToken);
208 logger.LogTrace("PID {pid} Username: {username}", Id, result);
209 return result;
210 }
211
213 public Task CreateDump(string outputFile, CancellationToken cancellationToken)
214 {
215 ArgumentNullException.ThrowIfNull(outputFile);
216
217 logger.LogTrace("Dumping PID {pid} to {dumpFilePath}...", Id, outputFile);
218 return processFeatures.CreateDump(handle, outputFile, cancellationToken);
219 }
220
226 async Task<int> WrapLifetimeTask(Task<int> lifetimeTask)
227 {
228 var exitCode = await lifetimeTask;
229 logger.LogTrace("PID {pid} exited with code {exitCode}", Id, exitCode);
230 return exitCode;
231 }
232 }
233}
IIOManager that resolves paths to Environment.CurrentDirectory.
const TaskCreationOptions BlockingTaskCreationOptions
The TaskCreationOptions used to spawn Tasks for potentially long running, blocking operations.
async Task< int > WrapLifetimeTask(Task< int > lifetimeTask)
Attaches a log message to the process' exit event.
Definition: Process.cs:226
async ValueTask DisposeAsync()
Definition: Process.cs:117
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
readonly Task< string > readTask
The Task<TResult> resulting in the process' standard output/error text.
Definition: Process.cs:54
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
Definition: Process.cs:160
Process(IProcessFeatures processFeatures, global::System.Diagnostics.Process handle, CancellationTokenSource readerCts, Task< int > lifetime, Task< string > readTask, ILogger< Process > logger, bool preExisting)
Initializes a new instance of the Process class.
Definition: Process.cs:66
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:130
void Terminate()
Asycnhronously terminates the process. To ensure the IProcess has ended, use the IProcessBase....
Definition: Process.cs:138
void Suspend()
Suspends the process.
Definition: Process.cs:175
async Task< string > GetExecutingUsername(CancellationToken cancellationToken)
Get the name of the account executing the IProcess. A Task<TResult> resulting in the name of the acco...
Definition: Process.cs:205
readonly ILogger< Process > logger
The ILogger for the Process.
Definition: Process.cs:33
Task< int > Lifetime
The Task<TResult> resulting in the exit code of the process.
Definition: Process.cs:23
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
void Resume()
Resumes the process.
Definition: Process.cs:190
Task CreateDump(string outputFile, CancellationToken cancellationToken)
Create a dump file of the process. A Task representing the running operation.
Definition: Process.cs:213
readonly CancellationTokenSource readerCts
The CancellationTokenSource used to shutdown the readTask.
Definition: Process.cs:43
Abstraction for suspending and resuming processes.
void SuspendProcess(global::System.Diagnostics.Process process)
Suspend a given process .
Task CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
Create a dump file for a given process .
void ResumeProcess(global::System.Diagnostics.Process process)
Resume a given suspended Process.
Task< string > GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken)
Get the name of the user executing a given process .
Abstraction over a global::System.Diagnostics.Process.
Definition: IProcess.cs:11