tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
WatchdogBase.cs
Go to the documentation of this file.
1using System;
4using System.Linq;
7
9
10using Serilog.Context;
11
28
30{
34#pragma warning disable CA1506 // TODO: Decomplexify
36 {
39
41 public uint? ClientCount { get; private set; }
42
45
48 {
49 get => status;
50 protected set
51 {
53 status = value;
54 Logger.LogTrace("Status set from {oldStatus} to {status}", oldStatus, status);
55 }
56 }
57
60
62 public abstract bool AlphaIsActive { get; }
63
66
68 public DreamDaemonLaunchParameters? LastLaunchParameters { get; protected set; }
69
71 public Models.CompileJob? ActiveCompileJob => GetActiveController()?.CompileJob;
72
74 public abstract RebootState? RebootState { get; }
75
80
84 protected ILogger<WatchdogBase> Logger { get; }
85
89 protected IChatManager Chat { get; }
90
95
99 protected IDmbFactory DmbFactory { get; }
100
104 protected IAsyncDelayer AsyncDelayer { get; }
105
109 protected IIOManager GameIOManager { get; }
110
115
120
125
130
135
140
145
150
155
159 volatile TaskCompletionSource activeParametersUpdated;
160
165
170
175
180
185
190
209 protected WatchdogBase(
210 IChatManager chat,
211 ISessionControllerFactory sessionControllerFactory,
212 IDmbFactory dmbFactory,
215 IServerControl serverControl,
216 IAsyncDelayer asyncDelayer,
220 IIOManager gameIOManager,
223 Api.Models.Instance metadata,
224 bool autoStart)
225 {
226 Chat = chat ?? throw new ArgumentNullException(nameof(chat));
227 SessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
228 DmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
231 AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
235 GameIOManager = gameIOManager ?? throw new ArgumentNullException(nameof(gameIOManager));
236 Logger = logger ?? throw new ArgumentNullException(nameof(logger));
240
241 ArgumentNullException.ThrowIfNull(serverControl);
242
243 chat.RegisterCommandHandler(this);
244
246 releaseServers = false;
247 activeParametersUpdated = new TaskCompletionSource();
248
249 restartRegistration = serverControl.RegisterForRestart(this);
250 try
251 {
254 }
255 catch
256 {
257 restartRegistration.Dispose();
258 synchronizationSemaphore?.Dispose();
259 throw;
260 }
261
262 Logger.LogTrace("Created watchdog");
263 }
264
267 {
268 Logger.LogTrace("Disposing...");
269 synchronizationSemaphore.Dispose();
270 restartRegistration.Dispose();
271
274 monitorCts?.Dispose();
275 disposed = true;
276 }
277
280 {
282 {
286 if (!currentEngine.HasValue)
287 return false;
288
289 bool match = launchParameters.CanApplyWithoutReboot(currentLaunchParameters, currentEngine.Value);
290 if (match || Status == WatchdogStatus.Offline || Status == WatchdogStatus.DelayedRestart)
291 return false;
292
293 var oldTcs = Interlocked.Exchange(ref activeParametersUpdated, new TaskCompletionSource());
294 oldTcs.SetResult();
295 }
296
297 return true;
298 }
299
302 {
304 {
308 if (Status != WatchdogStatus.Online || activeServer == null)
309 return new MessageContent
310 {
311 Text = "TGS: Server offline!",
312 };
313
315
316 if (commandResult == null)
317 return new MessageContent
318 {
319 Text = "TGS: Bad topic exchange!",
320 };
321
322 if (commandResult == null)
323 return new MessageContent
324 {
325 Text = "TGS: Bad topic response!",
326 };
327
329 {
330 Text = commandResult.CommandResponse?.Text ?? commandResult.CommandResponseMessage,
331 Embed = commandResult.CommandResponse?.Embed,
332 };
333
334 if (commandResponse.Text == null && commandResponse.Embed == null)
335 {
336 commandResponse.Text = "TGS: Command processed but no DMAPI response returned!";
337 }
338
340
341 return commandResponse;
342 }
343 }
344
346 public async ValueTask Launch(CancellationToken cancellationToken)
347 {
348 if (Status != WatchdogStatus.Offline)
349 throw new JobException(ErrorCode.WatchdogRunning);
351 await LaunchNoLock(true, true, true, null, cancellationToken);
352 }
353
355 public virtual async ValueTask ResetRebootState(CancellationToken cancellationToken)
356 {
358 {
359 if (Status == WatchdogStatus.Offline)
360 return;
362 if (toClear != null)
363 await toClear.SetRebootState(Session.RebootState.Normal, cancellationToken);
364 }
365 }
366
368 public async ValueTask Restart(bool graceful, CancellationToken cancellationToken)
369 {
370 if (Status == WatchdogStatus.Offline)
371 throw new JobException(ErrorCode.WatchdogNotRunning);
372
373 Logger.LogTrace("Begin Restart. Graceful: {gracefulFlag}", graceful);
375 {
376 if (!graceful)
377 {
378 Chat.QueueWatchdogMessage("Manual restart triggered...");
380 await LaunchNoLock(true, false, true, null, cancellationToken);
381 return;
382 }
383
385 if (toReboot != null
386 && !await toReboot.SetRebootState(Session.RebootState.Restart, cancellationToken))
387 Logger.LogWarning("Unable to send reboot state change event!");
388 }
389 }
390
392 public async Task StartAsync(CancellationToken cancellationToken)
393 {
395 var reattaching = reattachInfo != null;
396 if (!autoStart && !reattaching)
397 return;
398
399 var job = Models.Job.Create(
401 ? JobCode.StartupWatchdogReattach
402 : JobCode.StartupWatchdogLaunch,
403 null,
404 metadata,
405 DreamDaemonRights.Shutdown);
407 job,
408 async (core, databaseContextFactory, paramJob, progressFunction, ct) =>
409 {
410 if (core?.Watchdog != this)
412
414 await LaunchNoLock(true, true, true, reattachInfo, ct);
415
417 },
419 }
420
422 public async Task StopAsync(CancellationToken cancellationToken) =>
424
431
434 {
436 {
438
439 if (Status != WatchdogStatus.Offline)
440 {
441 Logger.LogDebug("Waiting for server to gracefully shut down.");
443 }
444 else
445 Logger.LogTrace("Graceful shutdown requested but server is already offline.");
446
447 return;
448 }
449
450 releaseServers = true;
451 if (Status == WatchdogStatus.Online)
452 Chat.QueueWatchdogMessage("Detaching...");
453 else
454 Logger.LogTrace("Not sending detach chat message as status is: {status}", Status);
455 }
456
458 public abstract ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken);
459
466
468 public async ValueTask<bool> Broadcast(string message, CancellationToken cancellationToken)
469 {
470 ArgumentNullException.ThrowIfNull(message);
471
473 if (activeServer == null)
474 {
475 Logger.LogInformation("Attempted broadcast failed, no active server!");
476 return false;
477 }
478
479 if (!activeServer.DMApiAvailable)
480 {
481 Logger.LogInformation("Attempted broadcast failed, no DMAPI!");
482 return false;
483 }
484
485 var minimumRequiredVersion = new Version(5, 7, 0);
486 if (activeServer.DMApiVersion < minimumRequiredVersion)
487 {
488 Logger.LogInformation(
489 "Attempted broadcast failed, insufficient interop version: {interopVersion}. Requires {minimumRequiredVersion}!",
490 activeServer.DMApiVersion,
492 return false;
493 }
494
495 Logger.LogInformation("Broadcasting: {message}", message);
496
497 var response = await activeServer.SendCommand(
500
501 return response != null && response.ErrorMessage == null;
502 }
503
506 {
508
509 // Method explicitly implemented to prevent accidental calls when this.eventConsumer should be used.
511
512 // Server may have ended
513 if (activeServer == null)
514 return;
515
517 var result = await activeServer.SendCommand(
520
522 }
523
526 => throw new NotSupportedException("Watchdogs do not support custom events!");
527
536
547 bool startMonitor,
548 bool announce,
549 bool announceFailure,
551 CancellationToken cancellationToken)
552 {
553 Logger.LogTrace("Begin LaunchImplNoLock");
554 if (startMonitor && Status != WatchdogStatus.Offline)
555 throw new JobException(ErrorCode.WatchdogRunning);
556
557 if (reattachInfo == null && !DmbFactory.DmbAvailable)
558 throw new JobException(ErrorCode.WatchdogCompileJobCorrupted);
559
560 // this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers
561 var eventTask = ValueTask.CompletedTask;
562 if (announce)
563 {
565 reattachInfo == null
566 ? "Launching..."
567 : "Reattaching..."); // simple announce
568 if (reattachInfo == null)
569 eventTask = HandleEventImpl(EventType.WatchdogLaunch, Enumerable.Empty<string>(), false, cancellationToken);
570 }
571
572 // since neither server is running, this is safe to do
575 ClientCount = null;
576
577 try
578 {
580 }
582 {
583 Logger.LogTrace(ex, "Controller initialization cancelled!");
584 throw;
585 }
586 catch (Exception e)
587 {
588 Logger.LogWarning(e, "Failed to start watchdog!");
591 {
593 if (announceFailure)
594 Chat.QueueWatchdogMessage("Startup failed!");
595 }
596
598 throw;
599 }
600 finally
601 {
602 // finish the chat task that's in flight
603 try
604 {
606 }
608 {
609 Logger.LogTrace(ex, "Announcement task canceled!");
610 }
611 }
612
613 Logger.LogInformation("Controller(s) initialized successfully");
614
615 if (startMonitor)
616 {
619 }
620 }
621
627 {
628 Logger.LogTrace("StopMonitor");
629 if (monitorTask == null)
630 return false;
631 var wasRunning = !monitorTask.IsCompleted;
632 monitorCts!.Cancel();
634 Logger.LogTrace("Stopped Monitor");
635 monitorCts.Dispose();
636 monitorTask = null;
637 monitorCts = null;
638 return wasRunning;
639 }
640
649 {
650 var launchResult = await controller.LaunchResult.WaitAsync(cancellationToken);
651
652 // Dead sessions won't trigger this
653 if (launchResult.ExitCode.HasValue) // you killed us ray...
654 throw new JobException(
655 ErrorCode.WatchdogStartupFailed,
656 new JobException($"{serverName} failed to start: {launchResult}"));
657 if (!launchResult.StartupTime.HasValue)
658 throw new JobException(
659 ErrorCode.WatchdogStartupTimeout,
660 new JobException($"{serverName} timed out on startup: {ActiveLaunchParameters.StartupTimeout!.Value}s"));
661 }
662
669 {
670 // we lost the server, just restart entirely
671 // DCT: Operation must always run
672 await DisposeAndNullControllers(CancellationToken.None);
673 ClientCount = null;
674 const string FailReattachMessage = "Unable to properly reattach to server! Restarting watchdog...";
675 Logger.LogWarning(FailReattachMessage);
676
678 await InitController(ValueTask.CompletedTask, null, cancellationToken);
679 }
680
686
702
708
717 CancellationToken cancellationToken);
718
725 protected async ValueTask BeforeApplyDmb(Models.CompileJob newCompileJob, CancellationToken cancellationToken)
726 {
728 {
729 Logger.LogTrace("Same compile job, not sending deployment event");
730 return;
731 }
732
734 metadata,
736
738 EventType.DeploymentActivation,
739 new List<string?>
740 {
741 GameIOManager.ResolvePath(newCompileJob.DirectoryName!.Value.ToString()),
742 },
743 false,
745
746 try
747 {
749 }
750 catch (Exception ex)
751 {
752 Logger.LogWarning(ex, "Failed to apply remote deployment!");
753 }
754
756 }
757
767 {
768 try
769 {
770 var sessionEventTask = relayToSession ? ((IEventConsumer)this).HandleEvent(eventType, parameters, false, cancellationToken) : ValueTask.CompletedTask;
775 }
776 catch (JobException ex)
777 {
778 Logger.LogError(ex, "Suppressing exception triggered by event!");
779 }
780 }
781
788 {
789 Logger.LogTrace("Monitor restart!");
790
792
793 for (var retryAttempts = 1; ; ++retryAttempts)
794 {
795 Status = WatchdogStatus.Restoring;
798 try
799 {
800 // use LaunchImplNoLock without announcements or restarting the monitor
801 await LaunchNoLock(false, false, false, null, cancellationToken);
802 Status = WatchdogStatus.Online;
803 Logger.LogDebug("Relaunch successful, resuming monitor...");
804 return;
805 }
807 {
809 }
810
811 Logger.LogWarning(launchException, "Failed to automatically restart the watchdog! Attempt: {attemptNumber}", retryAttempts);
812 Status = WatchdogStatus.DelayedRestart;
813
814 var retryDelay = Math.Min(
815 Convert.ToInt32(
816 Math.Pow(2, retryAttempts)),
817 TimeSpan.FromHours(1).TotalSeconds); // max of one hour, increasing by a power of 2 each time
818
820 $"Failed to restart (Attempt: {retryAttempts}), retrying in {retryDelay}s...");
821
823 TimeSpan.FromSeconds(retryDelay),
825 }
826 }
827
834 {
836
839 {
840 Logger.LogDebug("Found new CompileJob without waiting");
841 return;
842 }
843
845 }
846
852#pragma warning disable CA1502
854 {
855 Logger.LogTrace("Entered MonitorLifetimes");
856 Status = WatchdogStatus.Online;
857 using var cancellationTokenLoggingRegistration = cancellationToken.Register(() => Logger.LogTrace("Monitor cancellationToken triggered"));
858
859 // this function is responsible for calling HandlerMonitorWakeup when necessary and manitaining the MonitorState
860 try
861 {
863 Task? activeServerLifetime = null,
864 activeServerReboot = null,
865 activeServerStartup = null,
866 serverPrimed = null,
868 newDmbAvailable = null,
869 healthCheck = null;
871 var ranInitialDmbCheck = false;
872 for (ulong iteration = 1; nextAction != MonitorAction.Exit; ++iteration)
874 {
875 var nextMonitorWakeupTcs = new TaskCompletionSource();
876 try
877 {
878 Logger.LogTrace("Iteration {iteration} of monitor loop", iteration);
879 nextAction = MonitorAction.Continue;
880
882
884 {
887 {
888 if (sameController && oldTask?.IsCompleted == true)
889 return;
890
892 }
893
894 controller!.RebootGate = nextMonitorWakeupTcs.Task;
895
900
901 if (!sameController)
903
907 () =>
908 {
912 ranInitialDmbCheck = true;
913 return result;
914 });
915 }
916
917 if (controller != null)
918 {
920
925 : Task.Delay(
926 TimeSpan.FromSeconds(healthCheckSeconds),
928
929 // cancel waiting if requested
930 var toWaitOn = Task.WhenAny(
937 serverPrimed!);
938
939 // wait for something to happen
941 }
942 else
943 {
944 Logger.LogError("Controller was null on monitor wakeup! Attempting restart...");
945 nextAction = MonitorAction.Restart; // excuse me wtf?
946 }
947
948 cancellationToken.ThrowIfCancellationRequested();
949 Logger.LogTrace("Monitor activated");
950
951 // always run HandleMonitorWakeup from the context of the semaphore lock
953 {
954 // Set this sooner so chat sends don't hold us up
956 Status = WatchdogStatus.Restoring;
957
958 // multiple things may have happened, handle them one at a time
960 {
961 MonitorActivationReason activationReason = default; // this will always be assigned before being used
962
964 {
965 var taskCompleted = task?.IsCompleted == true;
966 task = null;
967 if (nextAction == MonitorAction.Skip)
968 nextAction = MonitorAction.Continue;
969 else if (taskCompleted)
970 {
972 return true;
973 }
974
975 return false;
976 }
977
978 // process the tasks in this order and call HandlerMonitorWakup for each depending on the new monitorState
986
988
989 if (!anyActivation)
991 else
992 {
993 Logger.LogTrace("Reason: {activationReason}", activationReason);
994 if (activationReason == MonitorActivationReason.HealthCheck)
997 else
1001 }
1002 }
1003 }
1004
1005 Logger.LogTrace("Next monitor action is to {nextAction}", nextAction);
1006
1007 // Restart if requested
1008 if (nextAction == MonitorAction.Restart)
1009 {
1011 nextAction = MonitorAction.Continue;
1012 }
1013 }
1015 {
1016 // really, this should NEVER happen
1017 Logger.LogError(
1018 e,
1019 "Monitor crashed! Iteration: {iteration}",
1020 iteration);
1021
1023 ? "Recovering"
1024 : "Shutting down";
1026 $"Monitor crashed, this should NEVER happen! Please report this, full details in logs! {nextActionMessage}. Error: {e.Message}");
1027
1028 if (disposed)
1030 else if (nextAction != MonitorAction.Exit)
1031 {
1032 if (GetActiveController()?.Lifetime.IsCompleted != true)
1034 else
1035 Logger.LogDebug("Server seems to be okay, not restarting");
1036 nextAction = MonitorAction.Continue;
1037 }
1038 }
1039 finally
1040 {
1041 nextMonitorWakeupTcs.SetResult();
1042 }
1043 }
1044 }
1046 {
1047 // stop signal
1048 Logger.LogDebug("Monitor cancelled");
1049
1050 if (releaseServers)
1051 {
1052 Logger.LogTrace("Detaching server...");
1054 if (controller != null)
1055 await controller.Release();
1056 else
1057 Logger.LogError("Controller was null on monitor shutdown!");
1058 }
1059 }
1060
1061 // DCT: Operation must always run
1062 await DisposeAndNullControllers(CancellationToken.None);
1063 Status = WatchdogStatus.Offline;
1064
1065 Logger.LogTrace("Monitor exiting...");
1066 }
1067#pragma warning restore CA1502
1068
1077 {
1078 if (Status == WatchdogStatus.Offline)
1079 return;
1080 if (!graceful)
1081 {
1084 ? EventType.WatchdogDetach
1085 : EventType.WatchdogShutdown,
1086 Enumerable.Empty<string>(),
1089
1090 if (announce)
1091 Chat.QueueWatchdogMessage("Shutting down...");
1092
1094
1096
1097 LastLaunchParameters = null;
1098 return;
1099 }
1100
1101 // merely set the reboot state
1103 if (toKill != null)
1104 {
1105 await toKill.SetRebootState(Session.RebootState.Shutdown, cancellationToken);
1106 Logger.LogTrace("Graceful termination requested");
1107 }
1108 else
1109 Logger.LogTrace("Could not gracefully terminate as there is no active controller!");
1110 }
1111
1118 {
1119 Logger.LogTrace("Sending health check to active server...");
1121 if (activeServer == null)
1122 return MonitorAction.Restart; // uhhhh???
1123
1125
1126 var shouldShutdown = activeServer.RebootState == Session.RebootState.Shutdown;
1127 if (response == null)
1128 {
1129 switch (++healthChecksMissed)
1130 {
1131 case 1:
1132 Logger.LogDebug("DEFCON 4: Game server missed first health check!");
1133 break;
1134 case 2:
1135 const string message2 = "DEFCON 3: Game server has missed 2 health checks!";
1136 Logger.LogInformation(message2);
1138 break;
1139 case 3:
1141 ? "shutdown"
1142 : "be restarted";
1143 const string logTemplate1 = "DEFCON 2: Game server has missed 3 health checks! If it does not respond to the next one, the watchdog will {actionToTake}!";
1144 Logger.LogWarning(logTemplate1, actionToTake);
1146 logTemplate1.Replace(
1147 "{actionToTake}",
1149 StringComparison.Ordinal));
1150 break;
1151 case 4:
1153 ? "Shutting down due to graceful termination request"
1154 : "Restarting";
1155 const string logTemplate2 = "DEFCON 1: Four health checks have been missed! {actionTaken}...";
1156 Logger.LogWarning(logTemplate2, actionTaken);
1158 logTemplate2.Replace(
1159 "{actionTaken}",
1161 StringComparison.Ordinal));
1162
1164 {
1165 Logger.LogDebug("DumpOnHealthCheckRestart enabled.");
1166 try
1167 {
1169 }
1170 catch (JobException ex)
1171 {
1172 Logger.LogWarning(ex, "Creating dump failed!");
1173 }
1174 catch (Win32Exception ex)
1175 {
1176 Logger.LogWarning(ex, "Creating dump failed!");
1177 }
1178 }
1179 else
1180 Logger.LogTrace("DumpOnHealthCheckRestart disabled.");
1181
1184 default:
1185 Logger.LogError("Invalid health checks missed count: {healthChecksMissed}", healthChecksMissed);
1186 break;
1187 }
1188 }
1189 else
1190 {
1192 ClientCount = response.ClientCount;
1193 }
1194
1195 return MonitorAction.Continue;
1196 }
1197
1203 {
1204 if (result?.ChatResponses != null)
1205 {
1207 foreach (var response in result.ChatResponses
1208 .Where(response =>
1209 {
1210 if (response.ChannelIds == null)
1211 {
1212 if (!warnedMissingChannelIds)
1213 {
1214 Logger.LogWarning("DMAPI response contains null channelIds!");
1215 warnedMissingChannelIds = true;
1216 }
1217
1218 return false;
1219 }
1220
1221 return true;
1222 }))
1224 response,
1225 response.ChannelIds!
1226 .Select(channelIdString =>
1227 {
1229 return (ulong?)channelId;
1230 else
1231 Logger.LogWarning("Could not parse chat response channel ID: {channelID}", channelIdString);
1232
1233 return null;
1234 })
1236 .Select(nullableChannelId => nullableChannelId!.Value));
1237 }
1238 }
1239
1246 {
1247 const string DumpDirectory = "ProcessDumps";
1248
1249 var session = GetActiveController();
1250 if (session?.Lifetime.IsCompleted != false)
1251 throw new JobException(ErrorCode.GameServerOffline);
1252
1253 var dumpFileExtension = session.DumpFileExtension;
1254
1255 var dumpFileNameTemplate = diagnosticsIOManager.ResolvePath(
1256 diagnosticsIOManager.ConcatPath(
1258 $"DreamDaemon-{DateTimeOffset.UtcNow.ToFileStamp()}"));
1259
1260 var dumpFileName = $"{dumpFileNameTemplate}{dumpFileExtension}";
1261 var iteration = 0;
1262 while (await diagnosticsIOManager.FileExists(dumpFileName, cancellationToken))
1263 dumpFileName = $"{dumpFileNameTemplate} ({++iteration}){dumpFileExtension}";
1264
1265 if (iteration == 0)
1266 await diagnosticsIOManager.CreateDirectory(DumpDirectory, cancellationToken);
1267
1268 if (session.Lifetime.IsCompleted)
1269 throw new JobException(ErrorCode.GameServerOffline);
1270
1271 Logger.LogInformation("Dumping session to {dumpFileName}...", dumpFileName);
1272 await session.CreateDump(dumpFileName, ActiveLaunchParameters.Minidumps!.Value, cancellationToken);
1273 }
1274 }
1275}
virtual ? long Id
The ID of the entity.
Definition EntityId.cs:13
Metadata about a server instance.
Definition Instance.cs:9
uint? HealthCheckSeconds
The number of seconds between each watchdog health check. 0 disables.
bool? DumpOnHealthCheckRestart
If a process core dump should be created prior to restarting the watchdog due to health check failure...
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
Represents a tgs_chat_user datum.
Definition ChatUser.cs:12
bool DmbAvailable
If LockNextDmb will succeed.
Definition DmbFactory.cs:42
Task OnNewerDmb
Get a Task that completes when the result of a call to LockNextDmb will be different than the previou...
Definition DmbFactory.cs:32
async ValueTask< CompileJob?> LatestCompileJob()
Gets the latest CompileJob.A ValueTask<TResult> resulting in the latest CompileJob or null if none ar...
const string DifferentCoreExceptionMessage
Message for the InvalidOperationException if ever a job starts on a different IInstanceCore than the ...
Definition Instance.cs:36
Represents a message to send to a chat provider.
Represents a chat command to be handled by DD.
Data structure for TopicCommandType.EventNotification requests.
static TopicParameters CreateBroadcastParameters(string broadcastMessage)
Initializes a new instance of the TopicParameters class.
Parameters necessary for duplicating a ISessionController session.
async ValueTask< ReattachInformation?> Load(CancellationToken cancellationToken)
Load a saved ReattachInformation.A ValueTask<TResult> resulting in the stored ReattachInformation if ...
ValueTask Clear(CancellationToken cancellationToken)
Clear any stored ReattachInformation.A ValueTask representing the running operation.
async ValueTask HandleRestart(Version? updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
Handle a restart of the server.A ValueTask representing the running operation.
async ValueTask< bool > Broadcast(string message, CancellationToken cancellationToken)
Send a broadcast message to the DMAPI.A ValueTask<TResult> resulting in true if the broadcast succee...
async ValueTask< MonitorAction > HandleHealthCheck(CancellationToken cancellationToken)
Handles a watchdog health check.
ISessionController? GetActiveController()
Get the active ISessionController.
readonly IJobManager jobManager
The IJobManager for the WatchdogBase.
WatchdogBase(IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IIOManager gameIOManager, ILogger< WatchdogBase > logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance metadata, bool autoStart)
Initializes a new instance of the WatchdogBase class.
ILogger< WatchdogBase > Logger
The ILogger for the WatchdogBase.
bool releaseServers
If the servers should be released instead of shutdown.
async ValueTask ReattachFailure(CancellationToken cancellationToken)
Call from InitController(ValueTask, ReattachInformation, CancellationToken) when a reattach operation...
async ValueTask CreateDumpNoLock(CancellationToken cancellationToken)
Attempt to create a process dump for the game server. Requires a lock on synchronizationSemaphore.
Models.? CompileJob ActiveCompileJob
Retrieves the Models.CompileJob currently running on the server.
DreamDaemonLaunchParameters? LastLaunchParameters
The DreamDaemonLaunchParameters the active server is using.This may not be the exact same as ActiveLa...
DateTimeOffset? LaunchTime
When the current server executions was started.
readonly bool autoStart
If the WatchdogBase should LaunchNoLock(bool, bool, bool, ReattachInformation, CancellationToken) in ...
async ValueTask Terminate(bool graceful, CancellationToken cancellationToken)
Stops the watchdog.A ValueTask representing the running operation.
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 Api.Models.Instance metadata
The Api.Models.Instance for the WatchdogBase.
bool AlphaIsActive
If the alpha server is the active server.
readonly IEventConsumer eventConsumer
The IEventConsumer that is not the WatchdogBase.
bool disposed
If the WatchdogBase has been DisposeAsync'd.
readonly SemaphoreSlim controllerDisposeSemaphore
SemaphoreSlim used for DisposeAndNullControllers.
WatchdogStatus Status
The current WatchdogStatus.
ValueTask< MonitorAction > HandleMonitorWakeup(MonitorActivationReason activationReason, CancellationToken cancellationToken)
Handles the actions to take when the monitor has to "wake up".
CancellationTokenSource? monitorCts
The CancellationTokenSource for the monitor loop.
Task? monitorTask
The Task running the monitor loop.
readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory
The IRemoteDeploymentManagerFactory for the WatchdogBase.
readonly SemaphoreSlim synchronizationSemaphore
The SemaphoreSlim for the WatchdogBase.
async ValueTask Restart(bool graceful, CancellationToken cancellationToken)
Restarts the watchdog.A ValueTask representing the running operation.
long? MemoryUsage
Gets the memory usage of the game server in bytes.
readonly IIOManager diagnosticsIOManager
The IIOManager pointing to the Diagnostics directory.
async ValueTask< MessageContent > HandleChatCommand(string commandName, string arguments, ChatUser sender, CancellationToken cancellationToken)
Handle a chat command.A ValueTask<TResult> resulting in the MessageContent text to send back.
ValueTask InitController(ValueTask eventTask, ReattachInformation? reattachInfo, CancellationToken cancellationToken)
Starts all ISessionControllers.
async ValueTask CreateDump(CancellationToken cancellationToken)
Attempt to create a process dump for DreamDaemon.A ValueTask representing the running operation.
IIOManager GameIOManager
The IIOManager for the WatchdogBase pointing to the Game directory.
DreamDaemonLaunchParameters ActiveLaunchParameters
The DreamDaemonLaunchParameters to be applied.
uint? ClientCount
Last known client count queried from the DMAPI. Requires health checks to be enabled to populate.
async ValueTask BeforeApplyDmb(Models.CompileJob newCompileJob, CancellationToken cancellationToken)
To be called before a given newCompileJob goes live.
async ValueTask< bool > StopMonitor()
Stops MonitorLifetimes(CancellationToken). Doesn't kill the servers.
async ValueTask MonitorRestart(CancellationToken cancellationToken)
Attempt to restart the monitor from scratch.
async ValueTask Launch(CancellationToken cancellationToken)
Start the IWatchdog.A ValueTask representing the running operation.
async Task InitialCheckDmbUpdated(CompileJob currentCompileJob)
Check for a new IDmbProvider.
async ValueTask LaunchNoLock(bool startMonitor, bool announce, bool announceFailure, ReattachInformation? reattachInfo, CancellationToken cancellationToken)
Launches the watchdog.
async ValueTask DisposeAndNullControllers(CancellationToken cancellationToken)
Wrapper for DisposeAndNullControllersImpl under a locked context.
ValueTask DisposeAndNullControllersImpl()
Call IDisposable.Dispose and null the fields for all ISessionControllers.
async ValueTask TerminateNoLock(bool graceful, bool announce, CancellationToken cancellationToken)
Implementation of Terminate(bool, CancellationToken). Does not lock synchronizationSemaphore.
async ValueTask< bool > ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
Changes the ActiveLaunchParameters. If currently running, may trigger a graceful restart....
void HandleChatResponses(TopicResponse? result)
Handle any TopicResponse.ChatResponses in a given topic result .
virtual async ValueTask ResetRebootState(CancellationToken cancellationToken)
Cancels pending graceful actions.A ValueTask representing the running operation.
async Task MonitorLifetimes(CancellationToken cancellationToken)
The main loop of the watchdog. Ayschronously waits for events to occur and then responds to them.
long? SessionId
An incrementing ID for representing current server execution.
async Task StartAsync(CancellationToken cancellationToken)
ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
Called when the owning Instance is renamed.A ValueTask representing the running operation.
int healthChecksMissed
The number of hearbeats missed.
IChatManager Chat
The IChatManager for the WatchdogBase.
async Task StopAsync(CancellationToken cancellationToken)
readonly IRestartRegistration restartRegistration
The IRestartRegistration for the WatchdogBase.
async ValueTask HandleEventImpl(EventType eventType, IEnumerable< string > parameters, bool relayToSession, CancellationToken cancellationToken)
Handle a given eventType without re-throwing errors.
WatchdogStatus status
Backing field for Status.
volatile TaskCompletionSource activeParametersUpdated
TaskCompletionSource that completes when ActiveLaunchParameters are changed and we are running.
Operation exceptions thrown from the context of a Models.Job.
async ValueTask Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan .A ValueTask representing the running operation.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger? logger=null)
Asyncronously locks a semaphore .
Helpers for manipulating the Serilog.Context.LogContext.
const string WatchdogMonitorIterationContextProperty
The Serilog.Context.LogContext property name for the ID of the watchdog monitor iteration currently b...
For managing connected chat services.
void QueueWatchdogMessage(string message)
Queue a chat message to configured watchdog channels.
void RegisterCommandHandler(ICustomCommandHandler customCommandHandler)
Registers a customCommandHandler to use.
ValueTask UpdateTrackingContexts(CancellationToken cancellationToken)
Force an update with the active channels on all active IChatTrackingContexts.
void QueueMessage(MessageContent message, IEnumerable< ulong > channelIds)
Queue a chat message to a given set of channelIds .
Handles Commands.ICommands that map to those defined in a IChatTrackingContext.
IRemoteDeploymentManager CreateRemoteDeploymentManager(Api.Models.Instance metadata, RemoteGitProvider remoteGitProvider)
Creates a IRemoteDeploymentManager for a given remoteGitProvider .
Consumes EventTypes and takes the appropriate actions.
ValueTask? HandleCustomEvent(string eventName, IEnumerable< string?> parameters, CancellationToken cancellationToken)
Handles a given custom event.
ValueTask HandleEvent(EventType eventType, IEnumerable< string?> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
Handle a given eventType .
Handles communication with a DreamDaemon IProcess.
Models.CompileJob CompileJob
Gets the CompileJob associated with the ISessionController.
ReattachInformation ReattachInformation
Gets the Session.ReattachInformation associated with the ISessionController.
EngineVersion EngineVersion
Gets the Api.Models.EngineVersion associated with the ISessionController.
Handles saving and loading ReattachInformation.
Runs and monitors the twin server controllers.
Definition IWatchdog.cs:16
Represents the lifetime of a IRestartHandler registration.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart.
Interface for using filesystems.
Definition IIOManager.cs:13
Manages the runtime of Jobs.
ValueTask RegisterOperation(Job job, JobEntrypoint operation, CancellationToken cancellationToken)
Registers a given Job and begins running it.
long? MemoryUsage
Gets the process' memory usage in bytes.
DateTimeOffset? LaunchTime
When the process was started.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12
JobCode
The different types of Response.JobResponse.
Definition JobCode.cs:9
WatchdogStatus
The current status of the watchdog.
@ List
User may list files if the Models.Instance allows it.
DreamDaemonRights
Rights for managing DreamDaemon.
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.
MonitorActivationReason
Reasons for the monitor to wake up.