2using System.Collections.Generic;
3using System.Diagnostics;
5using System.Threading.Tasks;
7using Microsoft.Extensions.Logging;
85 ILogger<AdvancedWatchdog> logger,
87 Api.Models.Instance instance,
91 sessionControllerFactory,
102 initialLaunchParameters,
108 LinkFactory = linkFactory ??
throw new ArgumentNullException(nameof(linkFactory));
116 Debug.Assert(disposeTask.IsCompleted,
"This should always be true during construction!");
117 disposeTask.GetAwaiter().GetResult();
126 await base.DisposeAndNullControllersImpl();
141 protected sealed override async ValueTask<MonitorAction>
HandleNormalReboot(CancellationToken cancellationToken)
151 if (!controller.ProcessingRebootBridgeRequest)
155 "The reboot bridge request completed before the watchdog could suspend the server! This can lead to buggy DreamDaemon behaviour and should be reported! To ensure stability, we will need to hard reboot the server");
163 Logger.LogInformation(
"Deployed .dme is not ready to swap, delaying until next reboot!");
164 Chat.
QueueWatchdogMessage(
"The pending deployment was not ready to be activated this reboot. It will be applied at the next one.");
173 var currentCompileJobId = controller.ReattachInformation.Dmb.CompileJob.Id;
178 var localDeploymentCleanupGate =
new TaskCompletionSource();
179 async Task CleanupLingeringDeployment()
183 "Holding old deployment {compileJobId} for up to {expiry} seconds...",
185 lingeringDeploymentExpirySeconds);
188 var timeout =
AsyncDelayer.
Delay(TimeSpan.FromSeconds(lingeringDeploymentExpirySeconds), CancellationToken.None).AsTask();
190 var completedTask = await Task.WhenAny(
191 localDeploymentCleanupGate.Task,
194 var timedOut = completedTask == timeout;
199 "Releasing old deployment {compileJobId}{afterTimeout}",
205 await lingeringDeployment.DisposeAsync();
208 var oldDeploymentCleanupGate = Interlocked.Exchange(ref
deploymentCleanupGate, localDeploymentCleanupGate);
209 oldDeploymentCleanupGate?.TrySetResult();
211 Logger.LogTrace(
"Replacing activeSwappable with pendingSwappable...");
217 CleanupLingeringDeployment());
227 Logger.LogTrace(
"Nothing to do as pendingSwappable is null.");
229 return await base.HandleNormalReboot(cancellationToken);
237 if (canSeamlesslySwap)
242 "Not swapping to new compile job {compileJobId} as it uses a different engine version ({newEngineVersion}) than what is currently active {oldEngineVersion}.",
246 canSeamlesslySwap =
false;
251 "Not swapping to new compile job {compileJobId} as it uses a different .dmb name ({newDmbName}) than what is currently active {oldDmbName}.",
255 canSeamlesslySwap =
false;
258 if (!canSeamlesslySwap)
260 Logger.LogDebug(
"Queueing graceful restart instead...");
261 await compileJobProvider.DisposeAsync();
262 await base.HandleNewDmbAvailable(cancellationToken);
272 Logger.LogWarning(
"Active compile job has no DMAPI! Commencing immediate .dmb swap. Note this behavior is known to be buggy in some DM code contexts. See https://github.com/tgstation/tgstation-server/issues/1550");
278 Logger.LogError(ex,
"Exception while swapping");
279 IDmbProvider providerToDispose = swappableProvider ?? compileJobProvider;
280 await providerToDispose.DisposeAsync();
292 throw new InvalidOperationException(
"Expected activeSwappable to be null!");
294 throw new InvalidOperationException(
"Expected pendingSwappable to be null!");
296 Logger.LogTrace(
"Prep for server launch");
308 Logger.LogTrace(ex,
"Initial link error, nulling ActiveSwappable");
334 await base.SessionStartupPersist(cancellationToken);
340 var result = await base.HandleMonitorWakeup(reason, cancellationToken);
371 Logger.LogTrace(
"Linking compile job...");
387 var suspended =
false;
391 server.SuspendProcess();
396 Logger.LogWarning(ex,
"Exception while suspending server!");
402 await newProvider.
MakeActive(cancellationToken);
408 server.ResumeProcess();
419 Logger.LogTrace(
"DrainDeploymentCleanupTasks...");
421 localDeploymentCleanupGate?.TrySetResult();
423 List<Task> localDeploymentCleanupTasks;
427 localDeploymentCleanupTasks =
new List<Task>(totalActiveTasks);
428 for (var i = totalActiveTasks - 1; i >= 0; --i)
431 if (!blocking && !currentTask.IsCompleted)
434 localDeploymentCleanupTasks.Add(currentTask);
439 return Task.WhenAll(localDeploymentCleanupTasks);
EngineType? Engine
The EngineType.
virtual ? long Id
The ID of the entity.
string? DmeName
The .dme file used for compilation.
Launch settings for DreamDaemon.
uint? StartupTimeout
The DreamDaemon startup timeout in seconds.
IDmbProvider LockNextDmb(string reason, [CallerFilePath] string? callerFile=null, [CallerLineNumber] int callerLine=default)
Gets the next IDmbProvider. DmbAvailable is a precondition.A new IDmbProvider.
A IDmbProvider that uses filesystem links to change directory structure underneath the server process...
Models.CompileJob CompileJob
The CompileJob of the .dmb.
Task FinishActivationPreparation(CancellationToken cancellationToken)
Should be awaited. before calling MakeActive(CancellationToken) to ensure the SwappableDmbProvider is...
bool Swapped
If MakeActive(CancellationToken) has been run.
ValueTask MakeActive(CancellationToken cancellationToken)
Make the SwappableDmbProvider active by replacing the live link with our CompileJob.
virtual ValueTask DisposeAsync()
ValueTask Update(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Update some reattachInformation .A ValueTask representing the running operation.
A IWatchdog that, instead of killing servers for updates, uses the wonders of filesystem links to swa...
readonly List< Task > deploymentCleanupTasks
List<T> of Tasks that are waiting to clean up old deployments.
override async ValueTask< IDmbProvider > PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken)
Prepare the server to launch a new instance with the WatchdogBase.ActiveLaunchParameters and a given ...
bool CanUseSwappableDmbProvider(IDmbProvider dmbProvider)
If the SwappableDmbProvider feature of the AdvancedWatchdog can be used with a given dmbProvider .
SwappableDmbProvider? pendingSwappable
The active SwappableDmbProvider for WatchdogBase.ActiveLaunchParameters.
async ValueTask InitialLink(CancellationToken cancellationToken)
Create the initial link to the live game directory using ActiveSwappable.
SwappableDmbProvider? ActiveSwappable
The SwappableDmbProvider for WatchdogBase.LastLaunchParameters.
override async ValueTask< MonitorAction > HandleNormalReboot(CancellationToken cancellationToken)
Handler for MonitorActivationReason.ActiveServerRebooted when the RebootState is RebootState....
Task DrainDeploymentCleanupTasks(bool blocking)
Asynchronously drain deploymentCleanupTasks.
SwappableDmbProvider CreateSwappableDmbProvider(IDmbProvider dmbProvider)
Create a SwappableDmbProvider for a given dmbProvider .
override async ValueTask SessionStartupPersist(CancellationToken cancellationToken)
Called to save the current Server into the WatchdogBase.SessionPersistor when initially launched....
override async ValueTask DisposeAndNullControllersImpl()
override async ValueTask HandleNewDmbAvailable(CancellationToken cancellationToken)
Handler for MonitorActivationReason.NewDmbAvailable.A ValueTask representing the running operation.
IFilesystemLinkFactory LinkFactory
The IFilesystemLinkFactory for the AdvancedWatchdog.
volatile? TaskCompletionSource deploymentCleanupGate
The TaskCompletionSource representing the cleanup of an unused IDmbProvider.
AdvancedWatchdog(IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IIOManager gameIOManager, IFilesystemLinkFactory linkFactory, ILogger< AdvancedWatchdog > logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
Initializes a new instance of the AdvancedWatchdog class.
async ValueTask PerformDmbSwap(SwappableDmbProvider newProvider, CancellationToken cancellationToken)
Suspends the BasicWatchdog.Server and calls SwappableDmbProvider.MakeActive(CancellationToken) on a n...
ValueTask ApplyInitialDmb(CancellationToken cancellationToken)
Set the ReattachInformation.InitialDmb for the BasicWatchdog.Server.
override async ValueTask< MonitorAction > HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
A IWatchdog that manages one server.
readonly IJobManager jobManager
The IJobManager for the WatchdogBase.
ILogger< WatchdogBase > Logger
The ILogger for the WatchdogBase.
Models.? CompileJob ActiveCompileJob
Retrieves the Models.CompileJob currently running on the server.
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 ValueTask DisposeAsync()
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.
IChatManager Chat
The IChatManager for the WatchdogBase.
ValueTask Restart()
Restarts the Host.A ValueTask representing the running operation.
async ValueTask Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan .A ValueTask representing the running operation.
For managing connected chat services.
void QueueWatchdogMessage(string message)
Queue a chat message to configured watchdog channels.
Factory for IDmbProviders.
Provides absolute paths to the latest compiled .dmbs.
EngineVersion EngineVersion
The Api.Models.EngineVersion used to build the .dmb.
Models.CompileJob CompileJob
The CompileJob of the .dmb.
Factory for creating IRemoteDeploymentManagers.
Consumes EventTypes and takes the appropriate actions.
Factory for ISessionControllers.
Handles saving and loading ReattachInformation.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
For creating filesystem symbolic links.
Interface for using filesystems.
Manages the runtime of Jobs.
For waiting asynchronously.
EngineType
The type of engine the codebase is using.
MonitorAction
The action for the monitor loop to take when control is returned to it.
MonitorActivationReason
Reasons for the monitor to wake up.