tgstation-server  4.4.0
The /tg/station 13 server suite
BasicWatchdog.cs
Go to the documentation of this file.
1 using Microsoft.Extensions.Logging;
2 using System;
3 using System.Globalization;
4 using System.Threading;
5 using System.Threading.Tasks;
13 using Tgstation.Server.Host.IO;
15 
16 namespace Tgstation.Server.Host.Components.Watchdog
17 {
22  {
24  public sealed override bool AlphaIsActive => true;
25 
27  public sealed override Models.CompileJob ActiveCompileJob => Server?.Dmb.CompileJob;
28 
30  public sealed override RebootState? RebootState => Server?.RebootState;
31 
35  protected ISessionController Server { get; private set; }
36 
41 
59  public BasicWatchdog(
60  IChatManager chat,
61  ISessionControllerFactory sessionControllerFactory,
62  IDmbFactory dmbFactory,
63  ISessionPersistor sessionPersistor,
64  IDatabaseContextFactory databaseContextFactory,
65  IJobManager jobManager,
66  IServerControl serverControl,
67  IAsyncDelayer asyncDelayer,
68  IIOManager diagnosticsIOManager,
69  IEventConsumer eventConsumer,
70  ILogger<BasicWatchdog> logger,
71  DreamDaemonLaunchParameters initialLaunchParameters,
72  Api.Models.Instance instance,
73  bool autoStart)
74  : base(
75  chat,
76  sessionControllerFactory,
77  dmbFactory,
78  sessionPersistor,
79  databaseContextFactory,
80  jobManager,
81  serverControl,
82  asyncDelayer,
83  diagnosticsIOManager,
84  eventConsumer,
85  logger,
86  initialLaunchParameters,
87  instance,
88  autoStart)
89  { }
90 
92  protected override async Task<MonitorAction> HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
93  {
94  switch (reason)
95  {
96  case MonitorActivationReason.ActiveServerCrashed:
97  string exitWord = Server.TerminationWasRequested ? "exited" : "crashed";
98  if (Server.RebootState == Session.RebootState.Shutdown)
99  {
100  // the time for graceful shutdown is now
101  await Chat.SendWatchdogMessage(
102  String.Format(
103  CultureInfo.InvariantCulture,
104  "Server {0}! Shutting down due to graceful termination request...",
105  exitWord),
106  false,
107  cancellationToken)
108  .ConfigureAwait(false);
109  return MonitorAction.Exit;
110  }
111 
112  await Chat.SendWatchdogMessage(
113  String.Format(
114  CultureInfo.InvariantCulture,
115  "Server {0}! Rebooting...",
116  exitWord),
117  false,
118  cancellationToken)
119  .ConfigureAwait(false);
120  return MonitorAction.Restart;
121  case MonitorActivationReason.ActiveServerRebooted:
122  var rebootState = Server.RebootState;
123  if (gracefulRebootRequired && rebootState == Session.RebootState.Normal)
124  {
125  Logger.LogError("Watchdog reached normal reboot state with gracefulRebootRequired set!");
126  rebootState = Session.RebootState.Restart;
127  }
128 
129  gracefulRebootRequired = false;
130  Server.ResetRebootState();
131 
132  switch (rebootState)
133  {
134  case Session.RebootState.Normal:
135  return HandleNormalReboot();
136  case Session.RebootState.Restart:
137  return MonitorAction.Restart;
138  case Session.RebootState.Shutdown:
139  // graceful shutdown time
140  await Chat.SendWatchdogMessage(
141  "Active server rebooted! Shutting down due to graceful termination request...",
142  false,
143  cancellationToken)
144  .ConfigureAwait(false);
145  return MonitorAction.Exit;
146  default:
147  throw new InvalidOperationException($"Invalid reboot state: {rebootState}");
148  }
149 
150  case MonitorActivationReason.ActiveLaunchParametersUpdated:
151  await Server.SetRebootState(Session.RebootState.Restart, cancellationToken).ConfigureAwait(false);
152  gracefulRebootRequired = true;
153  break;
154  case MonitorActivationReason.NewDmbAvailable:
155  await HandleNewDmbAvailable(cancellationToken).ConfigureAwait(false);
156  break;
157  case MonitorActivationReason.Heartbeat:
158  default:
159  throw new InvalidOperationException($"Invalid activation reason: {reason}");
160  }
161 
162  return MonitorAction.Continue;
163  }
164 
166  protected override void DisposeAndNullControllersImpl()
167  {
168  Server?.Dispose();
169  Server = null;
170  gracefulRebootRequired = false;
171  }
172 
174  protected sealed override ISessionController GetActiveController() => Server;
175 
177  protected override async Task InitControllers(
178  Task chatTask,
179  ReattachInformation reattachInfo,
180  CancellationToken cancellationToken)
181  {
182  // don't need a new dmb if reattaching
183  var reattachInProgress = reattachInfo != null;
184  var dmbToUse = reattachInProgress ? null : DmbFactory.LockNextDmb(1);
185 
186  // if this try catches something, both servers are killed
187  try
188  {
189  // start the alpha server task, either by launch a new process or attaching to an existing one
190  // The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
191  // The tasks pertaining to server startup times are in the ISessionControllers
192  Task<ISessionController> serverLaunchTask;
193  if (!reattachInProgress)
194  {
195  dmbToUse = await PrepServerForLaunch(dmbToUse, cancellationToken).ConfigureAwait(false);
196  serverLaunchTask = SessionControllerFactory.LaunchNew(
197  dmbToUse,
198  null,
199  ActiveLaunchParameters,
200  false,
201  cancellationToken);
202  }
203  else
204  serverLaunchTask = SessionControllerFactory.Reattach(reattachInfo, cancellationToken);
205 
206  // retrieve the session controller
207  Server = await serverLaunchTask.ConfigureAwait(false);
208 
209  // failed reattaches will return null
210  Server?.SetHighPriority();
211 
212  // possiblity of null servers due to failed reattaches
213  if (Server == null)
214  {
215  await ReattachFailure(
216  chatTask,
217  cancellationToken)
218  .ConfigureAwait(false);
219  return;
220  }
221 
222  await CheckLaunchResult(Server, "Server", cancellationToken).ConfigureAwait(false);
223 
224  Server.EnableCustomChatCommands();
225  }
226  catch
227  {
228  // kill the controllers
229  bool serverWasActive = Server != null;
230  DisposeAndNullControllers();
231 
232  // server didn't get control of this dmb
233  if (dmbToUse != null && !serverWasActive)
234  dmbToUse.Dispose();
235 
236  throw;
237  }
238  }
239 
244  protected virtual MonitorAction HandleNormalReboot()
245  {
246  bool dmbUpdatePending = ActiveLaunchParameters != LastLaunchParameters;
247  return dmbUpdatePending ? MonitorAction.Restart : MonitorAction.Continue;
248  }
249 
255  protected virtual Task HandleNewDmbAvailable(CancellationToken cancellationToken)
256  {
257  gracefulRebootRequired = true;
258  if (Server.Dmb.CompileJob.DMApiVersion == null)
259  return Chat.SendWatchdogMessage(
260  "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.",
261  true,
262  cancellationToken);
263  return Server.SetRebootState(Session.RebootState.Restart, cancellationToken);
264  }
265 
272  protected virtual Task<IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) => Task.FromResult(dmbToUse);
273 
275  public override Task ResetRebootState(CancellationToken cancellationToken)
276  {
277  if (!gracefulRebootRequired)
278  return base.ResetRebootState(cancellationToken);
279 
280  return Restart(true, cancellationToken);
281  }
282 
284  public sealed override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
285  => Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask;
286  }
287 }
async Task< ISessionController > Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Create a ISessionController from an existing DreamDaemon instance
Handles saving and loading ReattachInformation.
bool gracefulRebootRequired
If the server is set to gracefully reboot due to a pending dmb or settings change.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
virtual Task HandleNewDmbAvailable(CancellationToken cancellationToken)
Handler for MonitorActivationReason.NewDmbAvailable.
virtual MonitorAction HandleNormalReboot()
Handler for MonitorActivationReason.ActiveServerRebooted when the RebootState is RebootState.Normal.
Parameters necessary for duplicating a ISessionController session
override void DisposeAndNullControllersImpl()
Call IDisposable.Dispose and null the fields for all ISessionControllers.
override async Task InitControllers(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken)
Starts all ISessionControllers.
For managing connected chat services
Definition: IChatManager.cs:13
RebootState
Represents the action to take when /world/Reboot() is called
Definition: RebootState.cs:6
MonitorActivationReason
Reasons for the monitor to wake up
Consumes EventTypes and takes the appropriate actions
BasicWatchdog(IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IDatabaseContextFactory databaseContextFactory, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, ILogger< BasicWatchdog > logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
Initializes a new instance of the BasicWatchdog .
Task Restart()
Restarts the Host
Handles communication with a DreamDaemon IProcess
override async Task< MonitorAction > HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
Handles the actions to take when the monitor has to "wake up"
Manages the runtime of Jobs
Definition: IJobManager.cs:13
async Task< ISessionController > LaunchNew(IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, DreamDaemonLaunchParameters launchParameters, bool apiValidate, CancellationToken cancellationToken)
Create a ISessionController from a freshly launch DreamDaemon instance
Provides absolute paths to the latest compiled .dmbs
Definition: IDmbProvider.cs:9
Interface for using filesystems
Definition: IIOManager.cs:11
MonitorAction
The action for the monitor loop to take when control is returned to it
Definition: MonitorAction.cs:6
override Task ResetRebootState(CancellationToken cancellationToken)
Cancels pending graceful actions
IDmbProvider LockNextDmb(int lockCount)
Gets the next IDmbProvider
Definition: DmbFactory.cs:153
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...