diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
index d2a7329345..588f2e9fbe 100644
--- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
@@ -1,6 +1,5 @@
using Microsoft.Extensions.Logging;
using System;
-using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;
@@ -47,6 +46,11 @@ namespace Tgstation.Server.Host.Components.Byond
///
readonly IIOManager ioManager;
+ ///
+ /// The for the
+ ///
+ readonly IProcessExecutor processExecutor;
+
///
/// The for the
///
@@ -66,10 +70,12 @@ namespace Tgstation.Server.Host.Components.Byond
/// Construct a
///
/// The value of
+ /// The value of
/// The value of
- public WindowsByondInstaller(IIOManager ioManager, ILogger logger)
+ public WindowsByondInstaller(IIOManager ioManager, IProcessExecutor processExecutor, ILogger logger)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
+ this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
semaphore = new SemaphoreSlim(1);
@@ -115,43 +121,22 @@ namespace Tgstation.Server.Host.Components.Byond
//after this version lummox made DD depend of directx lol
if (version.Major >= 512 && version.Minor >= 1427 && !installedDirectX)
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
- {
if (!installedDirectX)
+ {
//always install it, it's pretty fast and will do better redundancy checking than us
- using (var p = new Process())
+ var rbdx = ioManager.ConcatPath(path, ByondDXDir);
+ using (var p = processExecutor.LaunchProcess(ioManager.ConcatPath(rbdx, "DXSETUP.exe"), rbdx, "/silent"))
{
- p.StartInfo.Arguments = "/silent";
- var rbdx = ioManager.ConcatPath(path, ByondDXDir);
- p.StartInfo.FileName = rbdx + "/DXSETUP.exe";
- p.StartInfo.UseShellExecute = false;
- p.StartInfo.WorkingDirectory = rbdx;
- p.EnableRaisingEvents = true;
- var tcs = new TaskCompletionSource();
- p.Exited += (a, b) => tcs.TrySetResult(null);
- try
- {
- p.Start();
- using (cancellationToken.Register(() => tcs.TrySetCanceled()))
- await tcs.Task.ConfigureAwait(false);
- }
- finally
- {
- try
- {
- if (!p.HasExited)
- {
- p.Kill();
- p.WaitForExit();
- }
- }
- catch (InvalidOperationException) { }
- }
+ int exitCode;
+ using (cancellationToken.Register(() => p.Terminate()))
+ exitCode = await p.Lifetime.ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
- if (p.ExitCode != 0)
- throw new Exception("Failed to install included DirectX! Exit code: " + p.ExitCode);
+ if (exitCode != 0)
+ throw new Exception(String.Format(CultureInfo.InvariantCulture, "Failed to install included DirectX! Exit code: {0}", exitCode));
installedDirectX = true;
}
- }
+ }
await setNoPromptTrustedModeTask.ConfigureAwait(false);
}
diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs
index 622a3e9780..0d10d11185 100644
--- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs
@@ -1,7 +1,6 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -74,6 +73,10 @@ namespace Tgstation.Server.Host.Components.Compiler
///
readonly IChat chat;
///
+ /// The for
+ ///
+ readonly IProcessExecutor processExecutor;
+ ///
/// The for
///
readonly ILogger logger;
@@ -89,8 +92,9 @@ namespace Tgstation.Server.Host.Components.Compiler
/// The value of
/// The value of
/// The value of
+ /// The value of
/// The value of
- public DreamMaker(IByondManager byond, IIOManager ioManager, StaticFiles.IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, IChat chat, ILogger logger)
+ public DreamMaker(IByondManager byond, IIOManager ioManager, StaticFiles.IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, IChat chat, IProcessExecutor processExecutor, ILogger logger)
{
this.byond = byond;
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
@@ -100,6 +104,7 @@ namespace Tgstation.Server.Host.Components.Compiler
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
+ this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -158,50 +163,14 @@ namespace Tgstation.Server.Host.Components.Compiler
/// A representing the running operation
async Task RunDreamMaker(string dreamMakerPath, Models.CompileJob job, CancellationToken cancellationToken)
{
- using (var dm = new Process())
+ using (var dm = processExecutor.LaunchProcess(dreamMakerPath, ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)), String.Format(CultureInfo.InvariantCulture, "-clean {0}.{1}", job.DmeName, DmeExtension), true, true))
{
- dm.StartInfo.FileName = dreamMakerPath;
- dm.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "-clean {0}.{1}", job.DmeName, DmeExtension);
- dm.StartInfo.WorkingDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName));
- dm.StartInfo.RedirectStandardOutput = true;
- dm.StartInfo.RedirectStandardError = true;
- dm.StartInfo.UseShellExecute = false;
- var outputList = new StringBuilder();
- var eventHandler = new DataReceivedEventHandler(
- delegate (object sender, DataReceivedEventArgs e)
- {
- outputList.Append(Environment.NewLine);
- outputList.Append(e.Data);
- }
- );
- dm.OutputDataReceived += eventHandler;
- dm.ErrorDataReceived += eventHandler;
+ using (cancellationToken.Register(() => dm.Terminate()))
+ job.ExitCode = await dm.Lifetime.ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
- dm.EnableRaisingEvents = true;
- var dmTcs = new TaskCompletionSource();
- dm.Exited += (a, b) => dmTcs.TrySetResult(null);
-
- logger.LogTrace("Running DreamMaker...");
- dm.Start();
- dm.BeginOutputReadLine();
- dm.BeginErrorReadLine();
- try
- {
- using (cancellationToken.Register(() => dmTcs.TrySetCanceled()))
- await dmTcs.Task.ConfigureAwait(false);
- }
- finally
- {
- if (!dm.HasExited)
- {
- dm.Kill();
- dm.WaitForExit();
- }
- }
-
- job.ExitCode = dm.ExitCode;
logger.LogDebug("DreamMaker exit code: {0}", job.ExitCode);
- job.Output = outputList.ToString();
+ job.Output = dm.GetCombinedOutput();
logger.LogTrace("DreamMaker output: {0}", job.Output);
}
}
diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs
index 7befe71b2c..89125b337b 100644
--- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs
@@ -78,9 +78,9 @@ namespace Tgstation.Server.Host.Components
readonly IProviderFactory providerFactory;
///
- /// The for the
+ /// The for the
///
- readonly IScriptExecutor scriptExecutor;
+ readonly IProcessExecutor processExecutor;
///
/// Construct an
@@ -97,8 +97,8 @@ namespace Tgstation.Server.Host.Components
/// The value of
/// The value of
/// The value of
- /// The value of
- public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor)
+ /// The value of
+ public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
@@ -112,7 +112,7 @@ namespace Tgstation.Server.Host.Components
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller));
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
- this.scriptExecutor = scriptExecutor ?? throw new ArgumentNullException(nameof(scriptExecutor));
+ this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
}
///
@@ -127,7 +127,7 @@ namespace Tgstation.Server.Host.Components
var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game");
var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration");
- var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, scriptExecutor, loggerFactory.CreateLogger());
+ var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, loggerFactory.CreateLogger());
var eventConsumer = new EventConsumer(configuration);
var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger(), metadata.CloneMetadata());
@@ -152,7 +152,7 @@ namespace Tgstation.Server.Host.Components
commandFactory.SetWatchdog(watchdog);
try
{
- var dreamMaker = new DreamMaker(byond, gameIoManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, chat, loggerFactory.CreateLogger());
+ var dreamMaker = new DreamMaker(byond, gameIoManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, chat, processExecutor, loggerFactory.CreateLogger());
return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory, loggerFactory.CreateLogger());
}
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
index 26076d6d09..fce5cf4a23 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
@@ -50,9 +50,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles
readonly ISymlinkFactory symlinkFactory;
///
- /// The for
+ /// The for
///
- readonly IScriptExecutor scriptExecutor;
+ readonly IProcessExecutor processExecutor;
///
/// The for
@@ -70,14 +70,14 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// The value of
/// The value of
/// The value of
- /// The value of
+ /// The value of
/// The value of
- public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IScriptExecutor scriptExecutor, ILogger logger)
+ public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IProcessExecutor processExecutor, ILogger logger)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager));
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
- this.scriptExecutor = scriptExecutor ?? throw new ArgumentNullException(nameof(scriptExecutor));
+ this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
semaphore = new SemaphoreSlim(1);
@@ -314,8 +314,14 @@ namespace Tgstation.Server.Host.Components.StaticFiles
var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory);
foreach (var I in files.Select(x => ioManager.GetFileName(x)).Where(x => x.StartsWith(scriptName, StringComparison.Ordinal)))
- if ((await scriptExecutor.ExecuteScript(ioManager.ConcatPath(resolvedScriptsDir, I), parameters, cancellationToken).ConfigureAwait(false)) != 0)
- return false;
+ using (var script = processExecutor.LaunchProcess(ioManager.ConcatPath(resolvedScriptsDir, I), String.Concat(parameters)))
+ using (cancellationToken.Register(() => script.Terminate()))
+ {
+ var exitCode = await script.Lifetime.ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+ if (exitCode != 0)
+ return false;
+ }
}
return true;
}
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IScriptExecutor.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IScriptExecutor.cs
deleted file mode 100644
index a9e26ff72f..0000000000
--- a/src/Tgstation.Server.Host/Components/StaticFiles/IScriptExecutor.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Tgstation.Server.Host.Components.StaticFiles
-{
- ///
- /// For executing system shell scripts
- ///
- interface IScriptExecutor
- {
- ///
- /// Execute a shell script and get the result
- ///
- /// The absolute path to the script
- /// Command line parameters for the script
- /// The for the operation
- /// A resulting in the or if an error occurred
- Task ExecuteScript(string scriptPath, IEnumerable parameters, CancellationToken cancellationToken);
- }
-}
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs b/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs
deleted file mode 100644
index ff2a90c625..0000000000
--- a/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Threading;
-using System.Threading.Tasks;
-using Tgstation.Server.Host.IO;
-
-namespace Tgstation.Server.Host.Components.StaticFiles
-{
- ///
- sealed class ScriptExecutor : IScriptExecutor
- {
- ///
- /// The for the
- ///
- readonly IIOManager ioManager;
-
- ///
- /// The for the
- ///
- readonly ILogger logger;
-
- ///
- /// Construct a
- ///
- /// The value of
- /// The value of
- public ScriptExecutor(IIOManager ioManager, ILogger logger)
- {
- this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
- this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
- }
-
- ///
- public async Task ExecuteScript(string scriptPath, IEnumerable parameters, CancellationToken cancellationToken)
- {
- var joinedParams = String.Join(" ", parameters);
- logger.LogInformation("Running script {0} {1}", scriptPath, joinedParams);
- try
- {
- using (var process = new Process())
- {
- process.StartInfo.FileName = scriptPath;
- process.StartInfo.WorkingDirectory = ioManager.GetDirectoryName(scriptPath);
- process.StartInfo.Arguments = joinedParams;
- process.EnableRaisingEvents = true;
-
- var tcs = new TaskCompletionSource();
- process.Exited += (a, b) => tcs.TrySetResult(null);
- try
- {
- process.Start();
- using (cancellationToken.Register(() => tcs.TrySetCanceled()))
- await tcs.Task.ConfigureAwait(false);
- }
- catch (InvalidOperationException)
- {
- try
- {
- process.Kill();
- process.WaitForExit();
- }
- catch (InvalidOperationException) { }
- }
-
- return process.ExitCode;
- }
- }
- catch (Exception e)
- {
- logger.LogWarning("Error running shell script {0}! Exception: {1}", scriptPath, e);
- return null;
- }
- }
- }
-}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs
index 85ec573591..130e1d0b96 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs
@@ -1,16 +1,21 @@
using Microsoft.Extensions.Logging;
using System;
-using System.Diagnostics;
using System.Globalization;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Byond;
+using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Components.Watchdog
{
///
sealed class Executor : IExecutor
{
+ ///
+ /// The for the
+ ///
+ readonly IProcessExecutor processExecutor;
+
///
/// The for the
///
@@ -39,14 +44,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
/// Construct an
///
+ /// The value of
/// The value of
- public Executor(ILogger logger)
+ public Executor(IProcessExecutor processExecutor, ILogger logger)
{
+ this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
///
- public ISession AttachToDreamDaemon(int processId, IByondExecutableLock byondLock) => new Session(Process.GetProcessById(processId), byondLock);
+ public ISession AttachToDreamDaemon(int processId, IByondExecutableLock byondLock) => new Session(processExecutor.GetProcess(processId), byondLock);
///
public ISession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, IByondExecutableLock byondLock, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory)
@@ -60,30 +67,21 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (parameters == null)
throw new ArgumentNullException(nameof(parameters));
- var proc = new Process();
- try
- {
- proc.StartInfo.FileName = byondLock.DreamDaemonPath;
- proc.StartInfo.WorkingDirectory = useSecondaryDirectory ? dmbProvider.SecondaryDirectory : dmbProvider.PrimaryDirectory;
+ var fileName = byondLock.DreamDaemonPath;
+ var workingDirectory = useSecondaryDirectory ? dmbProvider.SecondaryDirectory : dmbProvider.PrimaryDirectory;
- proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}\"",
- dmbProvider.DmbName,
- useSecondaryPort ? launchParameters.SecondaryPort : launchParameters.PrimaryPort,
- launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty,
- SecurityWord(launchParameters.SecurityLevel.Value),
- parameters);
-
- logger.LogTrace("Running DreamDaemon in {0}: {1} {2}", proc.StartInfo.WorkingDirectory, proc.StartInfo.FileName, proc.StartInfo.Arguments);
+ var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}\"",
+ dmbProvider.DmbName,
+ useSecondaryPort ? launchParameters.SecondaryPort : launchParameters.PrimaryPort,
+ launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty,
+ SecurityWord(launchParameters.SecurityLevel.Value),
+ parameters);
- proc.Start();
+ logger.LogTrace("Running DreamDaemon in {0}: {1} {2}", workingDirectory, fileName, arguments);
- return new Session(proc, byondLock);
- }
- catch
- {
- proc.Dispose();
- throw;
- }
+ var proc = processExecutor.LaunchProcess(fileName, workingDirectory, arguments);
+
+ return new Session(proc, byondLock);
}
}
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs
index 5fb7fb35dd..1728cc2ad3 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs
@@ -1,18 +1,11 @@
-namespace Tgstation.Server.Host.Components.Watchdog
+using Tgstation.Server.Host.Core;
+
+namespace Tgstation.Server.Host.Components.Watchdog
{
///
/// Represents a dream daemon process
///
- interface ISession : ISessionBase
+ interface ISession : ISessionBase, IProcess
{
- ///
- /// The
- ///
- int ProcessId { get; }
-
- ///
- /// Terminates the running process
- ///
- void Terminate();
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionBase.cs
index 672fbee8cf..bd1d9ee0dc 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionBase.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionBase.cs
@@ -1,18 +1,13 @@
-using System;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Components.Watchdog
{
- interface ISessionBase : IDisposable
+ interface ISessionBase : IProcessBase
{
///
/// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb or when it crashes
///
Task LaunchResult { get; }
-
- ///
- /// A representing the lifetime of the and resulting in the
- ///
- Task Lifetime { get; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs
index a26f14e130..01a1a7bdd9 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs
@@ -1,7 +1,7 @@
using System;
-using System.Diagnostics;
using System.Threading.Tasks;
using Tgstation.Server.Host.Components.Byond;
+using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Components.Watchdog
{
@@ -9,78 +9,67 @@ namespace Tgstation.Server.Host.Components.Watchdog
sealed class Session : ISession
{
///
- public int ProcessId => process.Id;
+ public int Id => process.Id;
+
+ ///
+ public Task Startup => process.Startup;
///
public Task LaunchResult { get; }
///
- public Task Lifetime => lifetimeTask.Task;
+ public Task Lifetime => process.Lifetime;
///
- /// The actual
+ /// The actual
///
- readonly Process process;
+ readonly IProcess process;
///
/// The for the
///
readonly IByondExecutableLock byondLock;
- ///
- /// The backing for
- ///
- readonly TaskCompletionSource lifetimeTask;
-
///
/// Construct a
///
/// The value of
/// The value of
- public Session(Process process, IByondExecutableLock byondLock)
+ public Session(IProcess process, IByondExecutableLock byondLock)
{
this.process = process ?? throw new ArgumentNullException(nameof(process));
this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock));
- LaunchResult = Task.Factory.StartNew(() =>
+ async Task GetLaunchResult()
{
var startTime = DateTimeOffset.Now;
- try
- {
- process.WaitForInputIdle();
- }
- catch (InvalidOperationException) { }
+ await process.Startup.ConfigureAwait(false);
var result = new LaunchResult
{
- ExitCode = process.HasExited ? (int?)process.ExitCode : null,
+ ExitCode = process.Lifetime.IsCompleted ? (int?)await process.Lifetime.ConfigureAwait(false) : null,
StartupTime = DateTimeOffset.Now - startTime
};
return result;
- }, default, TaskCreationOptions.LongRunning, TaskScheduler.Current);
- lifetimeTask = new TaskCompletionSource();
- try
- {
- process.EnableRaisingEvents = true;
- process.Exited += (a, b) => lifetimeTask.TrySetResult(process.ExitCode);
- }
- catch (InvalidOperationException)
- {
- //dead proccess
- lifetimeTask.TrySetResult(process.ExitCode);
- }
+ };
+ LaunchResult = GetLaunchResult();
}
///
- public void Dispose() => process.Dispose();
-
- ///
- public void Terminate()
+ public void Dispose()
{
- try
- {
- process.Kill();
- process.WaitForExit();
- }
- catch (InvalidOperationException) { }
+ process.Dispose();
+ byondLock.Dispose();
}
+
+ ///
+ public void Terminate() => process.Terminate();
+
+ ///
+ public string GetErrorOutput() => process.GetErrorOutput();
+
+ ///
+ public string GetStandardOutput() => process.GetStandardOutput();
+
+ ///
+ public string GetCombinedOutput() => process.GetCombinedOutput();
}
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs
index 57a3d47a4e..ea25ecc487 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs
@@ -159,7 +159,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
Dmb = dmbProvider,
IsPrimary = primaryDirectory,
Port = portToUse.Value,
- ProcessId = session.ProcessId,
+ ProcessId = session.Id,
ChatChannelsJson = interopInfo.ChatChannelsJson,
ChatCommandsJson = interopInfo.ChatCommandsJson,
ServerCommandsJson = interopInfo.ServerCommandsJson,
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index a3cd319530..03eeab5463 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -198,8 +198,8 @@ namespace Tgstation.Server.Host.Core
services.AddSingleton();
}
+ services.AddSingleton();
services.AddSingleton();
- services.AddSingleton();
services.AddSingleton();
services.AddSingleton(new ByondTopicSender
{
diff --git a/src/Tgstation.Server.Host/Core/IProcess.cs b/src/Tgstation.Server.Host/Core/IProcess.cs
new file mode 100644
index 0000000000..740b09951f
--- /dev/null
+++ b/src/Tgstation.Server.Host/Core/IProcess.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Core
+{
+ ///
+ /// Abstraction over a
+ ///
+ interface IProcess : IProcessBase
+ {
+ ///
+ /// The ' ID
+ ///
+ int Id { get; }
+
+ ///
+ /// The representing the time until the becomes "idle"
+ ///
+ Task Startup { get; }
+
+ ///
+ /// Get the stderr output of the
+ ///
+ /// The stderr output of the
+ string GetErrorOutput();
+
+ ///
+ /// Get the stdout output of the
+ ///
+ /// The stdout output of the
+ string GetStandardOutput();
+
+ ///
+ /// Get the stderr and stdout output of the
+ ///
+ /// The stderr and stdout output of the
+ string GetCombinedOutput();
+
+ ///
+ /// Terminates the process
+ ///
+ void Terminate();
+ }
+}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Core/IProcessBase.cs b/src/Tgstation.Server.Host/Core/IProcessBase.cs
new file mode 100644
index 0000000000..b96b93b2ae
--- /dev/null
+++ b/src/Tgstation.Server.Host/Core/IProcessBase.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Core
+{
+ ///
+ /// Represents process lifetime
+ ///
+ interface IProcessBase : IDisposable
+ {
+ ///
+ /// The resulting in the exit code of the process
+ ///
+ Task Lifetime { get; }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/IProcessExecutor.cs b/src/Tgstation.Server.Host/Core/IProcessExecutor.cs
new file mode 100644
index 0000000000..20c0325a45
--- /dev/null
+++ b/src/Tgstation.Server.Host/Core/IProcessExecutor.cs
@@ -0,0 +1,26 @@
+namespace Tgstation.Server.Host.Core
+{
+ ///
+ /// For launching '
+ ///
+ interface IProcessExecutor
+ {
+ ///
+ /// Launch a
+ ///
+ /// The full path to the executable file
+ /// The arguments for the
+ /// The working directory for the
+ /// If standard output should be read
+ /// If standard error should be read
+ /// A new
+ IProcess LaunchProcess(string fileName, string workingDirectory, string arguments = null, bool readOutput = false, bool readError = false);
+
+ ///
+ /// Get a by
+ ///
+ /// The
+ /// The represented by
+ IProcess GetProcess(int id);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/Core/Process.cs
new file mode 100644
index 0000000000..edca9f0516
--- /dev/null
+++ b/src/Tgstation.Server.Host/Core/Process.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Core
+{
+ ///
+ sealed class Process : IProcess
+ {
+
+ public int Id { get; }
+
+ public Task Startup { get; }
+
+ public Task Lifetime { get; }
+
+ readonly System.Diagnostics.Process handle;
+
+ readonly StringBuilder outputStringBuilder;
+ readonly StringBuilder errorStringBuilder;
+ readonly StringBuilder combinedStringBuilder;
+
+ public Process(System.Diagnostics.Process handle, Task lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder)
+ {
+ this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
+ Lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime));
+
+ this.outputStringBuilder = outputStringBuilder;
+ this.errorStringBuilder = errorStringBuilder;
+ this.combinedStringBuilder = combinedStringBuilder;
+
+ Id = handle.Id;
+ Startup = Task.Factory.StartNew(() =>
+ {
+ try
+ {
+ handle.WaitForInputIdle();
+ }
+ catch (InvalidOperationException) { }
+ }, default, TaskCreationOptions.LongRunning, TaskScheduler.Current);
+ }
+
+ public void Dispose() => handle.Dispose();
+
+ public string GetCombinedOutput()
+ {
+ if (combinedStringBuilder == null)
+ throw new InvalidOperationException("Output/Error reading was not enabled!");
+ return combinedStringBuilder.ToString();
+ }
+
+ public string GetErrorOutput()
+ {
+ if (errorStringBuilder == null)
+ throw new InvalidOperationException("Error reading was not enabled!");
+ return errorStringBuilder.ToString();
+ }
+
+ public string GetStandardOutput()
+ {
+ if (outputStringBuilder == null)
+ throw new InvalidOperationException("Output reading was not enabled!");
+ return errorStringBuilder.ToString();
+ }
+
+ public void Terminate()
+ {
+ try
+ {
+ handle.Kill();
+ handle.WaitForExit();
+ }
+ catch (InvalidOperationException) { }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs
new file mode 100644
index 0000000000..d42474b5c4
--- /dev/null
+++ b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Diagnostics;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Core
+{
+ ///
+ sealed class ProcessExecutor : IProcessExecutor
+ {
+ ///
+ /// Create a resulting in the exit code of a given
+ ///
+ /// The to attach the for
+ /// A new resulting in the exit code of
+ static Task AttachExitHandler(System.Diagnostics.Process handle)
+ {
+ handle.EnableRaisingEvents = true;
+ var tcs = new TaskCompletionSource();
+ handle.Exited += (a, b) => tcs.SetResult(handle.ExitCode);
+ return tcs.Task;
+ }
+
+ ///
+ public IProcess GetProcess(int id)
+ {
+ var handle = System.Diagnostics.Process.GetProcessById(id);
+ try
+ {
+ return new Process(handle, AttachExitHandler(handle), null, null, null);
+ }
+ catch
+ {
+ handle.Dispose();
+ throw;
+ }
+ }
+
+ ///
+ public IProcess LaunchProcess(string fileName, string workingDirectory, string arguments, bool readOutput, bool readError)
+ {
+ var handle = new System.Diagnostics.Process();
+ try
+ {
+ handle.StartInfo.FileName = fileName;
+ handle.StartInfo.Arguments = arguments;
+ handle.StartInfo.WorkingDirectory = workingDirectory;
+
+ StringBuilder outputStringBuilder = null, errorStringBuilder = null, combinedStringBuilder = null;
+ if (readOutput || readError)
+ {
+ handle.StartInfo.UseShellExecute = false;
+ combinedStringBuilder = new StringBuilder();
+ if (readOutput)
+ {
+ outputStringBuilder = new StringBuilder();
+ handle.StartInfo.RedirectStandardOutput = true;
+ var eventHandler = new DataReceivedEventHandler(
+ delegate (object sender, DataReceivedEventArgs e)
+ {
+ combinedStringBuilder.Append(Environment.NewLine);
+ combinedStringBuilder.Append(e.Data);
+ outputStringBuilder.Append(Environment.NewLine);
+ outputStringBuilder.Append(e.Data);
+ }
+ );
+ handle.OutputDataReceived += eventHandler;
+ }
+ if (readError)
+ {
+ errorStringBuilder = new StringBuilder();
+ handle.StartInfo.RedirectStandardError = true;
+ var eventHandler = new DataReceivedEventHandler(
+ delegate (object sender, DataReceivedEventArgs e)
+ {
+ combinedStringBuilder.Append(Environment.NewLine);
+ combinedStringBuilder.Append(e.Data);
+ errorStringBuilder.Append(Environment.NewLine);
+ errorStringBuilder.Append(e.Data);
+ }
+ );
+ handle.ErrorDataReceived += eventHandler;
+ }
+ }
+
+ var lifetimeTask = AttachExitHandler(handle);
+
+ handle.Start();
+ try
+ {
+ if (readOutput)
+ handle.BeginOutputReadLine();
+ }
+ catch (InvalidOperationException) { }
+ try
+ {
+ if (readError)
+ handle.BeginErrorReadLine();
+ }
+ catch (InvalidOperationException) { }
+
+ return new Process(handle, lifetimeTask, outputStringBuilder, errorStringBuilder, combinedStringBuilder);
+ }
+ catch
+ {
+ handle.Dispose();
+ throw;
+ }
+ }
+ }
+}
\ No newline at end of file