diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 21123e7288..59c11072a7 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Service UseShellExecute = true, Verb = "runas", Arguments = String.Join(" ", argList), - FileName = Assembly.GetExecutingAssembly().Location, + FileName = exe, WorkingDirectory = Environment.CurrentDirectory, }; using (Process.Start(startInfo)) diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 3d53c14e87..6c08cf40ac 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -1,14 +1,8 @@ -using Newtonsoft.Json; -using System; +using System; using System.Diagnostics; using System.Globalization; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Components.Models; -using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { @@ -35,34 +29,11 @@ namespace Tgstation.Server.Host.Components } } - /// - /// The for the - /// - readonly IInstanceShutdownHandler instanceShutdownMethod; - /// - /// The for the - /// - readonly IIOManager ioManager; - /// - /// The for the - /// - readonly IApplication application; - - /// - /// Construct a - /// - /// The value of - /// The value of - /// The value of - public DreamDaemonExecutor(IInstanceShutdownHandler instanceShutdownMethod, IIOManager ioManager, IApplication application) - { - this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); - this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); - this.application = application ?? throw new ArgumentNullException(nameof(application)); - } + /// + public IDreamDaemonSession AttachToDreamDaemon(int processId) => new DreamDaemonSession(Process.GetProcessById(processId)); /// - public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken) + public IDreamDaemonSession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory) { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); @@ -70,69 +41,32 @@ namespace Tgstation.Server.Host.Components throw new ArgumentNullException(nameof(dreamDaemonPath)); if (dmbProvider == null) throw new ArgumentNullException(nameof(dmbProvider)); - if (interopInfo == null) - throw new ArgumentNullException(nameof(interopInfo)); + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); - if (interopInfo.NextPort != launchParameters.PrimaryPort && interopInfo.NextPort != launchParameters.SecondaryPort) - throw new ArgumentOutOfRangeException(nameof(interopInfo), interopInfo, "interopInfo.NextPort must match primary or secondary ports!"); + var proc = new Process(); - var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; - - //serialize the interop info to the json - var jsonPath = String.Concat(Guid.NewGuid(), ".tgs.json"); - - var json = JsonConvert.SerializeObject(interopInfo); - - - using (var proc = new Process()) + try { proc.StartInfo.FileName = dreamDaemonPath; - proc.StartInfo.WorkingDirectory = isPrimary ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory; - await ioManager.WriteAllBytes(ioManager.ConcatPath(proc.StartInfo.WorkingDirectory, jsonPath), Encoding.UTF8.GetBytes(json), cancellationToken).ConfigureAwait(false); + proc.StartInfo.WorkingDirectory = useSecondaryDirectory ? dmbProvider.SecondaryDirectory : dmbProvider.PrimaryDirectory; - proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", + proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}\"", dmbProvider.DmbName, - isPrimary && !asDefaultOtherServer ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, + useSecondaryPort ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel.Value), - DreamDaemonParameters.HostVersion, Application.Version, - DreamDaemonParameters.InfoJsonPath, jsonPath); + parameters); - proc.EnableRaisingEvents = true; - var tcs = new TaskCompletionSource(); - proc.Exited += (a, b) => tcs.SetResult(null); + proc.Start(); - try - { - proc.Start(); - - await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - - onSuccessfulStartup?.SetResult(null); - - try - { - using (cancellationToken.Register(() => tcs.SetCanceled())) - await tcs.Task.ConfigureAwait(false); - } - finally - { - if (!alwaysKill && !await instanceShutdownMethod.PreserveActiveExecutablesIfNecessary(launchParameters, interopInfo.AccessToken, proc.Id, isPrimary).ConfigureAwait(false)) - { - proc.Kill(); - proc.WaitForExit(); - } - } - - return proc.ExitCode; - } - catch (Exception e) - { - onSuccessfulStartup?.SetException(e); - throw; - } + return new DreamDaemonSession(proc); + } + catch + { + proc.Dispose(); + throw; } } - } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonSession.cs b/src/Tgstation.Server.Host/Components/DreamDaemonSession.cs new file mode 100644 index 0000000000..7420c43ce7 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonSession.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class DreamDaemonSession : IDreamDaemonSession + { + /// + public int ProcessId => process.Id; + + /// + public Task SuccessfulStartup { get; } + + /// + public Task Lifetime => lifetimeTask.Task; + + /// + /// The actual + /// + readonly Process process; + /// + /// The backing for + /// + readonly TaskCompletionSource lifetimeTask; + + /// + /// Construct a + /// + /// The value of + public DreamDaemonSession(Process process) + { + this.process = process ?? throw new ArgumentNullException(nameof(process)); + + SuccessfulStartup = Task.Factory.StartNew(() => process.WaitForInputIdle(), default, TaskCreationOptions.LongRunning, TaskScheduler.Current); + lifetimeTask = new TaskCompletionSource(); + process.EnableRaisingEvents = true; + process.Exited += (a, b) => lifetimeTask.SetResult(process.ExitCode); + } + + /// + public void Dispose() => process.Dispose(); + + /// + public void Terminate() + { + try + { + process.Kill(); + process.WaitForExit(); + } + catch (InvalidOperationException) { } + } + } +} diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index 77980fbe04..8e2e44555a 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -1,12 +1,9 @@ -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Components.Models; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components { /// - /// For running a DreamDaemon instance + /// For creating s /// interface IDreamDaemonExecutor { @@ -17,11 +14,17 @@ namespace Tgstation.Server.Host.Components /// The that is completed when dream daemon starts without crashing /// The path to the dreamdaemon executable /// The for the .dmb to run - /// The for the run - /// If the resulting process should never be left alive - /// If the ports will be swapped for the launch - /// 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, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken); + /// The value of the -params command line option + /// If the field of should be used + /// If the field of should be used + /// A new + IDreamDaemonSession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory); + + /// + /// Attach to a running instance of DreamDaemon + /// + /// The + /// A new + IDreamDaemonSession AttachToDreamDaemon(int processId); } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs new file mode 100644 index 0000000000..0adbc8b8d2 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents a dream daemon process + /// + interface IDreamDaemonSession : IDisposable + { + /// + /// The + /// + int ProcessId { get; } + + /// + /// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb + /// + Task SuccessfulStartup { get; } + + /// + /// A representing the lifetime of the and resulting in the + /// + Task Lifetime { get; } + + /// + /// Terminates the running process + /// + void Terminate(); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 0634175e06..aec3cf3769 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -59,7 +59,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the operation /// A resulting in the of the operation [HttpPost] - public virtual Task UpdateAsync([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// /// Attempt to delete a