tgstation-server  4.4.0
The /tg/station 13 server suite
SessionPersistor.cs
Go to the documentation of this file.
1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.Extensions.Logging;
3 using System;
4 using System.Linq;
5 using System.Threading;
6 using System.Threading.Tasks;
10 using Z.EntityFramework.Plus;
11 
12 namespace Tgstation.Server.Host.Components.Session
13 {
16  {
21 
26 
31 
35  readonly ILogger<SessionPersistor> logger;
36 
40  readonly Api.Models.Instance metadata;
41 
51  IDatabaseContextFactory databaseContextFactory,
52  IDmbFactory dmbFactory,
53  IProcessExecutor processExecutor,
54  ILogger<SessionPersistor> logger,
55  Api.Models.Instance metadata)
56  {
57  this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
58  this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
59  this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
60  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
61  this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
62  }
63 
65  public Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) =>
66  {
67  if (reattachInformation == null)
68  throw new ArgumentNullException(nameof(reattachInformation));
69 
70  logger.LogDebug("Saving reattach information: {0}...", reattachInformation);
71 
72  await db
73  .ReattachInformations
74  .AsQueryable()
75  .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id)
76  .DeleteAsync(cancellationToken)
77  .ConfigureAwait(false);
78 
79  var dbReattachInfo = new Models.ReattachInformation
80  {
81  AccessIdentifier = reattachInformation.AccessIdentifier,
82  CompileJobId = reattachInformation.Dmb.CompileJob.Id,
83  Port = reattachInformation.Port,
84  ProcessId = reattachInformation.ProcessId,
85  RebootState = reattachInformation.RebootState,
86  LaunchSecurityLevel = reattachInformation.LaunchSecurityLevel
87  };
88 
89  db.ReattachInformations.Add(dbReattachInfo);
90  await db.Save(cancellationToken).ConfigureAwait(false);
91  });
92 
94  public async Task<ReattachInformation> Load(CancellationToken cancellationToken)
95  {
96  Models.ReattachInformation result = null;
97  TimeSpan? topicTimeout = null;
98  await databaseContextFactory.UseContext(async (db) =>
99  {
100  var dbReattachInfos = await db
101  .ReattachInformations
102  .AsQueryable()
103  .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id)
104  .Include(x => x.CompileJob)
105  .ToListAsync(cancellationToken).ConfigureAwait(false);
106  result = dbReattachInfos.FirstOrDefault();
107  if (result == default)
108  return;
109 
110  var timeoutMilliseconds = await db
111  .Instances
112  .AsQueryable()
113  .Where(x => x.Id == metadata.Id)
114  .Select(x => x.DreamDaemonSettings.TopicRequestTimeout)
115  .FirstOrDefaultAsync(cancellationToken)
116  .ConfigureAwait(false);
117 
118  if (timeoutMilliseconds == default)
119  {
120  logger.LogCritical("Missing TopicRequestTimeout!");
121  return;
122  }
123 
124  topicTimeout = TimeSpan.FromMilliseconds(timeoutMilliseconds.Value);
125 
126  bool first = true;
127  foreach (var reattachInfo in dbReattachInfos)
128  {
129  if (!first)
130  {
131  logger.LogWarning("Killing PID {0} associated with extra reattach information...", reattachInfo.ProcessId);
132  try
133  {
134  using var process = processExecutor.GetProcess(reattachInfo.ProcessId);
135  process.Terminate();
136  }
137  catch (Exception ex)
138  {
139  logger.LogWarning("Failed to kill process! Exception: {0}", ex);
140  }
141  }
142 
143  db.ReattachInformations.Remove(reattachInfo);
144  first = false;
145  }
146 
147  await db.Save(cancellationToken).ConfigureAwait(false);
148  }).ConfigureAwait(false);
149 
150  if (!topicTimeout.HasValue)
151  {
152  logger.LogDebug("Reattach information not found!");
153  return null;
154  }
155 
156  var info = new ReattachInformation(
157  result,
158  await dmbFactory.FromCompileJob(result.CompileJob, cancellationToken).ConfigureAwait(false),
159  topicTimeout.Value);
160 
161  logger.LogDebug("Reattach information loaded: {0}", info);
162 
163  return info;
164  }
165  }
166 }
readonly Api.Models.Instance metadata
The Api.Models.Instance for the SessionPersistor
CompileJob CompileJob
The CompileJob of the .dmb
Definition: IDmbProvider.cs:24
Handles saving and loading ReattachInformation.
async Task< ReattachInformation > Load(CancellationToken cancellationToken)
Load a saved ReattachInformation.
readonly IDmbFactory dmbFactory
The IDmbFactory for the SessionPersistor
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
Parameters necessary for duplicating a ISessionController session
readonly IProcessExecutor processExecutor
The IProcessExecutor for the SessionPersistor.
RebootState
Represents the action to take when /world/Reboot() is called
Definition: RebootState.cs:6
ushort Port
The port DreamDaemon was last listening on
DreamDaemonSecurity LaunchSecurityLevel
The DreamDaemonSecurity level DreamDaemon was launched with.
IDmbProvider Dmb
The IDmbProvider used by DreamDaemon
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance
SessionPersistor(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, IProcessExecutor processExecutor, ILogger< SessionPersistor > logger, Api.Models.Instance metadata)
Construct a SessionPersistor
readonly ILogger< SessionPersistor > logger
The ILogger for the SessionPersistor
RebootState RebootState
The current DreamDaemon reboot state
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the SessionPersistor