tgstation-server 5.12.7
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
61 IChatManager chat,
62 ISessionControllerFactory sessionControllerFactory,
63 IDmbFactory dmbFactory,
64 ISessionPersistor sessionPersistor,
66 IServerControl serverControl,
67 IAsyncDelayer asyncDelayer,
71 ILogger<BasicWatchdog> logger,
72 DreamDaemonLaunchParameters initialLaunchParameters,
73 Api.Models.Instance instance,
74 bool autoStart)
75 : base(
76 chat,
77 sessionControllerFactory,
78 dmbFactory,
79 sessionPersistor,
81 serverControl,
82 asyncDelayer,
86 logger,
87 initialLaunchParameters,
88 instance,
90 {
91 }
92
94 public override Task ResetRebootState(CancellationToken cancellationToken)
95 {
97 return base.ResetRebootState(cancellationToken);
98
99 return Restart(true, cancellationToken);
100 }
101
103 public sealed override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
104 => Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask;
105
107 protected override async Task<MonitorAction> HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
108 {
109 switch (reason)
110 {
111 case MonitorActivationReason.ActiveServerCrashed:
112 var eventType = Server.TerminationWasRequested
113 ? EventType.WorldEndProcess
114 : EventType.WatchdogCrash;
115 await HandleEventImpl(eventType, Enumerable.Empty<string>(), false, cancellationToken);
116
117 var exitWord = Server.TerminationWasRequested ? "exited" : "crashed";
118 if (Server.RebootState == Session.RebootState.Shutdown)
119 {
120 // the time for graceful shutdown is now
122 String.Format(
123 CultureInfo.InvariantCulture,
124 "Server {0}! Shutting down due to graceful termination request...",
125 exitWord));
126 return MonitorAction.Exit;
127 }
128
130 String.Format(
131 CultureInfo.InvariantCulture,
132 "Server {0}! Rebooting...",
133 exitWord));
134 return MonitorAction.Restart;
135 case MonitorActivationReason.ActiveServerRebooted:
136 var rebootState = Server.RebootState;
137 if (gracefulRebootRequired && rebootState == Session.RebootState.Normal)
138 {
139 Logger.LogError("Watchdog reached normal reboot state with gracefulRebootRequired set!");
140 rebootState = Session.RebootState.Restart;
141 }
142
144 Server.ResetRebootState();
145
146 var eventTask = HandleEventImpl(EventType.WorldReboot, Enumerable.Empty<string>(), false, cancellationToken);
147 try
148 {
149 switch (rebootState)
150 {
151 case Session.RebootState.Normal:
152 return await HandleNormalReboot(cancellationToken);
153 case Session.RebootState.Restart:
154 return MonitorAction.Restart;
155 case Session.RebootState.Shutdown:
156 // graceful shutdown time
158 "Active server rebooted! Shutting down due to graceful termination request...");
159 return MonitorAction.Exit;
160 default:
161 throw new InvalidOperationException($"Invalid reboot state: {rebootState}");
162 }
163 }
164 finally
165 {
166 await eventTask;
167 }
168
169 case MonitorActivationReason.ActiveLaunchParametersUpdated:
170 await Server.SetRebootState(Session.RebootState.Restart, cancellationToken);
172 break;
173 case MonitorActivationReason.NewDmbAvailable:
174 await HandleNewDmbAvailable(cancellationToken);
175 break;
176 case MonitorActivationReason.ActiveServerPrimed:
177 await HandleEventImpl(EventType.WorldPrime, Enumerable.Empty<string>(), false, cancellationToken);
178 break;
179 case MonitorActivationReason.ActiveServerStartup:
180 break; // unused in BasicWatchdog
181 case MonitorActivationReason.HealthCheck:
182 default:
183 throw new InvalidOperationException($"Invalid activation reason: {reason}");
184 }
185
186 return MonitorAction.Continue;
187 }
188
190 protected override async Task DisposeAndNullControllersImpl()
191 {
192 var disposeTask = Server?.DisposeAsync();
194 if (!disposeTask.HasValue)
195 return;
196
197 await disposeTask.Value;
198 Server = null;
199 }
200
202 protected sealed override ISessionController GetActiveController() => Server;
203
205 protected override async Task InitController(
206 Task eventTask,
207 ReattachInformation reattachInfo,
208 CancellationToken cancellationToken)
209 {
210 // don't need a new dmb if reattaching
211 var reattachInProgress = reattachInfo != null;
212 var dmbToUse = reattachInProgress ? null : DmbFactory.LockNextDmb(1);
213
214 // if this try catches something, both servers are killed
215 try
216 {
217 // start the alpha server task, either by launch a new process or attaching to an existing one
218 // The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
219 // The tasks pertaining to server startup times are in the ISessionControllers
220 Task<ISessionController> serverLaunchTask;
221 if (!reattachInProgress)
222 {
223 Logger.LogTrace("Initializing controller with CompileJob {compileJobId}...", dmbToUse.CompileJob.Id);
224 await BeforeApplyDmb(dmbToUse.CompileJob, cancellationToken);
225 dmbToUse = await PrepServerForLaunch(dmbToUse, cancellationToken);
226
227 await eventTask;
228 serverLaunchTask = SessionControllerFactory.LaunchNew(
229 dmbToUse,
230 null,
232 false,
233 cancellationToken);
234 }
235 else
236 {
237 await eventTask;
238 serverLaunchTask = SessionControllerFactory.Reattach(reattachInfo, cancellationToken);
239 }
240
241 // retrieve the session controller
242 Server = await serverLaunchTask;
243
244 // possiblity of null servers due to failed reattaches
245 if (Server == null)
246 {
247 await ReattachFailure(
248 cancellationToken);
249 return;
250 }
251
252 if (!reattachInProgress)
253 await SessionStartupPersist(cancellationToken);
254
255 await CheckLaunchResult(Server, "Server", cancellationToken);
256
257 Server.EnableCustomChatCommands();
258 }
259 catch (Exception ex)
260 {
261 Logger.LogTrace(ex, "Controller initialization failure!");
262
263 // kill the controllers
264 bool serverWasActive = Server != null;
265
266 // DCT: Operation must always run
267 await DisposeAndNullControllers(CancellationToken.None);
268
269 // server didn't get control of this dmb
270 if (dmbToUse != null && !serverWasActive)
271 dmbToUse.Dispose();
272
273 throw;
274 }
275 }
276
282 protected virtual Task SessionStartupPersist(CancellationToken cancellationToken)
283 {
284 return SessionPersistor.Save(Server.ReattachInformation, cancellationToken);
285 }
286
292 protected virtual Task<MonitorAction> HandleNormalReboot(CancellationToken cancellationToken)
293 {
294 var settingsUpdatePending = ActiveLaunchParameters != LastLaunchParameters;
295 var result = settingsUpdatePending ? MonitorAction.Restart : MonitorAction.Continue;
296 return Task.FromResult(result);
297 }
298
304 protected virtual async Task HandleNewDmbAvailable(CancellationToken cancellationToken)
305 {
307 if (Server.CompileJob.DMApiVersion == null)
308 {
310 "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.");
311 return;
312 }
313
314 await Server.SetRebootState(Session.RebootState.Restart, cancellationToken);
315 }
316
323 protected virtual Task<IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) => Task.FromResult(dmbToUse);
324 }
325}
IDmbProvider LockNextDmb(int lockCount)
Gets the next IDmbProvider. A new IDmbProvider.
Definition: DmbFactory.cs:163
Parameters necessary for duplicating a ISessionController session.
async Task< ISessionController > LaunchNew(IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, DreamDaemonLaunchParameters launchParameters, bool apiValidate, CancellationToken cancellationToken)
Create a ISessionController from a freshly launch DreamDaemon instance. A Task<TResult> resulting in ...
async Task< ISessionController > Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Create a ISessionController from an existing DreamDaemon instance. A Task<TResult> resulting in a new...
Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Save some reattachInformation . A Task representing the running operation.
virtual Task< IDmbProvider > PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken)
Prepare the server to launch a new instance with the WatchdogBase.ActiveLaunchParameters and a given ...
override async Task DisposeAndNullControllersImpl()
Call IDisposable.Dispose and null the fields for all ISessionControllers. A Task representing the run...
virtual Task SessionStartupPersist(CancellationToken cancellationToken)
Called to save the current Server into the WatchdogBase.SessionPersistor when initially launched.
override Task ResetRebootState(CancellationToken cancellationToken)
Cancels pending graceful actions. A Task representing the running operation.
BasicWatchdog(IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, ILogger< BasicWatchdog > logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
Initializes a new instance of the BasicWatchdog class.
virtual async Task HandleNewDmbAvailable(CancellationToken cancellationToken)
Handler for MonitorActivationReason.NewDmbAvailable.
override async Task< MonitorAction > HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
Handles the actions to take when the monitor has to "wake up". A Task<TResult> resulting in the Monit...
ISessionController Server
The single ISessionController.
sealed override ISessionController GetActiveController()
Get the active ISessionController. The active ISessionController.
virtual Task< MonitorAction > HandleNormalReboot(CancellationToken cancellationToken)
Handler for MonitorActivationReason.ActiveServerRebooted when the RebootState is RebootState....
override async Task InitController(Task eventTask, ReattachInformation reattachInfo, CancellationToken cancellationToken)
Starts all ISessionControllers. A Task representing the running operation.
bool gracefulRebootRequired
If the server is set to gracefully reboot due to a pending dmb or settings change.
sealed override bool AlphaIsActive
If the alpha server is the active server.
sealed override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
Called when the owning Instance is renamed. A Task representing the running operation.
readonly IJobManager jobManager
The IJobManager for the WatchdogBase.
ILogger< WatchdogBase > Logger
The ILogger for the WatchdogBase.
Definition: WatchdogBase.cs:73
async Task ReattachFailure(CancellationToken cancellationToken)
Call from InitController(Task, ReattachInformation, CancellationToken) when a reattach operation fail...
async Task Restart(bool graceful, CancellationToken cancellationToken)
Restarts the watchdog. A Task representing the running operation.
readonly bool autoStart
If the WatchdogBase should LaunchNoLock(bool, bool, bool, ReattachInformation, CancellationToken) in ...
readonly IEventConsumer eventConsumer
The IEventConsumer that is not the WatchdogBase.
async Task HandleEventImpl(EventType eventType, IEnumerable< string > parameters, bool relayToSession, CancellationToken cancellationToken)
Handle a given eventType without re-throwing errors.
DreamDaemonLaunchParameters LastLaunchParameters
The DreamDaemonLaunchParameters the active server is using.
Definition: WatchdogBase.cs:52
readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory
The IRemoteDeploymentManagerFactory for the WatchdogBase.
async Task DisposeAndNullControllers(CancellationToken cancellationToken)
Wrapper for DisposeAndNullControllersImpl under a locked context.
readonly IIOManager diagnosticsIOManager
The IIOManager pointing to the Diagnostics directory.
async Task CheckLaunchResult(ISessionController controller, string serverName, CancellationToken cancellationToken)
Check the LaunchResult of a given controller for errors and throw a JobException if any are detected...
DreamDaemonLaunchParameters ActiveLaunchParameters
The DreamDaemonLaunchParameters to be applied.
Definition: WatchdogBase.cs:49
async Task BeforeApplyDmb(Models.CompileJob newCompileJob, CancellationToken cancellationToken)
To be called before a given newCompileJob goes live.
IChatManager Chat
The IChatManager for the WatchdogBase.
Definition: WatchdogBase.cs:78
For managing connected chat services.
Definition: IChatManager.cs:15
void QueueWatchdogMessage(string message)
Queue a chat message to configured watchdog channels.
Provides absolute paths to the latest compiled .dmbs.
Definition: IDmbProvider.cs:11
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.
Definition: IJobManager.cs:13
EventType
Types of events. Mirror in tgs.dm.
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.
Definition: MonitorAction.cs:7
MonitorActivationReason
Reasons for the monitor to wake up.