tgstation-server 6.14.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
BasicWatchdog.cs
Go to the documentation of this file.
1using System;
2using System.Globalization;
3using System.Linq;
4using System.Threading;
5using System.Threading.Tasks;
6
7using Microsoft.Extensions.Logging;
8
9using Prometheus;
10
21
23{
28 {
30 public sealed override bool AlphaIsActive => true;
31
33 public sealed override RebootState? RebootState => Server?.RebootState;
34
38 protected ISessionController? Server { get; private set; }
39
44
65 IChatManager chat,
66 ISessionControllerFactory sessionControllerFactory,
67 IDmbFactory dmbFactory,
68 ISessionPersistor sessionPersistor,
70 IServerControl serverControl,
71 IAsyncDelayer asyncDelayer,
75 IMetricFactory metricFactory,
76 IIOManager gameIOManager,
77 ILogger<BasicWatchdog> logger,
78 DreamDaemonLaunchParameters initialLaunchParameters,
79 Api.Models.Instance instance,
80 bool autoStart)
81 : base(
82 chat,
83 sessionControllerFactory,
84 dmbFactory,
85 sessionPersistor,
87 serverControl,
88 asyncDelayer,
92 metricFactory,
93 gameIOManager,
94 logger,
95 initialLaunchParameters,
96 instance,
98 {
99 }
100
102 public override ValueTask ResetRebootState(CancellationToken cancellationToken)
103 {
105 return base.ResetRebootState(cancellationToken);
106
107 return Restart(true, cancellationToken);
108 }
109
111 public sealed override ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
112 => Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? ValueTask.CompletedTask;
113
115 protected override async ValueTask<MonitorAction> HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
116 {
117 var controller = Server!;
118 switch (reason)
119 {
120 case MonitorActivationReason.ActiveServerCrashed:
121 var eventType = controller.TerminationWasIntentional
122 ? EventType.WorldEndProcess
123 : EventType.WatchdogCrash;
124 await HandleEventImpl(eventType, Enumerable.Empty<string>(), false, cancellationToken);
125
126 var exitWord = controller.TerminationWasIntentional ? "exited" : "crashed";
127 if (controller.RebootState == Session.RebootState.Shutdown)
128 {
129 // the time for graceful shutdown is now
131 String.Format(
132 CultureInfo.InvariantCulture,
133 "Server {0}! Shutting down due to graceful termination request...",
134 exitWord));
135 return MonitorAction.Exit;
136 }
137
139 String.Format(
140 CultureInfo.InvariantCulture,
141 "Server {0}! Rebooting...",
142 exitWord));
143 return MonitorAction.Restart;
144 case MonitorActivationReason.ActiveServerRebooted:
145 var rebootState = controller.RebootState;
146 if (gracefulRebootRequired && rebootState == Session.RebootState.Normal)
147 {
148 Logger.LogError("Watchdog reached normal reboot state with gracefulRebootRequired set!");
149 rebootState = Session.RebootState.Restart;
150 }
151
153 controller.ResetRebootState();
154
155 var eventTask = HandleEventImpl(EventType.WorldReboot, Enumerable.Empty<string>(), false, cancellationToken);
156 try
157 {
158 switch (rebootState)
159 {
160 case Session.RebootState.Normal:
161 return await HandleNormalReboot(cancellationToken);
162 case Session.RebootState.Restart:
163 return MonitorAction.Restart;
164 case Session.RebootState.Shutdown:
165 // graceful shutdown time
167 "Active server rebooted! Shutting down due to graceful termination request...");
168 return MonitorAction.Exit;
169 default:
170 throw new InvalidOperationException($"Invalid reboot state: {rebootState}");
171 }
172 }
173 finally
174 {
175 await eventTask;
176 }
177
178 case MonitorActivationReason.ActiveLaunchParametersUpdated:
179 await controller.SetRebootState(Session.RebootState.Restart, cancellationToken);
181 break;
182 case MonitorActivationReason.NewDmbAvailable:
183 await HandleNewDmbAvailable(cancellationToken);
184 break;
185 case MonitorActivationReason.ActiveServerPrimed:
186 await HandleEventImpl(EventType.WorldPrime, Enumerable.Empty<string>(), false, cancellationToken);
187 break;
188 case MonitorActivationReason.ActiveServerStartup:
189 Status = Api.Models.WatchdogStatus.Online;
190 break;
191 case MonitorActivationReason.HealthCheck:
192 default:
193 throw new InvalidOperationException($"Invalid activation reason: {reason}");
194 }
195
196 return MonitorAction.Continue;
197 }
198
200 protected override async ValueTask DisposeAndNullControllersImpl()
201 {
202 var disposeTask = Server?.DisposeAsync();
204 if (!disposeTask.HasValue)
205 return;
206
207 await disposeTask.Value;
208 Server = null;
209 }
210
212 protected sealed override ISessionController? GetActiveController() => Server;
213
215 protected override async ValueTask InitController(
216 ValueTask eventTask,
217 ReattachInformation? reattachInfo,
218 CancellationToken cancellationToken)
219 {
220 // don't need a new dmb if reattaching
221 var reattachInProgress = reattachInfo != null;
222 var dmbToUse = reattachInProgress ? null : DmbFactory.LockNextDmb("Watchdog initialization");
223
224 // if this try catches something, both servers are killed
225 try
226 {
227 // start the alpha server task, either by launch a new process or attaching to an existing one
228 // The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
229 // The tasks pertaining to server startup times are in the ISessionControllers
230 if (!reattachInProgress)
231 {
232 Logger.LogTrace("Initializing controller with CompileJob {compileJobId}...", dmbToUse!.CompileJob.Id);
233 await BeforeApplyDmb(dmbToUse.CompileJob, cancellationToken);
234 dmbToUse = await PrepServerForLaunch(dmbToUse, cancellationToken);
235
236 await eventTask;
238 dmbToUse,
239 null,
241 false,
242 cancellationToken);
243 }
244 else
245 {
246 await eventTask;
247 Server = await SessionControllerFactory.Reattach(reattachInfo!, cancellationToken);
248 }
249
250 // possiblity of null servers due to failed reattaches
251 if (Server == null)
252 {
253 await ReattachFailure(
254 cancellationToken);
255 return;
256 }
257
258 if (!reattachInProgress)
259 await SessionStartupPersist(cancellationToken);
260
261 if (!SessionId.HasValue)
262 Logger.LogError("Server should have a session ID allocated by now but it doesn't!");
263 else
264 Logger.LogInformation("Watchdog starting session ID {id}", SessionId.Value);
265
266 await CheckLaunchResult(Server, "Server", cancellationToken);
267
268 // persist again, because the DMAPI can say we need a different topic port (Original OD behavior)
269 // kinda hacky imo, but at least we can safely forget about this
270 if (!reattachInProgress)
271 await SessionPersistor.Update(Server.ReattachInformation, cancellationToken);
272 }
273 catch (Exception ex)
274 {
275 Logger.LogTrace(ex, "Controller initialization failure!");
276
277 // kill the controllers
278 bool serverWasActive = Server != null;
279
280 // DCT: Operation must always run
281 await DisposeAndNullControllers(CancellationToken.None);
282
283 // server didn't get control of this dmb
284 if (dmbToUse != null && !serverWasActive)
285 await dmbToUse.DisposeAsync();
286
287 throw;
288 }
289 }
290
296 protected virtual ValueTask SessionStartupPersist(CancellationToken cancellationToken)
297 => SessionPersistor.Save(Server!.ReattachInformation, cancellationToken);
298
304 protected virtual ValueTask<MonitorAction> HandleNormalReboot(CancellationToken cancellationToken)
305 {
306 if (Server!.CompileJob.DMApiVersion != null)
307 Status = Api.Models.WatchdogStatus.Restoring;
308
309 return ValueTask.FromResult(MonitorAction.Continue);
310 }
311
317 protected virtual async ValueTask HandleNewDmbAvailable(CancellationToken cancellationToken)
318 {
320 if (Server!.CompileJob.DMApiVersion == null)
321 {
323 "A new deployment has been made but cannot be applied automatically as the currently running server has no DMAPI. Please manually reboot the server to apply the update.");
324 return;
325 }
326
327 await Server.SetRebootState(Session.RebootState.Restart, cancellationToken);
328 }
329
336 protected virtual ValueTask<IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) => ValueTask.FromResult(dmbToUse);
337 }
338}
virtual ? long Id
The ID of the entity.
Definition EntityId.cs:14
virtual ? Version DMApiVersion
The DMAPI Version.
Definition CompileJob.cs:43
IDmbProvider LockNextDmb(string reason, [CallerFilePath] string? callerFile=null, [CallerLineNumber] int callerLine=default)
Gets the next IDmbProvider. DmbAvailable is a precondition.A new IDmbProvider.
Parameters necessary for duplicating a ISessionController session.
async ValueTask< ISessionController > LaunchNew(IDmbProvider dmbProvider, IEngineExecutableLock? currentByondLock, DreamDaemonLaunchParameters launchParameters, bool apiValidate, CancellationToken cancellationToken)
Create a ISessionController from a freshly launch DreamDaemon instance.A ValueTask<TResult> resulting...
async ValueTask< ISessionController?> Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Create a ISessionController from an existing DreamDaemon instance.A ValueTask<TResult> resulting in a...
ValueTask Update(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Update some reattachInformation .A ValueTask representing the running operation.
ValueTask Save(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Save some reattachInformation .A ValueTask representing the running operation.
override ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
Called when the owning Instance is renamed.A ValueTask representing the running operation.
ISessionController? Server
The single ISessionController.
virtual ValueTask SessionStartupPersist(CancellationToken cancellationToken)
Called to save the current Server into the WatchdogBase.SessionPersistor when initially launched.
override async ValueTask InitController(ValueTask eventTask, ReattachInformation? reattachInfo, CancellationToken cancellationToken)
override ValueTask ResetRebootState(CancellationToken cancellationToken)
Cancels pending graceful actions.A ValueTask representing the running operation.
virtual ValueTask< IDmbProvider > PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken)
Prepare the server to launch a new instance with the WatchdogBase.ActiveLaunchParameters and a given ...
virtual async ValueTask HandleNewDmbAvailable(CancellationToken cancellationToken)
Handler for MonitorActivationReason.NewDmbAvailable.
BasicWatchdog(IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IMetricFactory metricFactory, IIOManager gameIOManager, ILogger< BasicWatchdog > logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
Initializes a new instance of the BasicWatchdog class.
override bool AlphaIsActive
If the alpha server is the active server.
virtual ValueTask< MonitorAction > HandleNormalReboot(CancellationToken cancellationToken)
Handler for MonitorActivationReason.ActiveServerRebooted when the RebootState is RebootState....
override async ValueTask< MonitorAction > HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
bool gracefulRebootRequired
If the server is set to gracefully reboot due to a pending dmb or settings change.
override? ISessionController GetActiveController()
readonly IJobManager jobManager
The IJobManager for the WatchdogBase.
ILogger< WatchdogBase > Logger
The ILogger for the WatchdogBase.
async ValueTask ReattachFailure(CancellationToken cancellationToken)
Call from InitController(ValueTask, ReattachInformation, CancellationToken) when a reattach operation...
readonly bool autoStart
If the WatchdogBase should LaunchNoLock(bool, bool, bool, ReattachInformation, CancellationToken) in ...
async ValueTask CheckLaunchResult(ISessionController controller, string serverName, CancellationToken cancellationToken)
Check the LaunchResult of a given controller for errors and throw a JobException if any are detected...
readonly IEventConsumer eventConsumer
The IEventConsumer that is not the WatchdogBase.
WatchdogStatus Status
The current WatchdogStatus.
readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory
The IRemoteDeploymentManagerFactory for the WatchdogBase.
readonly IIOManager diagnosticsIOManager
The IIOManager pointing to the Diagnostics directory.
DreamDaemonLaunchParameters ActiveLaunchParameters
The DreamDaemonLaunchParameters to be applied.
async ValueTask BeforeApplyDmb(Models.CompileJob newCompileJob, CancellationToken cancellationToken)
To be called before a given newCompileJob goes live.
async ValueTask DisposeAndNullControllers(CancellationToken cancellationToken)
Wrapper for DisposeAndNullControllersImpl under a locked context.
long? SessionId
An incrementing ID for representing current server execution.
IChatManager Chat
The IChatManager for the WatchdogBase.
async ValueTask HandleEventImpl(EventType eventType, IEnumerable< string > parameters, bool relayToSession, CancellationToken cancellationToken)
Handle a given eventType without re-throwing errors.
For managing connected chat services.
void QueueWatchdogMessage(string message)
Queue a chat message to configured watchdog channels.
Provides absolute paths to the latest compiled .dmbs.
Consumes EventTypes and takes the appropriate actions.
Handles communication with a DreamDaemon IProcess.
Handles saving and loading ReattachInformation.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
Interface for using filesystems.
Definition IIOManager.cs:13
Manages the runtime of Jobs.
EventType
Types of events. Mirror in tgs.dm. Prefer last listed name for script.
Definition EventType.cs:7
RebootState
Represents the action to take when /world/Reboot() is called.
Definition RebootState.cs:7
MonitorAction
The action for the monitor loop to take when control is returned to it.
@ Restart
The monitor should kill and restart both servers.
MonitorActivationReason
Reasons for the monitor to wake up.