Auto updates working at last

This commit is contained in:
Cyberboss
2018-08-01 15:45:59 -04:00
parent 2722b62673
commit 189bbafe4c
17 changed files with 174 additions and 88 deletions
+14 -6
View File
@@ -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;
}
}
}
}
@@ -26,13 +26,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="echo 'Publishing host to watchdog target path for configuration $(ConfigurationName)'&#xD;&#xA;dotnet publish $(ProjectDir)../Tgstation.Server.Host/Tgstation.Server.Host.csproj -c $(ConfigurationName) -o $(TargetDir)lib/Default" />
</Target>
<Target Name="AfterPublishDeploy" AfterTargets="Publish">
<Exec Command="echo 'Publishing host to watchdog publish dir for configuration $(ConfigurationName)'&#xD;&#xA;dotnet publish $(ProjectDir)../Tgstation.Server.Host/Tgstation.Server.Host.csproj -c $(ConfigurationName) -o $(PublishDir)lib/Default" />
</Target>
</Project>
+70 -22
View File
@@ -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...");
}
}
}
}
@@ -43,9 +43,9 @@ namespace Tgstation.Server.Host.Components
readonly IByondTopicSender byondTopicSender;
/// <summary>
/// The <see cref="IServerUpdater"/> for the <see cref="InstanceFactory"/>
/// The <see cref="IServerControl"/> for the <see cref="InstanceFactory"/>
/// </summary>
readonly IServerUpdater serverUpdater;
readonly IServerControl serverUpdater;
/// <summary>
/// The <see cref="ICryptographySuite"/> for the <see cref="InstanceFactory"/>
@@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Components
/// <param name="byondInstaller">The value of <see cref="byondInstaller"/></param>
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
/// <param name="scriptExecutor">The value of <see cref="scriptExecutor"/></param>
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));
@@ -131,7 +131,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="chat">The value of <see cref="chat"/></param>
/// <param name="sessionControllerFactory">The value of <see cref="sessionControllerFactory"/></param>
/// <param name="dmbFactory">The value of <see cref="dmbFactory"/></param>
/// <param name="serverUpdater">The <see cref="IServerUpdater"/> for the <see cref="Watchdog"/></param>
/// <param name="serverUpdater">The <see cref="IServerControl"/> for the <see cref="Watchdog"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="reattachInfoHandler">The value of <see cref="reattachInfoHandler"/></param>
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
@@ -140,7 +140,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="instance">The value of <see cref="instance"/></param>
/// <param name="autoStart">The value of <see cref="autoStart"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger<Watchdog> 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<Watchdog> 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);
@@ -22,9 +22,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
readonly ISessionControllerFactory sessionControllerFactory;
/// <summary>
/// The <see cref="IServerUpdater"/> for the <see cref="WatchdogFactory"/>
/// The <see cref="IServerControl"/> for the <see cref="WatchdogFactory"/>
/// </summary>
readonly IServerUpdater serverUpdater;
readonly IServerControl serverUpdater;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="WatchdogFactory"/>
@@ -69,7 +69,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
/// <param name="instance">The value of <see cref="instance"/></param>
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));
@@ -36,9 +36,9 @@ namespace Tgstation.Server.Host.Controllers
readonly IGitHubClient gitHubClient;
/// <summary>
/// The <see cref="IServerUpdater"/> for the <see cref="AdministrationController"/>
/// The <see cref="IServerControl"/> for the <see cref="AdministrationController"/>
/// </summary>
readonly IServerUpdater serverUpdater;
readonly IServerControl serverUpdater;
/// <summary>
/// The <see cref="IApplication"/> for the <see cref="AdministrationController"/>
@@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/></param>
public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger<AdministrationController> logger, IOptions<UpdatesConfiguration> updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false)
public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger<AdministrationController> logger, IOptions<UpdatesConfiguration> 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
}
/// <inheritdoc />
[HttpDelete]
[TgsAuthorize(AdministrationRights.RestartHost)]
public override Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
{
serverUpdater.Restart();
return Task.FromResult((IActionResult)Ok());
}
public Task<IActionResult> Delete() => Task.FromResult(serverUpdater.Restart() ? (IActionResult)Ok() : StatusCode((int)HttpStatusCode.NotImplemented));
}
}
@@ -114,8 +114,9 @@ namespace Tgstation.Server.Host.Controllers
}
/// <inheritdoc />
[HttpDelete]
[TgsAuthorize(DreamDaemonRights.Shutdown)]
public override async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
public async Task<IActionResult> Delete(CancellationToken cancellationToken)
{
//alias for stopping DD
var instance = instanceManager.GetInstance(Instance);
@@ -232,10 +232,15 @@ namespace Tgstation.Server.Host.Core
/// Configure the <see cref="Application"/>
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/> to configure</param>
public void Configure(IApplicationBuilder applicationBuilder)
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="Application"/></param>
public void Configure(IApplicationBuilder applicationBuilder, ILogger<Application> logger)
{
if (applicationBuilder == null)
throw new ArgumentNullException(nameof(applicationBuilder));
if (logger == null)
throw new ArgumentNullException(nameof(logger));
logger.LogInformation(VersionString);
serverAddresses = applicationBuilder.ServerFeatures.Get<IServerAddressesFeature>();
@@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Core
/// <summary>
/// Represents a service that may take an updated <see cref="Host"/> assembly and run it, stopping the current assembly in the process
/// </summary>
public interface IServerUpdater
public interface IServerControl
{
/// <summary>
/// Run a new <see cref="Host"/> assembly and stop the current one. This will likely trigger all active <see cref="CancellationToken"/>s
@@ -20,14 +20,15 @@ namespace Tgstation.Server.Host.Core
Task<bool> ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken);
/// <summary>
/// Register a given <paramref name="action"/> to run before stopping the server for updates
/// Register a given <paramref name="action"/> to run before stopping the server for a restart
/// </summary>
/// <param name="action">The <see cref="Action"/> to run</param>
void RegisterForUpdate(Action action);
void RegisterForRestart(Action action);
/// <summary>
/// Restarts the <see cref="Host"/>
/// </summary>
void Restart();
/// <returns><see langword="true"/> if live restarts are supported, <see langword="false"/> otherwise</returns>
bool Restart();
}
}
+5
View File
@@ -9,6 +9,11 @@ namespace Tgstation.Server.Host
/// </summary>
public interface IServer : IDisposable
{
/// <summary>
/// If the <see cref="IServer"/> should restart
/// </summary>
bool RestartRequested { get; }
/// <summary>
/// Runs the <see cref="IServer"/>
/// </summary>
+30 -17
View File
@@ -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
{
/// <summary>
/// Entrypoint for the <see cref="System.Diagnostics.Process"/>
/// Entrypoint for the <see cref="Process"/>
/// </summary>
static class Program
{
@@ -20,47 +21,59 @@ namespace Tgstation.Server.Host
/// Entrypoint for the <see cref="Program"/>
/// </summary>
/// <param name="args">The command line arguments</param>
/// <returns>The <see cref="System.Diagnostics.Process.ExitCode"/></returns>
/// <returns>The <see cref="Process.ExitCode"/></returns>
public static async Task<int> Main(string[] args)
{
var listArgs = new List<string>(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;
}
}
}
+20 -7
View File
@@ -11,8 +11,11 @@ using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host
{
/// <inheritdoc />
sealed class Server : IServer, IServerUpdater
sealed class Server : IServer, IServerControl
{
/// <inheritdoc />
public bool RestartRequested { get; private set; }
/// <summary>
/// The <see cref="IWebHostBuilder"/> for the <see cref="Server"/>
/// </summary>
@@ -32,7 +35,7 @@ namespace Tgstation.Server.Host
/// If a server update has been applied
/// </summary>
bool updated;
/// <summary>
/// The <see cref="cancellationTokenSource"/> for the <see cref="Server"/>
/// </summary>
@@ -50,6 +53,7 @@ namespace Tgstation.Server.Host
semaphore = new SemaphoreSlim(1);
updated = false;
RestartRequested = false;
}
/// <inheritdoc />
@@ -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<Application>()
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerUpdater>(this))
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerControl>(this))
.Build()
)
await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false);
@@ -111,19 +115,28 @@ namespace Tgstation.Server.Host
}
/// <inheritdoc />
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();
});
}
/// <inheritdoc />
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;
}
}
}
@@ -59,4 +59,8 @@
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>
@@ -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<bool> 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<Application>()
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerUpdater>(this))
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerControl>(this))
.Build()
)
{