From 189bbafe4c9eebe6823b80e003ab54b66131e2cc Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 15:45:59 -0400 Subject: [PATCH] Auto updates working at last --- src/Tgstation.Server.Host.Console/Program.cs | 20 ++-- .../Tgstation.Server.Host.Watchdog.csproj | 9 -- .../Watchdog.cs | 92 ++++++++++++++----- .../Components/InstanceFactory.cs | 6 +- .../Components/Watchdog/Watchdog.cs | 6 +- .../Components/Watchdog/WatchdogFactory.cs | 6 +- .../Controllers/AdministrationController.cs | 13 +-- .../Controllers/DreamDaemonController.cs | 3 +- src/Tgstation.Server.Host/Core/Application.cs | 7 +- .../{IServerUpdater.cs => IServerControl.cs} | 9 +- src/Tgstation.Server.Host/IServer.cs | 5 + src/Tgstation.Server.Host/Program.cs | 47 ++++++---- src/Tgstation.Server.Host/Server.cs | 27 ++++-- .../Tgstation.Server.Host.csproj | 4 + .../appsettings.Docker.json | 0 .../appsettings.json | 0 .../Core/TestApplication.cs | 8 +- 17 files changed, 174 insertions(+), 88 deletions(-) rename src/Tgstation.Server.Host/Core/{IServerUpdater.cs => IServerControl.cs} (84%) rename src/{Tgstation.Server.Host.Watchdog => Tgstation.Server.Host}/appsettings.Docker.json (100%) rename src/{Tgstation.Server.Host.Watchdog => Tgstation.Server.Host}/appsettings.json (100%) diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index e85a27b86b..0a62b2ed4e 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -39,13 +39,21 @@ namespace Tgstation.Server.Host.Console } using (var cts = new CancellationTokenSource()) { - AppDomain.CurrentDomain.ProcessExit += (a, b) => cts.Cancel(); - System.Console.CancelKeyPress += (a, b) => + void AppDomainHandler(object a, EventArgs b) => cts.Cancel(); + AppDomain.CurrentDomain.ProcessExit += AppDomainHandler; + try { - b.Cancel = true; - cts.Cancel(); - }; - await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), cts.Token).ConfigureAwait(false); + System.Console.CancelKeyPress += (a, b) => + { + b.Cancel = true; + cts.Cancel(); + }; + await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), cts.Token).ConfigureAwait(false); + } + finally + { + AppDomain.CurrentDomain.ProcessExit -= AppDomainHandler; + } } } } diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index cd45758108..e45d409e53 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -26,13 +26,4 @@ PreserveNewest - - - - - - - - - diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index c2e2afe4c2..f48b999c99 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -39,9 +39,10 @@ namespace Tgstation.Server.Host.Watchdog var enviromentPath = Environment.GetEnvironmentVariable("PATH"); var paths = enviromentPath.Split(';'); + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var exeName = "dotnet"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (isWindows) exeName += ".exe"; var dotnetPath = paths.Select(x => Path.Combine(x, exeName)) @@ -57,13 +58,26 @@ namespace Tgstation.Server.Host.Watchdog var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog + var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); #if DEBUG - rootLocation = Path.GetFullPath("../../../../Tgstation.Server.Host.Watchdog/bin/Debug/netstandard2.0"); + //just copy the shit where it belongs + Directory.Delete(assemblyStoragePath, true); + Directory.CreateDirectory(defaultAssemblyPath); + + var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.0"; + foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) + Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath)); + + foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) + File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true); + + const string AppSettingsJson = "appsettings.json"; + var rootJson = Path.Combine(rootLocation, AppSettingsJson); + File.Delete(rootJson); + File.Move(Path.Combine(defaultAssemblyPath, AppSettingsJson), rootJson); #endif - var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog - - var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName); @@ -86,6 +100,7 @@ namespace Tgstation.Server.Host.Watchdog using (logger.BeginScope("Host invocation")) { updateDirectory = Path.GetFullPath(Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString())); + logger.LogInformation("Update path set to {0}", updateDirectory); using (var process = new Process()) { process.StartInfo.FileName = dotnetPath; @@ -96,6 +111,8 @@ namespace Tgstation.Server.Host.Watchdog '"' + assemblyPath + '"', updateDirectory }; + if (Debugger.IsAttached) + arguments.Add("--attach-debugger"); arguments.AddRange(args); process.StartInfo.Arguments = String.Join(" ", arguments); @@ -108,13 +125,16 @@ namespace Tgstation.Server.Host.Watchdog tcs.TrySetResult(null); }; process.EnableRaisingEvents = true; - + logger.LogInformation("Launching host..."); var iShotTheSheriff = false; try { process.Start(); + + using (var processCts = new CancellationTokenSource()) + using (processCts.Token.Register(() => tcs.TrySetResult(null))) using (cancellationToken.Register(() => { if (!Directory.Exists(updateDirectory)) @@ -127,8 +147,11 @@ namespace Tgstation.Server.Host.Watchdog logger.LogInformation("Will force close host process if it doesn't exit in 10 seconds..."); - Thread.Sleep(TimeSpan.FromSeconds(10)); //things get weird if we use tasks or other stuff - tcs.TrySetResult(null); + try + { + processCts.CancelAfter(TimeSpan.FromSeconds(10)); + } + catch (ObjectDisposedException) { } //race conditions })) await tcs.Task.ConfigureAwait(false); } @@ -151,10 +174,15 @@ namespace Tgstation.Server.Host.Watchdog switch (process.ExitCode) { case 0: - //just a restart - logger.LogInformation("Watchdog will restart host..."); - break; + return; case 1: + if (!cancellationToken.IsCancellationRequested) + //just a restart + logger.LogInformation("Watchdog will restart host..."); + else + logger.LogWarning("Host requested restart but watchdog shutdown is in progress!"); + break; + case 2: //update path is now an exception document logger.LogCritical("Host crashed, propagating exception dump..."); var data = File.ReadAllText(updateDirectory); @@ -177,29 +205,46 @@ namespace Tgstation.Server.Host.Watchdog } } + //HEY YOU + //BE WARNED THAT IF YOU DEBUGGED THE HOST PROCESS THAT JUST LAUNCHED THE DEBUGGER WILL HOLD A LOCK ON THE DIRECTORY + //THIS MEANS THE FIRST DIRECTORY.MOVE WILL THROW if (Directory.Exists(updateDirectory)) { + logger.LogInformation("Applying server update..."); + if (isWindows) + { + //windows dick sucking resource unlocking + GC.Collect(); + await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false); + } var tempPath = Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString()); - Directory.Move(defaultAssemblyPath, tempPath); try { - Directory.Move(updateDirectory, defaultAssemblyPath); - logger.LogInformation("Server update complete, deleting old server..."); + Directory.Move(defaultAssemblyPath, tempPath); try { - Directory.Delete(tempPath, true); + Directory.Move(updateDirectory, defaultAssemblyPath); + logger.LogInformation("Server update complete, deleting old server..."); + try + { + Directory.Delete(tempPath, true); + } + catch (Exception e) + { + logger.LogWarning("Error deleting old server at {0}! Exception: {1}", tempPath, e); + } } catch (Exception e) { - logger.LogWarning("Error deleting old server at {0}! Exception: {1}", tempPath, e); + logger.LogError("Error moving updated server directory, attempting revert! Exception: {0}", e); + Directory.Delete(defaultAssemblyPath, true); + Directory.Move(tempPath, defaultAssemblyPath); + logger.LogInformation("Revert successful!"); } } - catch (Exception e) + catch(Exception e) { - logger.LogError("Error moving updated server directory, attempting revert! Exception: {0}", e); - Directory.Delete(defaultAssemblyPath, true); - Directory.Move(tempPath, defaultAssemblyPath); - logger.LogInformation("Revert successful!"); + logger.LogWarning("Failed to move out active host assembly! Exception: {0}", e); } } } @@ -216,7 +261,10 @@ namespace Tgstation.Server.Host.Watchdog { logger.LogCritical("Watchdog error! Exception: {0}", e); } - logger.LogInformation("Host watchdog exiting..."); + finally + { + logger.LogInformation("Host watchdog exiting..."); + } } } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 9646712c84..2994bf1ef5 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -43,9 +43,9 @@ namespace Tgstation.Server.Host.Components readonly IByondTopicSender byondTopicSender; /// - /// The for the + /// The for the /// - readonly IServerUpdater serverUpdater; + readonly IServerControl serverUpdater; /// /// The for the @@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor) + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index c27db4fed0..6c0d58c864 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -131,7 +131,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - /// The for the + /// The for the /// The value of /// The value of /// The value of @@ -140,7 +140,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -156,7 +156,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (serverUpdater == null) throw new ArgumentNullException(nameof(serverUpdater)); - serverUpdater.RegisterForUpdate(() => releaseServers = true); + serverUpdater.RegisterForRestart(() => releaseServers = true); chat.RegisterCommandHandler(this); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 2cf4f407af..65b0452042 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -22,9 +22,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly ISessionControllerFactory sessionControllerFactory; /// - /// The for the + /// The for the /// - readonly IServerUpdater serverUpdater; + readonly IServerControl serverUpdater; /// /// The for the @@ -69,7 +69,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance) + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerControl serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index f690dc720d..551dca9dc6 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -36,9 +36,9 @@ namespace Tgstation.Server.Host.Controllers readonly IGitHubClient gitHubClient; /// - /// The for the + /// The for the /// - readonly IServerUpdater serverUpdater; + readonly IServerControl serverUpdater; /// /// The for the @@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The containing value of - public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false) + public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); @@ -163,11 +163,8 @@ namespace Tgstation.Server.Host.Controllers } /// + [HttpDelete] [TgsAuthorize(AdministrationRights.RestartHost)] - public override Task Delete(long id, CancellationToken cancellationToken) - { - serverUpdater.Restart(); - return Task.FromResult((IActionResult)Ok()); - } + public Task Delete() => Task.FromResult(serverUpdater.Restart() ? (IActionResult)Ok() : StatusCode((int)HttpStatusCode.NotImplemented)); } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 4d805cca10..34d5be50c0 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -114,8 +114,9 @@ namespace Tgstation.Server.Host.Controllers } /// + [HttpDelete] [TgsAuthorize(DreamDaemonRights.Shutdown)] - public override async Task Delete(long id, CancellationToken cancellationToken) + public async Task Delete(CancellationToken cancellationToken) { //alias for stopping DD var instance = instanceManager.GetInstance(Instance); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b1108ed177..9674267ce9 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -232,10 +232,15 @@ namespace Tgstation.Server.Host.Core /// Configure the /// /// The to configure - public void Configure(IApplicationBuilder applicationBuilder) + /// The for the + public void Configure(IApplicationBuilder applicationBuilder, ILogger logger) { if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + + logger.LogInformation(VersionString); serverAddresses = applicationBuilder.ServerFeatures.Get(); diff --git a/src/Tgstation.Server.Host/Core/IServerUpdater.cs b/src/Tgstation.Server.Host/Core/IServerControl.cs similarity index 84% rename from src/Tgstation.Server.Host/Core/IServerUpdater.cs rename to src/Tgstation.Server.Host/Core/IServerControl.cs index 91fa46b2e7..377b7d4a9c 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdater.cs +++ b/src/Tgstation.Server.Host/Core/IServerControl.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Core /// /// Represents a service that may take an updated assembly and run it, stopping the current assembly in the process /// - public interface IServerUpdater + public interface IServerControl { /// /// Run a new assembly and stop the current one. This will likely trigger all active s @@ -20,14 +20,15 @@ namespace Tgstation.Server.Host.Core Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken); /// - /// Register a given to run before stopping the server for updates + /// Register a given to run before stopping the server for a restart /// /// The to run - void RegisterForUpdate(Action action); + void RegisterForRestart(Action action); /// /// Restarts the /// - void Restart(); + /// if live restarts are supported, otherwise + bool Restart(); } } diff --git a/src/Tgstation.Server.Host/IServer.cs b/src/Tgstation.Server.Host/IServer.cs index e15152cbd4..531b3b8afc 100644 --- a/src/Tgstation.Server.Host/IServer.cs +++ b/src/Tgstation.Server.Host/IServer.cs @@ -9,6 +9,11 @@ namespace Tgstation.Server.Host /// public interface IServer : IDisposable { + /// + /// If the should restart + /// + bool RestartRequested { get; } + /// /// Runs the /// diff --git a/src/Tgstation.Server.Host/Program.cs b/src/Tgstation.Server.Host/Program.cs index f8722c045e..65f5199f40 100644 --- a/src/Tgstation.Server.Host/Program.cs +++ b/src/Tgstation.Server.Host/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -7,7 +8,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host { /// - /// Entrypoint for the + /// Entrypoint for the /// static class Program { @@ -20,47 +21,59 @@ namespace Tgstation.Server.Host /// Entrypoint for the /// /// The command line arguments - /// The + /// The public static async Task Main(string[] args) { var listArgs = new List(args); - //first arg is 100% always the update path + //first arg is 100% always the update path, starting it otherwise is solely for debugging purposes string updatePath; if (listArgs.Count > 0) { updatePath = listArgs[0]; listArgs.RemoveAt(0); -#if DEBUG - System.Diagnostics.Debugger.Launch(); -#endif + if (listArgs.Remove("--attach-debugger")) + Debugger.Launch(); } else updatePath = null; try { - using (var cts = new CancellationTokenSource()) + using (var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath)) { - AppDomain.CurrentDomain.ProcessExit += (a, b) => cts.Cancel(); - Console.CancelKeyPress += (a, b) => + try { - b.Cancel = true; - cts.Cancel(); - }; - using (var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath)) - await server.RunAsync(cts.Token).ConfigureAwait(false); + using (var cts = new CancellationTokenSource()) + { + void AppDomainHandler(object a, EventArgs b) => cts.Cancel(); + AppDomain.CurrentDomain.ProcessExit += AppDomainHandler; + try + { + Console.CancelKeyPress += (a, b) => + { + b.Cancel = true; + cts.Cancel(); + }; + await server.RunAsync(cts.Token).ConfigureAwait(false); + } + finally + { + AppDomain.CurrentDomain.ProcessExit -= AppDomainHandler; + } + } + } + catch (OperationCanceledException) { } + return server.RestartRequested ? 1 : 0; } } - catch (OperationCanceledException) { } catch (Exception e) { if (updatePath != null) { File.WriteAllText(updatePath, e.ToString()); - return 1; + return 2; } throw; } - return 0; } } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 1dee6ddbe9..f24dc3b4e9 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -11,8 +11,11 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host { /// - sealed class Server : IServer, IServerUpdater + sealed class Server : IServer, IServerControl { + /// + public bool RestartRequested { get; private set; } + /// /// The for the /// @@ -32,7 +35,7 @@ namespace Tgstation.Server.Host /// If a server update has been applied /// bool updated; - + /// /// The for the /// @@ -50,6 +53,7 @@ namespace Tgstation.Server.Host semaphore = new SemaphoreSlim(1); updated = false; + RestartRequested = false; } /// @@ -66,14 +70,14 @@ namespace Tgstation.Server.Host { fsWatcher.Created += (a, b) => { - if (b.Name == updatePath && File.Exists(b.FullPath)) + if (b.FullPath == updatePath && File.Exists(b.FullPath)) cancellationTokenSource.Cancel(); }; fsWatcher.EnableRaisingEvents = true; } using (var webHost = webHostBuilder .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false); @@ -111,19 +115,28 @@ namespace Tgstation.Server.Host } /// - public void RegisterForUpdate(Action action) + public void RegisterForRestart(Action action) { + if (action == null) + throw new ArgumentNullException(nameof(action)); if (cancellationTokenSource == null) throw new InvalidOperationException("Tried to register an update action on a non-running Server!"); - cancellationTokenSource.Token.Register(action); + cancellationTokenSource.Token.Register(() => { + if (RestartRequested) + action(); + }); } /// - public void Restart() + public bool Restart() { + if (updatePath == null) + return false; if (cancellationTokenSource == null) throw new InvalidOperationException("Tried to restart a non-running Server!"); + RestartRequested = true; cancellationTokenSource.Cancel(); + return true; } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index a853acc041..85d750b8fc 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -59,4 +59,8 @@ + + + + diff --git a/src/Tgstation.Server.Host.Watchdog/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json similarity index 100% rename from src/Tgstation.Server.Host.Watchdog/appsettings.Docker.json rename to src/Tgstation.Server.Host/appsettings.Docker.json diff --git a/src/Tgstation.Server.Host.Watchdog/appsettings.json b/src/Tgstation.Server.Host/appsettings.json similarity index 100% rename from src/Tgstation.Server.Host.Watchdog/appsettings.json rename to src/Tgstation.Server.Host/appsettings.json diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 4ee3d58082..319b6d94c8 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -11,13 +11,13 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Core.Tests { [TestClass] - public sealed class TestApplication : IServerUpdater + public sealed class TestApplication : IServerControl { public Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException(); - public void RegisterForUpdate(Action action) => throw new NotImplementedException(); + public void RegisterForRestart(Action action) => throw new NotImplementedException(); - public void Restart() => throw new NotImplementedException(); + public bool Restart() => throw new NotImplementedException(); [TestMethod] public async Task TestSuccessfulStartup() @@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Core.Tests { using (var webHost = WebHost.CreateDefaultBuilder(new string[] { "Database:DatabaseType=Sqlite", "Database:ConnectionString=Data Source=" + dbName }) //force it to use sqlite .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) {