tgstation-server 6.12.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
19
21{
26 {
28 public sealed override bool AlphaIsActive => true;
29
31 public sealed override RebootState? RebootState => Server?.RebootState;
32
36 protected ISessionController? Server { get; private set; }
37
42
62 IChatManager chat,
63 ISessionControllerFactory sessionControllerFactory,
64 IDmbFactory dmbFactory,
65 ISessionPersistor sessionPersistor,
67 IServerControl serverControl,
68 IAsyncDelayer asyncDelayer,
72 IIOManager gameIOManager,
73 ILogger<BasicWatchdog> logger,
74 DreamDaemonLaunchParameters initialLaunchParameters,
75 Api.Models.Instance instance,
76 bool autoStart)
77 : base(
78 chat,
79 sessionControllerFactory,
80 dmbFactory,
81 sessionPersistor,
83 serverControl,
84 asyncDelayer,
88 gameIOManager,
89 logger,
90 initialLaunchParameters,
91 instance,
93 {
94 }
95
97 public override ValueTask ResetRebootState(CancellationToken cancellationToken)
98 {
100 return base.ResetRebootState(cancellationToken);
101
102 return Restart(true, cancellationToken);
103 }
104
106 public sealed override ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
107 => Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? ValueTask.CompletedTask;
108
110 protected override async ValueTask<MonitorAction> HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
111 {
112 var controller = Server!;
113 switch (reason)
114 {
115 case MonitorActivationReason.ActiveServerCrashed:
116 var eventType = controller.TerminationWasIntentional
117 ? EventType.WorldEndProcess
118 : EventType.WatchdogCrash;
119 await HandleEventImpl(eventType, Enumerable.Empty<string>(), false, cancellationToken);
120
121 var exitWord = controller.TerminationWasIntentional ? "exited" : "crashed";
122 if (controller.RebootState == Session.RebootState.Shutdown)
123 {
124 // the time for graceful shutdown is now
126 String.Format(
127 CultureInfo.InvariantCulture,
128 "Server {0}! Shutting down due to graceful termination request...",
129 exitWord));
130 return MonitorAction.Exit;
131 }
132
134 String.Format(
135 CultureInfo.InvariantCulture,
136 "Server {0}! Rebooting...",
137 exitWord));
138 return MonitorAction.Restart;
139 case MonitorActivationReason.ActiveServerRebooted:
140 var rebootState = controller.RebootState;
141 if (gracefulRebootRequired && rebootState == Session.RebootState.Normal)
142 {
143 Logger.LogError("Watchdog reached normal reboot state with gracefulRebootRequired set!");
144 rebootState = Session.RebootState.Restart;
145 }
146
148 controller.ResetRebootState();
149
150 var eventTask = HandleEventImpl(EventType.WorldReboot, Enumerable.Empty<string>(), false, cancellationToken);
151 try
152 {
153 switch (rebootState)
154 {
155 case Session.RebootState.Normal:
156 return await HandleNormalReboot(cancellationToken);
157 case Session.RebootState.Restart:
158 return MonitorAction.Restart;
159 case Session.RebootState.Shutdown:
160 // graceful shutdown time
162 "Active server rebooted! Shutting down due to graceful termination request...");
163 return MonitorAction.Exit;
164 default:
165 throw new InvalidOperationException($"Invalid reboot state: {rebootState}");
166 }
167 }
168 finally
169 {
170 await eventTask;
171 }
172
173 case MonitorActivationReason.ActiveLaunchParametersUpdated:
174 await controller.SetRebootState(Session.RebootState.Restart, cancellationToken);
176 break;
177 case MonitorActivationReason.NewDmbAvailable:
178 await HandleNewDmbAvailable(cancellationToken);
179 break;
180 case MonitorActivationReason.ActiveServerPrimed:
181 await HandleEventImpl(EventType.WorldPrime, Enumerable.Empty<string>(), false, cancellationToken);
182 break;
183 case MonitorActivationReason.ActiveServerStartup:
184 Status = Api.Models.WatchdogStatus.Online;
185 break;
186 case MonitorActivationReason.HealthCheck:
187 default:
188 throw new InvalidOperationException($"Invalid activation reason: {reason}");
189 }
190
191 return MonitorAction.Continue;
192 }
193
195 protected override async ValueTask DisposeAndNullControllersImpl()
196 {
197 var disposeTask = Server?.DisposeAsync();
199 if (!disposeTask.HasValue)
200 return;
201
202 await disposeTask.Value;
203 Server = null;
204 }
205
207 protected sealed override ISessionController? GetActiveController() => Server;
208
210 protected override async ValueTask InitController(
211 ValueTask eventTask,
212 ReattachInformation? reattachInfo,
213 CancellationToken cancellationToken)
214 {
215 // don't need a new dmb if reattaching
216 var reattachInProgress = reattachInfo != null;
217 var dmbToUse = reattachInProgress ? null : DmbFactory.LockNextDmb("Watchdog initialization");
218
219 // if this try catches something, both servers are killed
220 try
221 {
222 // start the alpha server task, either by launch a new process or attaching to an existing one
223 // The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
224 // The tasks pertaining to server startup times are in the ISessionControllers
225 if (!reattachInProgress)
226 {
227 Logger.LogTrace("Initializing controller with CompileJob {compileJobId}...", dmbToUse!.CompileJob.Id);
228 await BeforeApplyDmb(dmbToUse.CompileJob, cancellationToken);
229 dmbToUse = await PrepServerForLaunch(dmbToUse, cancellationToken);
230
231 await eventTask;
233 dmbToUse,
234 null,
236 false,
237 cancellationToken);
238 }
239 else
240 {
241 await eventTask;
242 Server = await SessionControllerFactory.Reattach(reattachInfo!, cancellationToken);
243 }
244
245 // possiblity of null servers due to failed reattaches
246 if (Server == null)
247 {
248 await ReattachFailure(
249 cancellationToken);
250 return;
251 }
252
253 if (!reattachInProgress)
254 await SessionStartupPersist(cancellationToken);
255
256 if (!SessionId.HasValue)
257 Logger.LogError("Server should have a session ID allocated by now but it doesn't!");
258 else
259 Logger.LogInformation("Watchdog starting session ID {id}", SessionId.Value);
260
261 await CheckLaunchResult(Server, "Server", cancellationToken);
262
263 // persist again, because the DMAPI can say we need a different topic port (Original OD behavior)
264 // kinda hacky imo, but at least we can safely forget about this
265 if (!reattachInProgress)
266 await SessionPersistor.Update(Server.ReattachInformation, cancellationToken);
267 }
268 catch (Exception ex)
269 {
270 Logger.LogTrace(ex, "Controller initialization failure!");
271
272 // kill the controllers
273 bool serverWasActive = Server != null;
274
275 // DCT: Operation must always run
276 await DisposeAndNullControllers(CancellationToken.None);
277
278 // server didn't get control of this dmb
279 if (dmbToUse != null && !serverWasActive)
280 await dmbToUse.DisposeAsync();
281
282 throw;
283 }
284 }
285
291 protected virtual ValueTask SessionStartupPersist(CancellationToken cancellationToken)
292 => SessionPersistor.Save(Server!.ReattachInformation, cancellationToken);
293
299 protected virtual ValueTask<MonitorAction> HandleNormalReboot(CancellationToken cancellationToken)
300 {
301 if (Server!.CompileJob.DMApiVersion != null)
302 Status = Api.Models.WatchdogStatus.Restoring;
303
304 return ValueTask.FromResult(MonitorAction.Continue);
305 }
306
312 protected virtual async ValueTask HandleNewDmbAvailable(CancellationToken cancellationToken)
313 {
315 if (Server!.CompileJob.DMApiVersion == null)
316 {
318 "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.");
319 return;
320 }
321
322 await Server.SetRebootState(Session.RebootState.Restart, cancellationToken);
323 }
324
331 protected virtual ValueTask<IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) => ValueTask.FromResult(dmbToUse);
332 }
333}
virtual ? long Id
The ID of the entity.
Definition EntityId.cs:13
virtual ? Version DMApiVersion
The DMAPI Version.
Definition CompileJob.cs:41
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.
BasicWatchdog(IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IIOManager gameIOManager, ILogger< BasicWatchdog > logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
Initializes a new instance of the BasicWatchdog class.
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.
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.