2using System.Collections.Generic;
3using System.Diagnostics;
5using System.Threading.Tasks;
7using Microsoft.Extensions.Logging;
86 IMetricFactory metricFactory,
89 ILogger<AdvancedWatchdog> logger,
91 Api.Models.Instance instance,
95 sessionControllerFactory,
107 initialLaunchParameters,
113 LinkFactory = linkFactory ??
throw new ArgumentNullException(nameof(linkFactory));
121 Debug.Assert(disposeTask.IsCompleted,
"This should always be true during construction!");
122 disposeTask.GetAwaiter().GetResult();
131 await base.DisposeAndNullControllersImpl();
146 protected sealed override async ValueTask<MonitorAction>
HandleNormalReboot(CancellationToken cancellationToken)
156 if (!controller.ProcessingRebootBridgeRequest)
160 "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");
168 Logger.LogInformation(
"Deployed .dme is not ready to swap, delaying until next reboot!");
169 Chat.
QueueWatchdogMessage(
"The pending deployment was not ready to be activated this reboot. It will be applied at the next one.");
178 var currentCompileJobId = controller.ReattachInformation.Dmb.CompileJob.Id;
183 var localDeploymentCleanupGate =
new TaskCompletionSource();
184 async Task CleanupLingeringDeployment()
188 "Holding old deployment {compileJobId} for up to {expiry} seconds...",
190 lingeringDeploymentExpirySeconds);
193 var timeout =
AsyncDelayer.
Delay(TimeSpan.FromSeconds(lingeringDeploymentExpirySeconds), CancellationToken.None).AsTask();
195 var completedTask = await Task.WhenAny(
196 localDeploymentCleanupGate.Task,
199 var timedOut = completedTask == timeout;
204 "Releasing old deployment {compileJobId}{afterTimeout}",
210 await lingeringDeployment.DisposeAsync();
213 var oldDeploymentCleanupGate = Interlocked.Exchange(ref
deploymentCleanupGate, localDeploymentCleanupGate);
214 oldDeploymentCleanupGate?.TrySetResult();
216 Logger.LogTrace(
"Replacing activeSwappable with pendingSwappable...");
222 CleanupLingeringDeployment());
232 Logger.LogTrace(
"Nothing to do as pendingSwappable is null.");
234 return await base.HandleNormalReboot(cancellationToken);
242 if (canSeamlesslySwap)
247 "Not swapping to new compile job {compileJobId} as it uses a different engine version ({newEngineVersion}) than what is currently active {oldEngineVersion}.",
251 canSeamlesslySwap =
false;
256 "Not swapping to new compile job {compileJobId} as it uses a different .dmb name ({newDmbName}) than what is currently active {oldDmbName}.",
260 canSeamlesslySwap =
false;
263 if (!canSeamlesslySwap)
265 Logger.LogDebug(
"Queueing graceful restart instead...");
266 await compileJobProvider.DisposeAsync();
267 await base.HandleNewDmbAvailable(cancellationToken);
277 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");
283 Logger.LogError(ex,
"Exception while swapping");
284 IDmbProvider providerToDispose = swappableProvider ?? compileJobProvider;
285 await providerToDispose.DisposeAsync();
297 throw new InvalidOperationException(
"Expected activeSwappable to be null!");
299 throw new InvalidOperationException(
"Expected pendingSwappable to be null!");
301 Logger.LogTrace(
"Prep for server launch");
313 Logger.LogTrace(ex,
"Initial link error, nulling ActiveSwappable");
339 await base.SessionStartupPersist(cancellationToken);
345 var result = await base.HandleMonitorWakeup(reason, cancellationToken);
376 Logger.LogTrace(
"Linking compile job...");
392 var suspended =
false;
396 server.SuspendProcess();
401 Logger.LogWarning(ex,
"Exception while suspending server!");
407 await newProvider.
MakeActive(cancellationToken);
413 server.ResumeProcess();
424 Logger.LogTrace(
"DrainDeploymentCleanupTasks...");
426 localDeploymentCleanupGate?.TrySetResult();
428 List<Task> localDeploymentCleanupTasks;
432 localDeploymentCleanupTasks =
new List<Task>(totalActiveTasks);
433 for (var i = totalActiveTasks - 1; i >= 0; --i)
436 if (!blocking && !currentTask.IsCompleted)
439 localDeploymentCleanupTasks.Add(currentTask);
444 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.
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.
AdvancedWatchdog(IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IMetricFactory metricFactory, IIOManager gameIOManager, IFilesystemLinkFactory linkFactory, ILogger< AdvancedWatchdog > logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
Initializes a new instance of the AdvancedWatchdog class.
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.