More stuff

This commit is contained in:
Cyberboss
2018-04-24 10:48:40 -04:00
parent fff7d1b10a
commit e929253a0f
6 changed files with 194 additions and 66 deletions
@@ -40,6 +40,11 @@ namespace Tgstation.Server.Api.Models.Internal
/// </summary>
public string Output { get; set; }
/// <summary>
/// The Game folder the results were compiled into
/// </summary>
public Guid? OutputGuid { get; set; }
/// <summary>
/// Exit code of DM. If <see langword="null"/>
/// </summary>
@@ -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 <see cref="IInstanceShutdownMethod"/> for <see cref="DreamDaemon"/>
/// </summary>
readonly IInstanceShutdownMethod instanceShutdownMethod;
/// <summary>
/// The <see cref="IIOManager"/> for <see cref="DreamDaemon"/>
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="IDreamDaemonExecutor"/> for <see cref="DreamDaemon"/>
/// </summary>
readonly IDreamDaemonExecutor dreamDaemonExecutor;
/// <summary>
/// Used for write control to class variables
/// </summary>
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);
}
/// <inheritdoc />
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<object> 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<object>();
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);
}
/// <inheritdoc />
public void CancelGracefulActions()
{
lock (this)
@@ -185,6 +155,7 @@ namespace Tgstation.Server.Host.Components
}
}
/// <inheritdoc />
public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
{
Task launchTask;
@@ -199,6 +170,7 @@ namespace Tgstation.Server.Host.Components
await launchTask.ConfigureAwait(false);
}
/// <inheritdoc />
public async Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
{
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -220,7 +192,7 @@ namespace Tgstation.Server.Host.Components
}
}
/// <inheritdoc />
public async Task Restart(bool graceful, CancellationToken cancellationToken)
{
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -246,10 +218,13 @@ namespace Tgstation.Server.Host.Components
}
}
/// <inheritdoc />
public Task StartAsync(CancellationToken cancellationToken) => autoStart ? Launch(currentLaunchParameters, cancellationToken) : Task.CompletedTask;
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken) => Terminate(false, cancellationToken);
/// <inheritdoc />
public async Task Terminate(bool graceful, CancellationToken cancellationToken)
{
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -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
{
/// <inheritdoc />
sealed class DreamDaemonExecutor : IDreamDaemonExecutor
{
/// <summary>
/// Change a given <paramref name="securityLevel"/> into the appropriate DreamDaemon command line word
/// </summary>
/// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to change</param>
/// <returns>A <see cref="string"/> representation of the command line parameter</returns>
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));
}
}
/// <summary>
/// The <see cref="IInstanceShutdownMethod"/> for the <see cref="DreamDaemonExecutor"/>
/// </summary>
readonly IInstanceShutdownMethod instanceShutdownMethod;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="DreamDaemonExecutor"/>
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// Construct a <see cref="DreamDaemonExecutor"/>
/// </summary>
/// <param name="instanceShutdownMethod">The value of <see cref="instanceShutdownMethod"/></param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
public DreamDaemonExecutor(IInstanceShutdownMethod instanceShutdownMethod, IIOManager ioManager)
{
this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
}
/// <inheritdoc />
public async Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> 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<object>();
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;
}
}
}
}
}
@@ -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";
}
}
@@ -0,0 +1,25 @@
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models.Internal;
namespace Tgstation.Server.Host.Components
{
/// <summary>
/// For running a DreamDaemon instance
/// </summary>
interface IDreamDaemonExecutor
{
/// <summary>
/// Run a dream daemon instance
/// </summary>
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/></param>
/// <param name="onSuccessfulStartup">The <see cref="TaskCompletionSource{TResult}"/> that is completed when dream daemon starts without crashing</param>
/// <param name="dreamDaemonPath">The path to the dreamdaemon executable</param>
/// <param name="dmbPath">The path to the .dmb to run, the working directory will be derived from this</param>
/// <param name="accessToken">The access token to be used for communication</param>
/// <param name="usePrimaryPort">If the server should open on <see cref="DreamDaemonLaunchParameters.PrimaryPort"/> or <see cref="DreamDaemonLaunchParameters.SecondaryPort"/> of <paramref name="launchParameters"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> representing the lifetime of the process and resulting in the exit code</returns>
Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken);
}
}
@@ -7,6 +7,8 @@ namespace Tgstation.Server.Host.Components
/// </summary>
sealed class ServerControlEventArgs : EventArgs
{
public bool PrimeReboot { get; set; }
/// <summary>
/// If <see langword="true"/> the event will result in a new run of the world
/// </summary>