tgstation-server 5.12.7
The /tg/station 13 server suite
Loading...
Searching...
No Matches
SessionPersistor.cs
Go to the documentation of this file.
1using System;
2using System.Linq;
3using System.Threading;
4using System.Threading.Tasks;
5
6using Microsoft.EntityFrameworkCore;
7using Microsoft.Extensions.Logging;
8
12
13using Z.EntityFramework.Plus;
14
16{
19 {
24
29
34
38 readonly ILogger<SessionPersistor> logger;
39
44
57 ILogger<SessionPersistor> logger,
58 Api.Models.Instance metadata)
59 {
60 this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
61 this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
62 this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
63 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
64 this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
65 }
66
68 public Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) =>
69 {
70 ArgumentNullException.ThrowIfNull(reattachInformation);
71
72 logger.LogDebug("Saving reattach information: {info}...", reattachInformation);
73
74 await ClearImpl(db, false, cancellationToken);
75
76 var dbReattachInfo = new Models.ReattachInformation
77 {
78 AccessIdentifier = reattachInformation.AccessIdentifier,
79 CompileJobId = reattachInformation.Dmb.CompileJob.Id.Value,
80 InitialCompileJobId = reattachInformation.InitialDmb?.CompileJob.Id.Value,
81 Port = reattachInformation.Port,
82 ProcessId = reattachInformation.ProcessId,
83 RebootState = reattachInformation.RebootState,
84 LaunchSecurityLevel = reattachInformation.LaunchSecurityLevel,
85 LaunchVisibility = reattachInformation.LaunchVisibility,
86 };
87
88 db.ReattachInformations.Add(dbReattachInfo);
89 await db.Save(cancellationToken);
90 });
91
93 public async Task<ReattachInformation> Load(CancellationToken cancellationToken)
94 {
95 Models.ReattachInformation result = null;
96 TimeSpan? topicTimeout = null;
97
98 async Task KillProcess(Models.ReattachInformation reattachInfo)
99 {
100 try
101 {
102 await using var process = processExecutor.GetProcess(reattachInfo.ProcessId);
103 if (process != null)
104 {
105 if (reattachInfo == result)
106 {
107 logger.LogWarning("Killing PID {pid} associated with CompileJob-less reattach information...", reattachInfo.ProcessId);
108 }
109 else
110 {
111 logger.LogWarning("Killing PID {pid} associated with extra reattach information...", reattachInfo.ProcessId);
112 }
113
114 process.Terminate();
115 await process.Lifetime;
116 }
117 }
118 catch (Exception ex)
119 {
120 logger.LogWarning(ex, "Failed to kill process!");
121 }
122 }
123
124 await databaseContextFactory.UseContext(async (db) =>
125 {
126 var dbReattachInfos = await db
127 .ReattachInformations
128 .AsQueryable()
129 .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id)
130 .Include(x => x.CompileJob)
131 .Include(x => x.InitialCompileJob)
132 .ToListAsync(cancellationToken);
133 result = dbReattachInfos.FirstOrDefault();
134 if (result == default)
135 return;
136
137 var timeoutMilliseconds = await db
138 .Instances
139 .AsQueryable()
140 .Where(x => x.Id == metadata.Id)
141 .Select(x => x.DreamDaemonSettings.TopicRequestTimeout)
142 .FirstOrDefaultAsync(cancellationToken);
143
144 if (timeoutMilliseconds == default)
145 {
146 logger.LogCritical("Missing TopicRequestTimeout!");
147 return;
148 }
149
150 topicTimeout = TimeSpan.FromMilliseconds(timeoutMilliseconds.Value);
151
152 bool first = true;
153 foreach (var reattachInfo in dbReattachInfos)
154 {
155 if (first)
156 {
157 first = false;
158 continue;
159 }
160
161 await KillProcess(reattachInfo);
162
163 db.ReattachInformations.Remove(reattachInfo);
164 logger.LogTrace("Deleting ReattachInformation {id}...", reattachInfo.Id);
165 }
166
167 await db.Save(cancellationToken);
168 });
169
170 if (!topicTimeout.HasValue)
171 {
172 logger.LogDebug("Reattach information not found!");
173 return null;
174 }
175
176 var dmb = await dmbFactory.FromCompileJob(result.CompileJob, cancellationToken);
177 if (dmb == null)
178 {
179 logger.LogError("Unable to reattach! Could not load .dmb!");
180 await KillProcess(result);
181
182 await databaseContextFactory.UseContext(async db =>
183 {
184 logger.LogTrace("Deleting ReattachInformation {id}...", result.Id);
185 await db
186 .ReattachInformations
187 .AsQueryable()
188 .Where(x => x.Id == result.Id)
189 .DeleteAsync(cancellationToken);
190 });
191 return null;
192 }
193
194 IDmbProvider initialDmb = null;
195 if (result.InitialCompileJob != null)
196 {
197 logger.LogTrace("Loading initial compile job...");
198 initialDmb = await dmbFactory.FromCompileJob(result.InitialCompileJob, cancellationToken);
199 }
200
201 logger.LogTrace("Retrieved ReattachInformation");
202
203 var info = new ReattachInformation(
204 result,
205 dmb,
206 initialDmb,
207 topicTimeout.Value);
208
209 logger.LogDebug("Reattach information loaded: {info}", info);
210
211 return info;
212 }
213
215 public Task Clear(CancellationToken cancellationToken) => databaseContextFactory
216 .UseContext(
217 db =>
218 {
219 logger.LogDebug("Clearing reattach information");
220 return ClearImpl(db, true, cancellationToken);
221 });
222
230 async Task ClearImpl(IDatabaseContext databaseContext, bool instant, CancellationToken cancellationToken)
231 {
232 var baseQuery = databaseContext
234 .AsQueryable()
235 .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id);
236
237 if (instant)
238 await baseQuery
239 .DeleteAsync(cancellationToken);
240 else
241 {
242 var results = await baseQuery.ToListAsync(cancellationToken);
243 foreach (var result in results)
244 databaseContext.ReattachInformations.Remove(result);
245 }
246 }
247 }
248}
Metadata about a server instance.
Definition: Instance.cs:9
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance.
Parameters necessary for duplicating a ISessionController session.
readonly ILogger< SessionPersistor > logger
The ILogger for the SessionPersistor.
readonly IProcessExecutor processExecutor
The IProcessExecutor for the SessionPersistor.
async Task< ReattachInformation > Load(CancellationToken cancellationToken)
Load a saved ReattachInformation. A Task<TResult> resulting in the stored ReattachInformation if any.
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the SessionPersistor.
readonly Api.Models.Instance metadata
The Api.Models.Instance for the SessionPersistor.
Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Save some reattachInformation . A Task representing the running operation.
readonly IDmbFactory dmbFactory
The IDmbFactory for the SessionPersistor.
async Task ClearImpl(IDatabaseContext databaseContext, bool instant, CancellationToken cancellationToken)
Clear any stored ReattachInformation.
SessionPersistor(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, IProcessExecutor processExecutor, ILogger< SessionPersistor > logger, Api.Models.Instance metadata)
Initializes a new instance of the SessionPersistor class.
Task Clear(CancellationToken cancellationToken)
Clear any stored ReattachInformation. A Task representing the running operation.
Database representation of Components.Session.ReattachInformation.
CompileJob CompileJob
The Models.CompileJob for the Components.Session.ReattachInformation.Dmb.
CompileJob InitialCompileJob
The Models.CompileJob the server was initially launched with in the case of Windows.
Task< IDmbProvider > FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken)
Gets a IDmbProvider for a given CompileJob.
Provides absolute paths to the latest compiled .dmbs.
Definition: IDmbProvider.cs:11
Handles saving and loading ReattachInformation.
void Remove(TModel model)
Remove a given model from the the working set.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components.
Task UseContext(Func< IDatabaseContext, Task > operation)
Run an operation in the scope of an IDatabaseContext.
IDatabaseCollection< ReattachInformation > ReattachInformations
The DbSet<TEntity> for ReattachInformations.
IProcess GetProcess(int id)
Get a IProcess by id .
RebootState
Represents the action to take when /world/Reboot() is called.
Definition: RebootState.cs:7