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 => updatePath != null;
76 public Server(IHostBuilder hostBuilder,
string updatePath)
78 this.hostBuilder = hostBuilder ??
throw new ArgumentNullException(nameof(hostBuilder));
79 this.updatePath = updatePath;
81 hostBuilder.ConfigureServices(serviceCollection => serviceCollection.AddSingleton<
IServerControl>(
this));
83 restartHandlers =
new List<IRestartHandler>();
84 restartLock =
new object();
93 if (checkWatchdog && !WatchdogPresent && propagatedException == null)
94 throw new InvalidOperationException(
"Server restarts are not supported");
96 if (cancellationTokenSource == null || logger == null)
97 throw new InvalidOperationException(
"Tried to control a non-running Server!");
106 if (propagatedException == null)
109 if (otherException != null)
110 throw new AggregateException(propagatedException, otherException);
112 throw propagatedException;
116 public async Task
Run(CancellationToken cancellationToken)
118 using (cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
119 using (var fsWatcher = updatePath != null ?
new FileSystemWatcher(Path.GetDirectoryName(updatePath)) : null)
121 if (fsWatcher != null)
123 fsWatcher.Created += (a, b) =>
125 if (b.FullPath == updatePath && File.Exists(b.FullPath))
128 logger.LogInformation(
"Host watchdog appears to be requesting server termination!");
129 cancellationTokenSource.Cancel();
132 fsWatcher.EnableRaisingEvents =
true;
135 using var host = hostBuilder.Build();
138 logger = host.Services.GetRequiredService<ILogger<Server>>();
139 using (cancellationToken.Register(() => logger.LogInformation(
"Server termination requested!")))
141 var generalConfigurationOptions = host.Services.GetRequiredService<IOptions<GeneralConfiguration>>();
142 generalConfiguration = generalConfigurationOptions.Value;
143 await host.RunAsync(cancellationTokenSource.Token).ConfigureAwait(
false);
148 CheckExceptionPropagation(ex);
153 CheckExceptionPropagation(null);
160 throw new ArgumentNullException(nameof(version));
161 if (updateZipUrl == null)
162 throw new ArgumentNullException(nameof(updateZipUrl));
163 if (ioManager == null)
164 throw new ArgumentNullException(nameof(ioManager));
168 logger.LogTrace(
"Begin ApplyUpdate...");
172 if (updating || RestartRequested)
174 logger.LogTrace(
"Aborted due to concurrency conflict!");
181 async
void RunUpdate()
185 logger.LogInformation(
"Updating server to version {0} ({1})...", version, updateZipUrl);
187 if (cancellationTokenSource == null)
188 throw new InvalidOperationException(
"Tried to update a non-running Server!");
189 var cancellationToken = cancellationTokenSource.Token;
191 logger.LogTrace(
"Downloading zip package...");
192 var updateZipData = await ioManager.
DownloadFile(updateZipUrl, cancellationToken).ConfigureAwait(
false);
196 logger.LogTrace(
"Exctracting zip package to {0}...", updatePath);
197 await ioManager.
ZipToDirectory(updatePath, updateZipData, cancellationToken).ConfigureAwait(
false);
205 await ioManager.
DeleteDirectory(updatePath,
default).ConfigureAwait(
false);
209 throw new AggregateException(e, e2);
215 await Restart(version, null,
true).ConfigureAwait(
false);
217 catch (OperationCanceledException)
219 logger.LogInformation(
"Server update cancelled!");
223 logger.LogError(
"Error updating server! Exception: {0}", e);
239 throw new ArgumentNullException(nameof(handler));
244 if (!RestartRequested)
246 logger.LogTrace(
"Registering restart handler {0}...", handler);
247 restartHandlers.Add(handler);
251 if (!RestartRequested)
252 restartHandlers.Remove(handler);
260 public Task Restart() => Restart(null, null,
true);
269 async Task
Restart(Version newVersion, Exception exception,
bool requireWatchdog)
271 CheckSanity(requireWatchdog);
273 logger.LogTrace(
"Begin Restart...");
277 if ((updating && newVersion == null) || RestartRequested)
279 logger.LogTrace(
"Aborted due to concurrency conflict!");
283 RestartRequested =
true;
284 propagatedException = exception;
287 if (exception == null)
288 using (var cts =
new CancellationTokenSource())
290 logger.LogInformation(
"Restarting server...");
291 var cancellationToken = cts.Token;
292 var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList());
294 var expiryTask = Task.Delay(TimeSpan.FromMilliseconds(generalConfiguration.
RestartTimeout));
295 await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(
false);
296 logger.LogTrace(
"Joining restart handlers...");
300 await eventsTask.ConfigureAwait(
false);
302 catch (OperationCanceledException)
304 logger.LogError(
"Restart timeout hit! Existing DreamDaemon processes will be lost and must be killed manually before being restarted with TGS!");
308 logger.LogError(
"Restart handlers error! Exception: {0}", e);
312 logger.LogTrace(
"Stopping host...");
313 cancellationTokenSource.Cancel();
317 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
int RestartTimeout
The timeout milliseconds for restarting the server
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
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