1 using Microsoft.Extensions.DependencyInjection;
2 using Microsoft.Extensions.Hosting;
3 using Microsoft.Extensions.Logging;
4 using Microsoft.Extensions.Options;
6 using System.Collections.Generic;
10 using System.Threading.Tasks;
21 public bool RestartRequested {
get;
private set; }
24 public bool WatchdogPresent =>
25 #if WATCHDOG_FREE_RESTART 81 public Server(IHostBuilder hostBuilder,
string updatePath)
83 this.hostBuilder = hostBuilder ??
throw new ArgumentNullException(nameof(hostBuilder));
84 this.updatePath = updatePath;
86 hostBuilder.ConfigureServices(serviceCollection => serviceCollection.AddSingleton<
IServerControl>(
this));
88 restartHandlers =
new List<IRestartHandler>();
89 restartLock =
new object();
98 if (checkWatchdog && !WatchdogPresent && propagatedException == null)
99 throw new InvalidOperationException(
"Server restarts are not supported");
101 if (cancellationTokenSource == null || logger == null)
102 throw new InvalidOperationException(
"Tried to control a non-running Server!");
111 if (propagatedException == null)
114 if (otherException != null)
115 throw new AggregateException(propagatedException, otherException);
117 throw propagatedException;
121 public async Task
Run(CancellationToken cancellationToken)
123 using (cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
124 using (var fsWatcher = updatePath != null ?
new FileSystemWatcher(Path.GetDirectoryName(updatePath)) : null)
126 if (fsWatcher != null)
128 fsWatcher.Created += (a, b) =>
130 if (b.FullPath == updatePath && File.Exists(b.FullPath))
133 logger.LogInformation(
"Host watchdog appears to be requesting server termination!");
134 cancellationTokenSource.Cancel();
137 fsWatcher.EnableRaisingEvents =
true;
140 using var host = hostBuilder.Build();
143 logger = host.Services.GetRequiredService<ILogger<Server>>();
144 using (cancellationToken.Register(() => logger.LogInformation(
"Server termination requested!")))
146 var generalConfigurationOptions = host.Services.GetRequiredService<IOptions<GeneralConfiguration>>();
147 generalConfiguration = generalConfigurationOptions.Value;
148 await host.RunAsync(cancellationTokenSource.Token).ConfigureAwait(
false);
153 CheckExceptionPropagation(ex);
158 CheckExceptionPropagation(null);
165 throw new ArgumentNullException(nameof(version));
166 if (updateZipUrl == null)
167 throw new ArgumentNullException(nameof(updateZipUrl));
168 if (ioManager == null)
169 throw new ArgumentNullException(nameof(ioManager));
173 logger.LogTrace(
"Begin ApplyUpdate...");
177 if (updating || RestartRequested)
179 logger.LogTrace(
"Aborted due to concurrency conflict!");
186 async
void RunUpdate()
190 logger.LogInformation(
"Updating server to version {0} ({1})...", version, updateZipUrl);
192 if (cancellationTokenSource == null)
193 throw new InvalidOperationException(
"Tried to update a non-running Server!");
194 var cancellationToken = cancellationTokenSource.Token;
196 logger.LogTrace(
"Downloading zip package...");
197 var updateZipData = await ioManager.
DownloadFile(updateZipUrl, cancellationToken).ConfigureAwait(
false);
201 logger.LogTrace(
"Exctracting zip package to {0}...", updatePath);
202 await ioManager.
ZipToDirectory(updatePath, updateZipData, cancellationToken).ConfigureAwait(
false);
210 await ioManager.
DeleteDirectory(updatePath,
default).ConfigureAwait(
false);
214 throw new AggregateException(e, e2);
220 await Restart(version, null,
true).ConfigureAwait(
false);
222 catch (OperationCanceledException)
224 logger.LogInformation(
"Server update cancelled!");
228 logger.LogError(
"Error updating server! Exception: {0}", e);
244 throw new ArgumentNullException(nameof(handler));
249 if (!RestartRequested)
251 logger.LogTrace(
"Registering restart handler {0}...", handler);
252 restartHandlers.Add(handler);
256 if (!RestartRequested)
257 restartHandlers.Remove(handler);
265 public Task Restart() => Restart(null, null,
true);
274 async Task
Restart(Version newVersion, Exception exception,
bool requireWatchdog)
276 CheckSanity(requireWatchdog);
278 logger.LogTrace(
"Begin Restart...");
282 if ((updating && newVersion == null) || RestartRequested)
284 logger.LogTrace(
"Aborted due to concurrency conflict!");
288 RestartRequested =
true;
289 propagatedException = exception;
292 if (exception == null)
293 using (var cts =
new CancellationTokenSource())
295 logger.LogInformation(
"Restarting server...");
296 var cancellationToken = cts.Token;
297 var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList());
299 var expiryTask = Task.Delay(TimeSpan.FromMilliseconds(generalConfiguration.
RestartTimeout));
300 await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(
false);
301 logger.LogTrace(
"Joining restart handlers...");
305 await eventsTask.ConfigureAwait(
false);
307 catch (OperationCanceledException)
309 logger.LogError(
"Restart timeout hit! Existing DreamDaemon processes will be lost and must be killed manually before being restarted with TGS!");
313 logger.LogError(
"Restart handlers error! Exception: {0}", e);
317 logger.LogTrace(
"Stopping host...");
318 cancellationTokenSource.Cancel();
322 public Task Die(Exception exception) => Restart(null, exception,
false);
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
ILogger< Server > logger
The ILogger for the Server
bool ApplyUpdate(Version version, Uri updateZipUrl, IIOManager ioManager)
Run a new Host assembly and stop the current one. This will likely trigger all active CancellationTok...
readonly List< IRestartHandler > restartHandlers
The IRestartHandlers to run when the Server restarts
Use server authentication
Task< byte[]> DownloadFile(Uri url, CancellationToken cancellationToken)
Downloads a file from url
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart
Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken)
Extract a set of zipFileBytes to a given path
async Task Restart(Version newVersion, Exception exception, bool requireWatchdog)
Implements Restart()
async Task Run(CancellationToken cancellationToken)
Runs the IServer
readonly object restartLock
object for certain restart related operations.
GeneralConfiguration generalConfiguration
The GeneralConfiguration for the Server
void CheckExceptionPropagation(Exception otherException)
Re-throw propagatedException if it exists
readonly IHostBuilder hostBuilder
The IHostBuilder for the Server
Handler for server restarts
readonly string updatePath
The absolute path to install updates to
Server(IHostBuilder hostBuilder, string updatePath)
Construct a Server
CancellationTokenSource cancellationTokenSource
The cancellationTokenSource for the Server
General configuration options
bool updating
If a server update has been or is being applied
Exception propagatedException
The Exception to propagate when the server terminates
Interface for using filesystems
Represents the lifetime of a IRestartHandler registration
uint RestartTimeout
The timeout milliseconds for restarting the server
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
void CheckSanity(bool checkWatchdog)
Throws an InvalidOperationException if the IServerControl cannot be used