From e929253a0f21c2543f63b422b79094c8bcbed381 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Apr 2018 10:48:40 -0400 Subject: [PATCH] More stuff --- .../Models/Internal/CompileJob.cs | 5 + .../Components/DreamDaemon.cs | 107 +++++++---------- .../Components/DreamDaemonExecutor.cs | 110 ++++++++++++++++++ .../Components/DreamDaemonParameters.cs | 11 ++ .../Components/IDreamDaemonExecutor.cs | 25 ++++ .../Components/ServerControlEventArgs.cs | 2 + 6 files changed, 194 insertions(+), 66 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs create mode 100644 src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index add902e9cb..d31366048f 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -40,6 +40,11 @@ namespace Tgstation.Server.Api.Models.Internal /// public string Output { get; set; } + /// + /// The Game folder the results were compiled into + /// + public Guid? OutputGuid { get; set; } + /// /// Exit code of DM. If /// diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index 6ca1a8fc6b..15926c884d 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -1,6 +1,4 @@ using System; -using System.Diagnostics; -using System.Globalization; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -51,12 +49,22 @@ namespace Tgstation.Server.Host.Components /// The for /// readonly IInstanceShutdownMethod instanceShutdownMethod; + /// + /// The for + /// readonly IIOManager ioManager; + /// + /// The for + /// + readonly IDreamDaemonExecutor dreamDaemonExecutor; + + /// + /// Used for write control to class variables + /// + readonly SemaphoreSlim semaphore; readonly bool autoStart; - readonly SemaphoreSlim semaphore; - DreamDaemonLaunchParameters currentLaunchParameters; CancellationTokenSource watchdogCancellationTokenSource; @@ -78,6 +86,7 @@ namespace Tgstation.Server.Host.Components semaphore = new SemaphoreSlim(1); } + /// public void Dispose() { if (watchdogCancellationTokenSource != null) @@ -97,22 +106,7 @@ namespace Tgstation.Server.Host.Components return Task.CompletedTask; } } - - static string SecurityWord(DreamDaemonSecurity securityLevel) - { - switch (securityLevel) - { - case DreamDaemonSecurity.Safe: - return "safe"; - case DreamDaemonSecurity.Trusted: - return "trusted"; - case DreamDaemonSecurity.Ultrasafe: - return "ultrasafe"; - default: - throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel)); - } - } - + async Task Watchdog(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) { async Task RunOnce(string executablePath) @@ -121,53 +115,28 @@ namespace Tgstation.Server.Host.Components throw new InvalidOperationException("No byond version installed!"); await byond.ClearCache(cancellationToken).ConfigureAwait(false); - using (var proc = new Process()) + + string dmb = null; //TODO + var accessToken = cryptographySuite.GetSecureString(); + var usePrimaryPort = true; + + var ddTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, executablePath, dmb, accessToken, usePrimaryPort, cancellationToken); + + await onSuccessfulStartup.Task.ConfigureAwait(false); + + interop.SetRun(usePrimaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken); + + int ddExitCode; + try { - var accessToken = cryptographySuite.GetSecureString(); - proc.StartInfo.FileName = executablePath; - proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {5}-close -verbose -params \"server_service={3}&server_service_version={4}&{6}={7}\" -{2} -public", DMB, launchParameters.PrimaryPort, SecurityWord(launchParameters.SecurityLevel), accessToken, Application.Version, launchParameters.AllowWebClient ? "-webclient " : String.Empty); - - proc.EnableRaisingEvents = true; - var tcs = new TaskCompletionSource(); - proc.Exited += (a, b) => tcs.SetResult(null); - - try - { - proc.Start(); - - await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - - if (onSuccessfulStartup != null) - { - onSuccessfulStartup.SetResult(null); - onSuccessfulStartup = null; - } - - try - { - using (cancellationToken.Register(() => tcs.SetCanceled())) - await tcs.Task.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - return; - } - finally - { - if (!instanceShutdownMethod.GracefulShutdown) - { - proc.Kill(); - proc.WaitForExit(); - } - } - } - catch (Exception e) - { - onSuccessfulStartup?.SetException(e); - throw; - } - await eventConsumer.HandleEvent(proc.ExitCode != 0 ? EventType.DDCrash : EventType.DDExit, null, cancellationToken).ConfigureAwait(false); + ddExitCode = await ddTask.ConfigureAwait(false); } + finally + { + interop.SetRun(null, null); + } + + await eventConsumer.HandleEvent(ddExitCode != 0 ? EventType.DDCrash : EventType.DDExit, null, cancellationToken).ConfigureAwait(false); }; do @@ -176,6 +145,7 @@ namespace Tgstation.Server.Host.Components } while (!cancellationToken.IsCancellationRequested); } + /// public void CancelGracefulActions() { lock (this) @@ -185,6 +155,7 @@ namespace Tgstation.Server.Host.Components } } + /// public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { Task launchTask; @@ -199,6 +170,7 @@ namespace Tgstation.Server.Host.Components await launchTask.ConfigureAwait(false); } + /// public async Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -220,7 +192,7 @@ namespace Tgstation.Server.Host.Components } } - + /// public async Task Restart(bool graceful, CancellationToken cancellationToken) { await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -246,10 +218,13 @@ namespace Tgstation.Server.Host.Components } } + /// public Task StartAsync(CancellationToken cancellationToken) => autoStart ? Launch(currentLaunchParameters, cancellationToken) : Task.CompletedTask; + /// public Task StopAsync(CancellationToken cancellationToken) => Terminate(false, cancellationToken); + /// public async Task Terminate(bool graceful, CancellationToken cancellationToken) { await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs new file mode 100644 index 0000000000..56dc49d5e9 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -0,0 +1,110 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class DreamDaemonExecutor : IDreamDaemonExecutor + { + /// + /// Change a given into the appropriate DreamDaemon command line word + /// + /// The level to change + /// A representation of the command line parameter + static string SecurityWord(DreamDaemonSecurity securityLevel) + { + switch (securityLevel) + { + case DreamDaemonSecurity.Safe: + return "safe"; + case DreamDaemonSecurity.Trusted: + return "trusted"; + case DreamDaemonSecurity.Ultrasafe: + return "ultrasafe"; + default: + throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel)); + } + } + + /// + /// The for the + /// + readonly IInstanceShutdownMethod instanceShutdownMethod; + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// Construct a + /// + /// The value of + /// The value of + public DreamDaemonExecutor(IInstanceShutdownMethod instanceShutdownMethod, IIOManager ioManager) + { + this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + } + + /// + public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken) + { + using (var proc = new Process()) + { + proc.StartInfo.FileName = dreamDaemonPath; + proc.StartInfo.WorkingDirectory = ioManager.GetDirectoryName(dmbPath); + proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}&{8}={9}&{10}={11}&{12}={13}\"", dmbPath, usePrimaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel), + DreamDaemonParameters.AccessToken, accessToken, + DreamDaemonParameters.HostVersion, Application.Version, + DreamDaemonParameters.IsSecondaryServer, !usePrimaryPort, + DreamDaemonParameters.PrimaryPort, launchParameters.PrimaryPort, + DreamDaemonParameters.SecondaryPort, launchParameters.SecondaryPort); + + proc.EnableRaisingEvents = true; + var tcs = new TaskCompletionSource(); + proc.Exited += (a, b) => tcs.SetResult(null); + + try + { + proc.Start(); + + await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + + if (onSuccessfulStartup != null) + { + onSuccessfulStartup.SetResult(null); + onSuccessfulStartup = null; + } + + try + { + using (cancellationToken.Register(() => tcs.SetCanceled())) + await tcs.Task.ConfigureAwait(false); + } + finally + { + if (!instanceShutdownMethod.GracefulShutdown) + { + proc.Kill(); + proc.WaitForExit(); + } + } + + return proc.ExitCode; + } + catch (Exception e) + { + onSuccessfulStartup.SetException(e); + throw; + } + } + } + + } +} diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs new file mode 100644 index 0000000000..e6b409730d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs @@ -0,0 +1,11 @@ +namespace Tgstation.Server.Host.Components +{ + static class DreamDaemonParameters + { + public const string AccessToken = "tgs_key"; + public const string HostVersion = "tgs_ver"; + public const string PrimaryPort = "tgs_port1"; + public const string SecondaryPort = "tgs_port2"; + public const string IsSecondaryServer = "tgs_second"; + } +} diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs new file mode 100644 index 0000000000..5ed6534bbf --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components +{ + /// + /// For running a DreamDaemon instance + /// + interface IDreamDaemonExecutor + { + /// + /// Run a dream daemon instance + /// + /// The + /// The that is completed when dream daemon starts without crashing + /// The path to the dreamdaemon executable + /// The path to the .dmb to run, the working directory will be derived from this + /// The access token to be used for communication + /// If the server should open on or of + /// The for the operation + /// A representing the lifetime of the process and resulting in the exit code + Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs index ba9b7b2bef..74275114bc 100644 --- a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs +++ b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs @@ -7,6 +7,8 @@ namespace Tgstation.Server.Host.Components /// sealed class ServerControlEventArgs : EventArgs { + public bool PrimeReboot { get; set; } + /// /// If the event will result in a new run of the world ///