2using System.Collections.Generic;
7using System.Threading.Tasks;
9using HotChocolate.Execution;
11using Microsoft.Extensions.DependencyInjection;
12using Microsoft.Extensions.Hosting;
13using Microsoft.Extensions.Logging;
14using Microsoft.Extensions.Options;
35#if WATCHDOG_FREE_RESTART
44 internal IHost? Host {
get;
private set; }
126 public async ValueTask
Run(CancellationToken cancellationToken)
130 using (var fsWatcher = updateDirectory !=
null ?
new FileSystemWatcher(updateDirectory) :
null)
132 if (fsWatcher !=
null)
139 fsWatcher.EnableRaisingEvents =
true;
146 logger = Host.Services.GetRequiredService<ILogger<Server>>();
149 using (cancellationToken.Register(() =>
logger.LogInformation(
"Server termination requested!")))
161 catch (OperationCanceledException ex)
163 logger.LogDebug(ex,
"Server run cancelled!");
188 ArgumentNullException.ThrowIfNull(updateExecutor);
189 ArgumentNullException.ThrowIfNull(newVersion);
194 throw new InvalidOperationException(
"Tried to start update when server was initialized without an updatePath set!");
196 var
logger = this.logger!;
197 logger.LogTrace(
"Begin ApplyUpdate...");
199 CancellationToken criticalCancellationToken;
204 logger.LogDebug(
"Aborted update due to concurrency conflict!");
209 throw new InvalidOperationException(
"Tried to update a non-running Server!");
215 async Task RunUpdate()
217 var updateExecutedSuccessfully =
false;
220 updateExecutedSuccessfully = await updateExecutor.
ExecuteUpdate(
updatePath, criticalCancellationToken, criticalCancellationToken);
222 catch (OperationCanceledException ex)
224 logger.LogDebug(ex,
"Update cancelled!");
229 logger.LogError(ex,
"Update errored!");
233 if (updateExecutedSuccessfully)
235 logger.LogTrace(
"Update complete!");
240 logger.LogTrace(
"Stopping host due to termination request...");
245 logger.LogTrace(
"Update failed!");
257 ArgumentNullException.ThrowIfNull(handler);
261 var
logger = this.logger!;
265 logger.LogTrace(
"Registering restart handler {handlerImplementationName}...", handler);
276 logger.LogWarning(
"Restart handler {handlerImplementationName} register after a shutdown had begun!", handler);
289 if (exception !=
null)
293 return ValueTask.CompletedTask;
304 var internalConfigurationOptions = services.GetRequiredService<IOptions<InternalConfiguration>>();
305 var apiDumpPath = internalConfigurationOptions.Value.DumpGraphQLApiPath;
306 if (String.IsNullOrWhiteSpace(apiDumpPath))
309 logger!.LogInformation(
"Dumping GraphQL API spec to {path} and exiting...", apiDumpPath);
312 var resolver = services.GetRequiredService<IRequestExecutorResolver>();
313 var executor = await resolver.GetRequestExecutorAsync(cancellationToken: cancellationToken);
314 var sdl = executor.Schema.Print();
328 throw new InvalidOperationException(
"Server restarts are not supported");
331 throw new InvalidOperationException(
"Tried to control a non-running Server!");
343 if (otherException !=
null)
357 async ValueTask
RestartImpl(Version? newVersion,
Exception? exception,
bool requireWatchdog,
bool completeAsap)
362 bool isGracefulShutdown = !requireWatchdog && exception ==
null;
363 var
logger = this.logger!;
365 "Begin {restartType}...",
368 ?
"semi-graceful shutdown"
369 :
"graceful shutdown"
376 logger.LogTrace(
"Aborted restart due to concurrency conflict!");
384 if (exception ==
null)
386 var giveHandlersTimeToWaitAround = isGracefulShutdown && !completeAsap;
387 logger.LogInformation(
"Stopping server...");
388 using var cts =
new CancellationTokenSource(
389 TimeSpan.FromMinutes(
390 giveHandlersTimeToWaitAround
393 var cancellationToken = cts.Token;
396 ValueTask eventsTask;
401 x => x.HandleRestart(newVersion, giveHandlersTimeToWaitAround, cancellationToken))
404 logger.LogTrace(
"Joining restart handlers...");
407 catch (OperationCanceledException ex)
409 if (isGracefulShutdown)
410 logger.LogWarning(ex,
"Graceful shutdown timeout hit! Existing DreamDaemon processes will be terminated!");
414 "Restart timeout hit! Existing DreamDaemon processes will be lost and must be killed manually before being restarted with TGS!");
418 logger.LogError(e,
"Restart handlers error!");
432 logger?.LogTrace(
"FileSystemWatcher triggered.");
437 logger?.LogInformation(
"Host watchdog appears to be requesting server termination!");
449 logger?.LogInformation(
"An update is in progress, we will wait for that to complete...");
459 logger!.LogDebug(
"Stopping host...");
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
bool WatchdogPresent
true if live updates are supported, false. TryStartUpdate(IServerUpdateExecutor, Version) and Restart...
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart.A new IRestartRegistration ...
ValueTask Die(Exception? exception)
Kill the server with a fatal exception.A Task representing the running operation.
bool UpdateInProgress
Whether or not the server is currently updating.
async ValueTask Run(CancellationToken cancellationToken)
Runs the IServer.A ValueTask representing the running operation.
bool TryStartUpdate(IServerUpdateExecutor updateExecutor, Version newVersion)
Attempt to update with a given updateExecutor .true if the update started successfully,...
Task? updateTask
The Task that is used for asynchronously updating the server.
async ValueTask RestartImpl(Version? newVersion, Exception? exception, bool requireWatchdog, bool completeAsap)
Implements Restart().
void CheckSanity(bool checkWatchdog)
Throws an InvalidOperationException if the IServerControl cannot be used.
ValueTask GracefulShutdown(bool detach)
Gracefully shutsdown the Host.A ValueTask representing the running operation.
readonly List< IRestartHandler > restartHandlers
The IRestartHandlers to run when the Server restarts.
CancellationTokenSource? cancellationTokenSource
The cancellationTokenSource for the Server.
async void WatchForShutdownFileCreation(object sender, FileSystemEventArgs eventArgs)
Event handler for the updatePath's FileSystemWatcher. Triggers shutdown if requested by host watchdog...
readonly IIOManager ioManager
The IIOManager to use.
ValueTask Restart()
Restarts the Host.A ValueTask representing the running operation.
IOptionsMonitor< GeneralConfiguration >? generalConfigurationOptions
The IOptionsMonitor<TOptions> of GeneralConfiguration for the Server.
bool terminateIfUpdateFails
If there is an update in progress and this flag is set, it should stop the server immediately if it f...
readonly? string updatePath
The absolute path to install updates to.
ILogger< Server >? logger
The ILogger for the Server.
readonly IHostBuilder hostBuilder
The IHostBuilder for the Server.
Exception? propagatedException
The Exception to propagate when the server terminates.
readonly object restartLock
lock object for certain restart related operations.
bool shutdownInProgress
If the server is being shut down or restarted.
Server(IIOManager ioManager, IHostBuilder hostBuilder, string? updatePath)
Initializes a new instance of the Server class.
async ValueTask< bool > DumpGraphQLSchemaIfRequested(IServiceProvider services, CancellationToken cancellationToken)
Checks if InternalConfiguration.DumpGraphQLApiPath is set and dumps the GraphQL API Schema to it if s...
bool RestartRequested
If the IServer should restart.
void StopServerImmediate()
Fires off the cancellationTokenSource without any checks, shutting down everything.
void CheckExceptionPropagation(Exception? otherException)
Re-throw propagatedException if it exists.
Runs a given disposeAction on Dispose.
Handler for server restarts.
Represents the lifetime of a IRestartHandler registration.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
Executes server update operations.
ValueTask< bool > ExecuteUpdate(string updatePath, CancellationToken cancellationToken, CancellationToken criticalCancellationToken)
Executes a pending server update by extracting the new server to a given updatePath .
Interface for using filesystems.
string ResolvePath()
Retrieve the full path of the current working directory.
string GetDirectoryName(string path)
Gets the directory portion of a given path .
ValueTask WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content.
Task< bool > FileExists(string path, CancellationToken cancellationToken)
Check that the file at path exists.