Fix webroot issue in deployments.

Propagate startup exceptions to the watchdog
This commit is contained in:
Jordan Brown
2018-11-27 16:04:17 -05:00
parent f9835c11de
commit d3b5a10701
4 changed files with 98 additions and 38 deletions
@@ -39,6 +39,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly IJobManager jobManager;
/// <summary>
/// The <see cref="IServerControl"/> for the <see cref="InstanceManager"/>
/// </summary>
readonly IServerControl serverControl;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="InstanceManager"/>
/// </summary>
@@ -67,7 +72,7 @@ namespace Tgstation.Server.Host.Components
/// <param name="databaseContextFactory">The value of <paramref name="databaseContextFactory"/></param>
/// <param name="application">The value of <see cref="application"/></param>
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
/// <param name="serverControl">The <see cref="IServerControl"/> used to register the <see cref="InstanceManager"/> as a <see cref="IRestartHandler"/></param>
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger<InstanceManager> logger)
{
@@ -76,8 +81,7 @@ namespace Tgstation.Server.Host.Components
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
if (serverControl == null)
throw new ArgumentNullException(nameof(serverControl));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
serverControl.RegisterForRestart(this);
@@ -215,7 +219,7 @@ namespace Tgstation.Server.Host.Components
await factoryStartup.ConfigureAwait(false);
await dbInstances.ForEachAsync(metadata => tasks.Add(metadata.Online.Value ? OnlineInstance(metadata, cancellationToken) : Task.CompletedTask), cancellationToken).ConfigureAwait(false);
await Task.WhenAll(tasks).ConfigureAwait(false);
logger.LogInformation("Instance manager ready!");
logger.LogInformation("Server ready!");
application.Ready(null);
}
catch (OperationCanceledException)
@@ -226,6 +230,14 @@ namespace Tgstation.Server.Host.Components
{
logger.LogCritical("Instance manager startup error! Exception: {0}", e);
application.Ready(e);
try
{
await serverControl.Die(e).ConfigureAwait(false);
}
catch (Exception e2)
{
logger.LogCritical("Failed to kill server! Exception: {0}", e2);
}
}
});
@@ -36,5 +36,12 @@ namespace Tgstation.Server.Host.Core
/// </summary>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task Restart();
/// <summary>
/// Kill the server with a fatal exception
/// </summary>
/// <param name="exception">The <see cref="Exception"/> to propagate to the watchdog if any</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task Die(Exception exception);
}
}
+57 -33
View File
@@ -48,6 +48,11 @@ namespace Tgstation.Server.Host
/// </summary>
CancellationTokenSource cancellationTokenSource;
/// <summary>
/// The <see cref="Exception"/> to propagate when the server terminates
/// </summary>
Exception propagatedException;
/// <summary>
/// If a server update has been or is being applied
/// </summary>
@@ -63,6 +68,8 @@ namespace Tgstation.Server.Host
this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder));
this.updatePath = updatePath;
webHostBuilder.ConfigureServices(serviceCollection => serviceCollection.AddSingleton<IServerControl>(this));
restartHandlers = new List<IRestartHandler>();
}
@@ -72,13 +79,22 @@ namespace Tgstation.Server.Host
/// <param name="checkWatchdog">If <see cref="WatchdogPresent"/> should be checked</param>
void CheckSanity(bool checkWatchdog)
{
if (checkWatchdog && !WatchdogPresent)
if (checkWatchdog && !WatchdogPresent && propagatedException == null)
throw new InvalidOperationException("Server restarts are not supported");
if (cancellationTokenSource == null || logger == null)
throw new InvalidOperationException("Tried to control a non-running Server!");
}
/// <summary>
/// Re-throw <see cref="propagatedException"/> if it exists
/// </summary>
void CheckExceptionPropagation()
{
if (propagatedException != null)
throw propagatedException;
}
/// <inheritdoc />
public async Task RunAsync(CancellationToken cancellationToken)
{
@@ -94,16 +110,20 @@ namespace Tgstation.Server.Host
};
fsWatcher.EnableRaisingEvents = true;
}
using (var webHost = webHostBuilder
.UseStartup<Application>()
.ConfigureServices(serviceCollection => serviceCollection.AddSingleton<IServerControl>(this))
.Build()
)
{
logger = webHost.Services.GetRequiredService<ILogger<Server>>();
await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false);
}
using (var webHost = webHostBuilder.Build())
try
{
logger = webHost.Services.GetRequiredService<ILogger<Server>>();
await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
CheckExceptionPropagation();
throw;
}
}
CheckExceptionPropagation();
}
/// <inheritdoc />
@@ -163,7 +183,7 @@ namespace Tgstation.Server.Host
throw;
}
await Restart(version).ConfigureAwait(false);
await Restart(version, null).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -207,14 +227,15 @@ namespace Tgstation.Server.Host
}
/// <inheritdoc />
public Task Restart() => Restart(null);
public Task Restart() => Restart(null, null);
/// <summary>
/// Implements <see cref="Restart()"/>
/// </summary>
/// <param name="newVersion">The <see cref="Version"/> of any potential updates being applied</param>
/// <param name="exception">The potential value of <see cref="propagatedException"/></param>
/// <returns></returns>
async Task Restart(Version newVersion)
async Task Restart(Version newVersion, Exception exception)
{
CheckSanity(true);
@@ -228,33 +249,36 @@ namespace Tgstation.Server.Host
return;
}
RestartRequested = true;
propagatedException = exception;
}
logger.LogInformation("Restarting server...");
using (var cts = new CancellationTokenSource())
{
logger.LogTrace("Running restart handlers...");
var cancellationToken = cts.Token;
var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList());
//YA GOT 10 SECONDS
var expiryTask = Task.Delay(TimeSpan.FromSeconds(10));
await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false);
logger.LogTrace("Joining restart handlers...");
cts.Cancel();
try
if (exception == null)
using (var cts = new CancellationTokenSource())
{
await eventsTask.ConfigureAwait(false);
logger.LogInformation("Restarting server...");
var cancellationToken = cts.Token;
var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList());
//YA GOT 10 SECONDS
var expiryTask = Task.Delay(TimeSpan.FromSeconds(10));
await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false);
logger.LogTrace("Joining restart handlers...");
cts.Cancel();
try
{
await eventsTask.ConfigureAwait(false);
}
catch (OperationCanceledException) { }
catch (Exception e)
{
logger.LogError("Restart handlers error! Exception: {0}", e);
}
}
catch (OperationCanceledException) { }
catch (Exception e)
{
logger.LogError("Restart handlers error! Exception: {0}", e);
}
}
logger.LogTrace("Stopping host...");
cancellationTokenSource.Cancel();
}
/// <inheritdoc />
public Task Die(Exception exception) => Restart(null, exception);
}
}
+18 -1
View File
@@ -1,5 +1,10 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using System.Reflection;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host
{
@@ -7,6 +12,18 @@ namespace Tgstation.Server.Host
public sealed class ServerFactory : IServerFactory
{
/// <inheritdoc />
public IServer CreateServer(string[] args, string updatePath) => new Server(WebHost.CreateDefaultBuilder(args ?? throw new ArgumentNullException(nameof(args))), updatePath);
public IServer CreateServer(string[] args, string updatePath) {
var webHost = WebHost.CreateDefaultBuilder(args ?? throw new ArgumentNullException(nameof(args)))
.ConfigureAppConfiguration((context, configurationBuilder) => configurationBuilder.SetBasePath(Directory.GetCurrentDirectory()))
.UseStartup<Application>()
.SuppressStatusMessages(true)
.UseShutdownTimeout(TimeSpan.FromMinutes(1));
if(updatePath != null)
webHost.UseContentRoot(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
return new Server(webHost, updatePath);
}
}
}