mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-30 16:39:21 +01:00
Let's rebuild from the ground up.
Overhauls DreamDaemonExecutor and adds DreamDaemonSession
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceShutdownHandler"/> for the <see cref="DreamDaemonExecutor"/>
|
||||
/// </summary>
|
||||
readonly IInstanceShutdownHandler instanceShutdownMethod;
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="DreamDaemonExecutor"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
/// <summary>
|
||||
/// The <see cref="IApplication"/> for the <see cref="DreamDaemonExecutor"/>
|
||||
/// </summary>
|
||||
readonly IApplication application;
|
||||
|
||||
/// <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>
|
||||
/// <param name="application">The value of <see cref="application"/></param>
|
||||
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));
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public IDreamDaemonSession AttachToDreamDaemon(int processId) => new DreamDaemonSession(Process.GetProcessById(processId));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> 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<object>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DreamDaemonSession : IDreamDaemonSession
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ProcessId => process.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SuccessfulStartup { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> Lifetime => lifetimeTask.Task;
|
||||
|
||||
/// <summary>
|
||||
/// The actual <see cref="Process"/>
|
||||
/// </summary>
|
||||
readonly Process process;
|
||||
/// <summary>
|
||||
/// The backing <see cref="TaskCompletionSource{TResult}"/> for <see cref="Lifetime"/>
|
||||
/// </summary>
|
||||
readonly TaskCompletionSource<int> lifetimeTask;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DreamDaemonSession"/>
|
||||
/// </summary>
|
||||
/// <param name="process">The value of <see cref="process"/></param>
|
||||
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<int>();
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Exited += (a, b) => lifetimeTask.SetResult(process.ExitCode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => process.Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Terminate()
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Kill();
|
||||
process.WaitForExit();
|
||||
}
|
||||
catch (InvalidOperationException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// For running a DreamDaemon instance
|
||||
/// For creating <see cref="IDreamDaemonSession"/>s
|
||||
/// </summary>
|
||||
interface IDreamDaemonExecutor
|
||||
{
|
||||
@@ -17,11 +14,17 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <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="dmbProvider">The <see cref="IDmbProvider"/> for the .dmb to run</param>
|
||||
/// <param name="interopInfo">The <see cref="InteropInfo"/> for the run</param>
|
||||
/// <param name="alwaysKill">If the resulting process should never be left alive</param>
|
||||
/// <param name="asDefaultOtherServer">If <see langword="true"/> the ports will be swapped for the launch</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, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken);
|
||||
/// <param name="parameters">The value of the -params command line option</param>
|
||||
/// <param name="useSecondaryPort">If the <see cref="DreamDaemonLaunchParameters.SecondaryPort"/> field of <paramref name="launchParameters"/> should be used</param>
|
||||
/// <param name="useSecondaryDirectory">If the <see cref="IDmbProvider.SecondaryDirectory"/> field of <paramref name="dmbProvider"/> should be used</param>
|
||||
/// <returns>A new <see cref="IDreamDaemonSession"/></returns>
|
||||
IDreamDaemonSession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory);
|
||||
|
||||
/// <summary>
|
||||
/// Attach to a running instance of DreamDaemon
|
||||
/// </summary>
|
||||
/// <param name="processId">The <see cref="IDreamDaemonSession.ProcessId"/></param>
|
||||
/// <returns>A new <see cref="IDreamDaemonSession"/></returns>
|
||||
IDreamDaemonSession AttachToDreamDaemon(int processId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a dream daemon process
|
||||
/// </summary>
|
||||
interface IDreamDaemonSession : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="System.Diagnostics.Process.Id"/>
|
||||
/// </summary>
|
||||
int ProcessId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Task"/> that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb
|
||||
/// </summary>
|
||||
Task SuccessfulStartup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Task"/> representing the lifetime of the <see cref="System.Diagnostics.Process"/> and resulting in the <see cref="System.Diagnostics.Process.ExitCode"/>
|
||||
/// </summary>
|
||||
Task<int> Lifetime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Terminates the running process
|
||||
/// </summary>
|
||||
void Terminate();
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
[HttpPost]
|
||||
public virtual Task<IActionResult> UpdateAsync([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
|
||||
public virtual Task<IActionResult> Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to delete a <paramref name="model"/>
|
||||
|
||||
Reference in New Issue
Block a user