diff --git a/build/Version.props b/build/Version.props
index f75f23d239..c27dc59c94 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -11,7 +11,7 @@
15.0.0
7.0.0
5.7.0
- 1.4.0
+ 1.4.1
1.2.1
2.0.0
netstandard2.0
diff --git a/src/Tgstation.Server.Host.Console/PosixSignalChecker.cs b/src/Tgstation.Server.Host.Console/PosixSignalChecker.cs
index e037b5936f..b18ece3c03 100644
--- a/src/Tgstation.Server.Host.Console/PosixSignalChecker.cs
+++ b/src/Tgstation.Server.Host.Console/PosixSignalChecker.cs
@@ -31,7 +31,7 @@ namespace Tgstation.Server.Host.Console
}
///
- public async ValueTask CheckSignals(Func startChild, CancellationToken cancellationToken)
+ public async ValueTask CheckSignals(Func startChild, CancellationToken cancellationToken)
{
var (childPid, _) = startChild?.Invoke(null) ?? throw new ArgumentNullException(nameof(startChild));
var signalTcs = new TaskCompletionSource();
diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs
index e142de81be..c8383865af 100644
--- a/src/Tgstation.Server.Host.Console/Program.cs
+++ b/src/Tgstation.Server.Host.Console/Program.cs
@@ -38,7 +38,7 @@ namespace Tgstation.Server.Host.Console
/// A representing the running operation.
internal static async Task Main(string[] args)
{
- System.Console.Title = $"{Constants.CanonicalPackageName} Host Watchdog v{Assembly.GetExecutingAssembly().GetName().Version.Semver()}";
+ System.Console.Title = $"{Constants.CanonicalPackageName} Host Watchdog v{Assembly.GetExecutingAssembly().GetName().Version?.Semver()}";
var arguments = new List(args);
var trace = arguments.Remove("--trace-host-watchdog");
@@ -61,7 +61,7 @@ namespace Tgstation.Server.Host.Console
}
using var cts = new CancellationTokenSource();
- void AppDomainHandler(object a, EventArgs b) => cts.Cancel();
+ void AppDomainHandler(object? a, EventArgs b) => cts.Cancel();
AppDomain.CurrentDomain.ProcessExit += AppDomainHandler;
try
{
diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj
index 92902912bb..3cbf1c1a16 100644
--- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj
+++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj
@@ -5,6 +5,7 @@
Exe
$(TgsFrameworkVersion)
$(TgsCoreVersion)
+ enable
false
../../build/uac_elevation_manifest.xml
diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs
index 9e6deaeb63..c2f72b0901 100644
--- a/src/Tgstation.Server.Host.Service/Program.cs
+++ b/src/Tgstation.Server.Host.Service/Program.cs
@@ -77,7 +77,7 @@ namespace Tgstation.Server.Host.Service
/// The --passthroughargs or -p option.
///
[Option(ShortName = "p", Description = "Arguments passed to main host process")]
- public string PassthroughArgs { get; set; }
+ public string? PassthroughArgs { get; set; }
///
/// Entrypoint for the application.
@@ -157,7 +157,7 @@ namespace Tgstation.Server.Host.Service
/// Runs sc.exe to either uninstall a given or install the running .
///
/// The name of a service to uninstall.
- void InvokeSC(string serviceToUninstall)
+ void InvokeSC(string? serviceToUninstall)
{
using var installer = new ServiceInstaller();
if (serviceToUninstall != null)
@@ -172,6 +172,9 @@ namespace Tgstation.Server.Host.Service
Assembly.GetExecutingAssembly().Location);
var assemblyDirectory = Path.GetDirectoryName(fullPathToAssembly);
+ if (assemblyDirectory == null)
+ throw new InvalidOperationException($"Failed to resolve directory name of {assemblyDirectory}");
+
var assemblyNameWithoutExtension = Path.GetFileNameWithoutExtension(fullPathToAssembly);
var exePath = Path.Combine(assemblyDirectory, $"{assemblyNameWithoutExtension}.exe");
@@ -260,10 +263,8 @@ namespace Tgstation.Server.Host.Service
var stop = !Detach;
if (!stop)
{
- serviceController.ExecuteCommand(
- PipeCommands.GetServiceCommandId(
- PipeCommands.CommandDetachingShutdown)
- .Value);
+ var serviceControllerCommand = PipeCommands.GetServiceCommandId(PipeCommands.CommandDetachingShutdown);
+ serviceController.ExecuteCommand(serviceControllerCommand!.Value);
serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30));
if (serviceController.Status != ServiceControllerStatus.Stopped)
stop = true;
diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs
index 8f5554304f..56491187fb 100644
--- a/src/Tgstation.Server.Host.Service/ServerService.cs
+++ b/src/Tgstation.Server.Host.Service/ServerService.cs
@@ -1,20 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
-using System.IO.Pipes;
-using System.Linq;
using System.Runtime.Versioning;
using System.ServiceProcess;
-using System.Text;
using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.EventLog;
using Tgstation.Server.Common;
-using Tgstation.Server.Host.Common;
using Tgstation.Server.Host.Watchdog;
namespace Tgstation.Server.Host.Service
@@ -23,7 +17,7 @@ namespace Tgstation.Server.Host.Service
/// Represents a as a .
///
[SupportedOSPlatform("windows")]
- sealed class ServerService : ServiceBase, ISignalChecker
+ sealed class ServerService : ServiceBase
{
///
/// The canonical windows service name.
@@ -35,45 +29,22 @@ namespace Tgstation.Server.Host.Service
///
readonly IWatchdogFactory watchdogFactory;
+ ///
+ /// The used by the .
+ ///
+ readonly Lazy loggerFactory;
+
///
/// The of command line arguments the service was invoked with.
///
readonly string[] commandLineArguments;
///
- /// The minimum for the .
+ /// The active .
///
- readonly LogLevel minimumLogLevel;
-
- ///
- /// The used by the .
- ///
- ILoggerFactory loggerFactory;
-
- ///
- /// The for the .
- ///
- ILogger logger;
-
- ///
- /// The that represents the running .
- ///
- Task watchdogTask;
-
- ///
- /// The for the .
- ///
- CancellationTokenSource cancellationTokenSource;
-
- ///
- /// The for sending to the server process.
- ///
- AnonymousPipeServerStream commandPipeServer;
-
- ///
- /// The for receiving the .
- ///
- AnonymousPipeServerStream readyPipeServer;
+#pragma warning disable CA2213 // Disposable fields should be disposed
+ volatile ServiceLifetime? serviceLifetime;
+#pragma warning restore CA2213 // Disposable fields should be disposed
///
/// Initializes a new instance of the class.
@@ -85,21 +56,15 @@ namespace Tgstation.Server.Host.Service
{
this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory));
this.commandLineArguments = commandLineArguments ?? throw new ArgumentNullException(nameof(commandLineArguments));
- this.minimumLogLevel = minimumLogLevel;
- ServiceName = Name;
- }
- ///
- public async ValueTask CheckSignals(Func startChildAndGetPid, CancellationToken cancellationToken)
- {
- await using (commandPipeServer = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable))
- await using (readyPipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable))
+ ServiceName = Name;
+ loggerFactory = new Lazy(() => LoggerFactory.Create(builder => builder.AddEventLog(new EventLogSettings
{
- var (_, lifetimeTask) = startChildAndGetPid($"--Internal:CommandPipe={commandPipeServer.GetClientHandleAsString()} --Internal:ReadyPipe={readyPipeServer.GetClientHandleAsString()}");
- commandPipeServer.DisposeLocalCopyOfClientHandle();
- readyPipeServer.DisposeLocalCopyOfClientHandle();
- await lifetimeTask;
- }
+ LogName = EventLog.Log,
+ MachineName = EventLog.MachineName,
+ SourceName = EventLog.Source,
+ Filter = (message, logLevel) => logLevel >= minimumLogLevel,
+ })));
}
///
@@ -112,53 +77,21 @@ namespace Tgstation.Server.Host.Service
{
if (disposing)
{
- loggerFactory?.Dispose();
- cancellationTokenSource?.Dispose();
- commandPipeServer?.Dispose();
- readyPipeServer?.Dispose();
+ OnStop();
+
+ if (loggerFactory.IsValueCreated)
+ loggerFactory.Value.Dispose();
}
base.Dispose(disposing);
}
///
- protected override void OnCustomCommand(int command)
- {
- var commandsToCheck = PipeCommands.AllCommands;
- foreach (var stringCommand in commandsToCheck)
- {
- var commandId = PipeCommands.GetServiceCommandId(stringCommand);
- if (command == commandId)
- {
- SendCommandToHostThroughPipe(stringCommand);
- return;
- }
- }
-
- logger.LogWarning("Received unknown service command: {command}", command);
- }
+ protected override void OnCustomCommand(int command) => serviceLifetime!.HandleCustomCommand(command);
///
protected override void OnStart(string[] args)
{
- if (loggerFactory == null)
- {
- loggerFactory = LoggerFactory.Create(builder => builder.AddEventLog(new EventLogSettings
- {
- LogName = EventLog.Log,
- MachineName = EventLog.MachineName,
- SourceName = EventLog.Source,
- Filter = (message, logLevel) => logLevel >= minimumLogLevel,
- }));
-
- logger = loggerFactory.CreateLogger();
- }
-
- var watchdog = watchdogFactory.CreateWatchdog(this, loggerFactory);
-
- cancellationTokenSource?.Dispose();
- cancellationTokenSource = new CancellationTokenSource();
-
var newArgs = new List(commandLineArguments.Length + args.Length + 1)
{
"--General:SetupWizardMode=Never",
@@ -167,94 +100,18 @@ namespace Tgstation.Server.Host.Service
newArgs.AddRange(commandLineArguments);
newArgs.AddRange(args);
- watchdogTask = RunWatchdog(watchdog, newArgs.ToArray(), cancellationTokenSource.Token);
-
- if (!watchdogTask.IsCompleted && watchdog.InitialHostVersion >= new Version(5, 14, 0))
- {
- logger.LogInformation("Waiting for host to finish starting...");
- using var streamReader = new StreamReader(
- readyPipeServer,
- Encoding.UTF8,
- leaveOpen: true);
-
- var line = streamReader.ReadLine(); // Intentionally blocking service startup
- logger.LogDebug("Pipe read: {line}", line);
- }
-
- // Maybe we'll use this pipe more in the future, but for now leaving it open is just a resource waste
- readyPipeServer.Dispose();
+ serviceLifetime = new ServiceLifetime(
+ Stop,
+ signalChecker => watchdogFactory.CreateWatchdog(signalChecker, loggerFactory.Value),
+ loggerFactory.Value.CreateLogger(),
+ args);
}
///
protected override void OnStop()
{
- cancellationTokenSource.Cancel();
- watchdogTask.GetAwaiter().GetResult();
- }
-
- ///
- /// Executes the , stopping the service if it exits.
- ///
- /// The to run.
- /// The arguments for the .
- /// The for the operation.
- /// A representing the running operation.
- async Task RunWatchdog(IWatchdog watchdog, string[] args, CancellationToken cancellationToken)
- {
- await watchdog.RunAsync(false, args, cancellationToken);
-
- async void StopServiceAsync()
- {
- try
- {
- await Task.Run(Stop, cancellationToken); // DCT intentional
- }
- catch (OperationCanceledException ex)
- {
- logger.LogTrace(ex, "Stopping service cancelled!");
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "Error stopping service!");
- }
- }
-
- StopServiceAsync();
- }
-
- ///
- /// Sends a command to the main server process.
- ///
- /// One of the .
- void SendCommandToHostThroughPipe(string command)
- {
- var localPipeServer = commandPipeServer;
- if (localPipeServer == null)
- {
- logger.LogWarning("Unable to send command \"{command}\" to main server process. Is the service running?", command);
- return;
- }
-
- logger.LogDebug("Send command: {command}", command);
- try
- {
- var encoding = Encoding.UTF8;
- using var streamWriter = new StreamWriter(
- localPipeServer,
- encoding,
- PipeCommands
- .AllCommands
- .Select(
- command => encoding.GetByteCount(
- command + Environment.NewLine))
- .Max(),
- true);
- streamWriter.WriteLine(command);
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "Error attempting to send command \"{command}\"", command);
- }
+ var oldLifetime = Interlocked.Exchange(ref serviceLifetime, null);
+ oldLifetime?.DisposeAsync().GetAwaiter().GetResult();
}
}
}
diff --git a/src/Tgstation.Server.Host.Service/ServiceLifetime.cs b/src/Tgstation.Server.Host.Service/ServiceLifetime.cs
new file mode 100644
index 0000000000..efc058fbaa
--- /dev/null
+++ b/src/Tgstation.Server.Host.Service/ServiceLifetime.cs
@@ -0,0 +1,200 @@
+using System;
+using System.IO;
+using System.IO.Pipes;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Extensions.Logging;
+
+using Tgstation.Server.Host.Common;
+using Tgstation.Server.Host.Watchdog;
+
+namespace Tgstation.Server.Host.Service
+{
+ ///
+ /// Represents the lifetime of the service.
+ ///
+ sealed class ServiceLifetime : ISignalChecker, IAsyncDisposable
+ {
+ ///
+ /// The for the .
+ ///
+ readonly ILogger logger;
+
+ ///
+ /// The that represents the running .
+ ///
+ readonly Task watchdogTask;
+
+ ///
+ /// The for the .
+ ///
+ readonly CancellationTokenSource cancellationTokenSource;
+
+ ///
+ /// The for sending to the server process.
+ ///
+ AnonymousPipeServerStream? commandPipeServer;
+
+ ///
+ /// The for receiving the .
+ ///
+ AnonymousPipeServerStream? readyPipeServer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// An to manually stop the service.
+ /// A taking a and returning the to run.
+ /// The value of .
+ /// The arguments for the .
+ public ServiceLifetime(Action stopService, Func watchdogFactory, ILogger logger, string[] args)
+ {
+ ArgumentNullException.ThrowIfNull(stopService);
+ ArgumentNullException.ThrowIfNull(watchdogFactory);
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ ArgumentNullException.ThrowIfNull(args);
+
+ cancellationTokenSource = new CancellationTokenSource();
+ watchdogTask = RunWatchdog(
+ stopService,
+ watchdogFactory(this),
+ args,
+ cancellationTokenSource.Token);
+ }
+
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ cancellationTokenSource.Cancel();
+ await watchdogTask;
+ cancellationTokenSource.Dispose();
+
+ if (commandPipeServer != null)
+ await commandPipeServer.DisposeAsync();
+
+ if (readyPipeServer != null)
+ await readyPipeServer.DisposeAsync();
+ }
+
+ ///
+ public async ValueTask CheckSignals(Func startChildAndGetPid, CancellationToken cancellationToken)
+ {
+ try
+ {
+ await using (commandPipeServer = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable))
+ await using (readyPipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable))
+ {
+ var (_, lifetimeTask) = startChildAndGetPid($"--Internal:CommandPipe={commandPipeServer.GetClientHandleAsString()} --Internal:ReadyPipe={readyPipeServer.GetClientHandleAsString()}");
+ commandPipeServer.DisposeLocalCopyOfClientHandle();
+ readyPipeServer.DisposeLocalCopyOfClientHandle();
+ await lifetimeTask;
+ }
+ }
+ finally
+ {
+ readyPipeServer = null;
+ commandPipeServer = null;
+ }
+ }
+
+ ///
+ /// Handle a custom service .
+ ///
+ /// The command sent to the service.
+ public void HandleCustomCommand(int command)
+ {
+ var commandsToCheck = PipeCommands.AllCommands;
+ foreach (var stringCommand in commandsToCheck)
+ {
+ var commandId = PipeCommands.GetServiceCommandId(stringCommand);
+ if (command == commandId)
+ {
+ SendCommandToHostThroughPipe(stringCommand);
+ return;
+ }
+ }
+
+ logger.LogWarning("Received unknown service command: {command}", command);
+ }
+
+ ///
+ /// Executes the , stopping the service if it exits.
+ ///
+ /// An to manually stop the service.
+ /// The to run.
+ /// The arguments for the .
+ /// The for the operation.
+ /// A representing the running operation.
+ async Task RunWatchdog(Action stopService, IWatchdog watchdog, string[] args, CancellationToken cancellationToken)
+ {
+ var localWatchdogTask = watchdog.RunAsync(false, args, cancellationToken);
+
+ if (!localWatchdogTask.IsCompleted && (await watchdog.InitialHostVersion) >= new Version(5, 14, 0))
+ if (readyPipeServer != null)
+ {
+ logger.LogInformation("Waiting for host to finish starting...");
+ using var streamReader = new StreamReader(
+ readyPipeServer,
+ Encoding.UTF8,
+ leaveOpen: true);
+
+ var line = streamReader.ReadLine(); // Intentionally blocking service startup
+ logger.LogDebug("Pipe read: {line}", line);
+
+ // Maybe we'll use this pipe more in the future, but for now leaving it open is just a resource waste
+ readyPipeServer.Dispose();
+ }
+ else
+ logger.LogError("Watchdog started and ready pipe was not initialized!");
+
+ await localWatchdogTask;
+
+ try
+ {
+ stopService();
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Error stopping service!");
+ }
+ }
+
+ ///
+ /// Sends a command to the main server process.
+ ///
+ /// One of the .
+ void SendCommandToHostThroughPipe(string command)
+ {
+ var localPipeServer = commandPipeServer;
+ if (localPipeServer == null)
+ {
+ logger.LogWarning("Unable to send command \"{command}\" to main server process. Is the service running?", command);
+ return;
+ }
+
+ logger.LogDebug("Send command: {command}", command);
+ try
+ {
+ var encoding = Encoding.UTF8;
+ using var streamWriter = new StreamWriter(
+ localPipeServer,
+ encoding,
+ PipeCommands
+ .AllCommands
+ .Select(
+ command => encoding.GetByteCount(
+ command + Environment.NewLine))
+ .Max(),
+ true);
+ streamWriter.WriteLine(command);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Error attempting to send command \"{command}\"", command);
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj
index aa98536f38..54710f6b47 100644
--- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj
+++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj
@@ -4,6 +4,7 @@
WinExe
win-x86;win-x64
+ enable
$(TgsFrameworkVersion)
$(TgsCoreVersion)
diff --git a/src/Tgstation.Server.Host.Watchdog/ISignalChecker.cs b/src/Tgstation.Server.Host.Watchdog/ISignalChecker.cs
index b23295dd0f..2b8d42ee80 100644
--- a/src/Tgstation.Server.Host.Watchdog/ISignalChecker.cs
+++ b/src/Tgstation.Server.Host.Watchdog/ISignalChecker.cs
@@ -12,9 +12,9 @@ namespace Tgstation.Server.Host.Watchdog
///
/// Relays signals received to the host process.
///
- /// An to start the main process. It accepts an optional additional command line argument as a paramter and returns it's and lifetime .
+ /// An to start the main process. It accepts an optional additional command line argument as a paramter and returns it's and lifetime . Must be called.
/// The for the operation.
/// A representing the running operation.
- ValueTask CheckSignals(Func startChild, CancellationToken cancellationToken);
+ ValueTask CheckSignals(Func startChildAndGetPid, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs
index dadff2c41b..c9f9e89da9 100644
--- a/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs
+++ b/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs
@@ -10,9 +10,9 @@ namespace Tgstation.Server.Host.Watchdog
public interface IWatchdog
{
///
- /// Gets the current version of the host process. Set once begins and doesn't immediately return .
+ /// Gets a resulting in the current version of the host process. Guaranteed to complete once begins and doesn't immediately return .
///
- Version InitialHostVersion { get; }
+ Task InitialHostVersion { get; }
///
/// Run the .
diff --git a/src/Tgstation.Server.Host.Watchdog/NoopSignalChecker.cs b/src/Tgstation.Server.Host.Watchdog/NoopSignalChecker.cs
index fa5dd56f94..270cf63380 100644
--- a/src/Tgstation.Server.Host.Watchdog/NoopSignalChecker.cs
+++ b/src/Tgstation.Server.Host.Watchdog/NoopSignalChecker.cs
@@ -10,10 +10,10 @@ namespace Tgstation.Server.Host.Watchdog
public sealed class NoopSignalChecker : ISignalChecker
{
///
- public ValueTask CheckSignals(Func startChild, CancellationToken cancellationToken)
+ public ValueTask CheckSignals(Func startChildAndGetPid, CancellationToken cancellationToken)
{
- ArgumentNullException.ThrowIfNull(startChild);
- startChild(null);
+ ArgumentNullException.ThrowIfNull(startChildAndGetPid);
+ startChildAndGetPid(null);
return ValueTask.CompletedTask;
}
}
diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj
index ae3b89cdb2..04f187c08e 100644
--- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj
+++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj
@@ -3,6 +3,7 @@
$(TgsFrameworkVersion)
+ enable
false
$(TgsHostWatchdogVersion)
diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs
index bab5e9a07b..a574a6ab6b 100644
--- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs
+++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Watchdog
sealed class Watchdog : IWatchdog
{
///
- public Version InitialHostVersion { get; private set; }
+ public Task InitialHostVersion => initialHostVersionTcs.Task;
///
/// The for the .
@@ -32,6 +32,11 @@ namespace Tgstation.Server.Host.Watchdog
///
readonly ILogger logger;
+ ///
+ /// Backing for .
+ ///
+ readonly TaskCompletionSource initialHostVersionTcs;
+
///
/// Initializes a new instance of the class.
///
@@ -41,6 +46,8 @@ namespace Tgstation.Server.Host.Watchdog
{
this.signalChecker = signalChecker ?? throw new ArgumentNullException(nameof(signalChecker));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+
+ initialHostVersionTcs = new TaskCompletionSource();
}
///
@@ -54,7 +61,7 @@ namespace Tgstation.Server.Host.Watchdog
currentProcessId = currentProc.Id;
logger.LogDebug("PID: {pid}", currentProcessId);
- string updateDirectory = null;
+ string? updateDirectory = null;
try
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
@@ -76,6 +83,11 @@ namespace Tgstation.Server.Host.Watchdog
var executingAssembly = Assembly.GetExecutingAssembly();
var rootLocation = Path.GetDirectoryName(executingAssembly.Location);
+ if (rootLocation == null)
+ {
+ logger.LogCritical("Failed to get the directory name of the executing assembly: {location}", executingAssembly.Location);
+ return false;
+ }
var assemblyStoragePath = Path.Combine(rootLocation, "lib"); // always always next to watchdog
@@ -119,9 +131,18 @@ namespace Tgstation.Server.Host.Watchdog
return false;
}
- InitialHostVersion = Version.Parse(FileVersionInfo.GetVersionInfo(assemblyPath).FileVersion);
+ var fileVersion = FileVersionInfo.GetVersionInfo(assemblyPath).FileVersion;
+ if (fileVersion == null)
+ {
+ logger.LogCritical("Failed to parse version info from {assemblyPath}!", assemblyPath);
+ return false;
+ }
- var watchdogVersion = executingAssembly.GetName().Version.Semver().ToString();
+ initialHostVersionTcs.SetResult(
+ Version.Parse(
+ fileVersion));
+
+ var watchdogVersion = executingAssembly.GetName().Version?.Semver().ToString();
while (!cancellationToken.IsCancellationRequested)
using (logger.BeginScope("Host invocation"))
@@ -158,8 +179,8 @@ namespace Tgstation.Server.Host.Watchdog
var killedHostProcess = false;
try
{
- Task processTask = null;
- (int, Task) StartProcess(string additionalArg)
+ Task? processTask = null;
+ (int, Task) StartProcess(string? additionalArg)
{
if (additionalArg != null)
process.StartInfo.Arguments += $" {additionalArg}";
@@ -199,7 +220,7 @@ namespace Tgstation.Server.Host.Watchdog
var checkerTask = signalChecker.CheckSignals(StartProcess, cts.Token);
try
{
- await processTask;
+ await processTask!;
}
finally
{
@@ -338,10 +359,11 @@ namespace Tgstation.Server.Host.Watchdog
catch (OperationCanceledException ex)
{
logger.LogDebug(ex, "Exiting due to cancellation...");
- if (!Directory.Exists(updateDirectory))
- File.Delete(updateDirectory);
- else
- Directory.Delete(updateDirectory, true);
+ if (updateDirectory != null)
+ if (!Directory.Exists(updateDirectory))
+ File.Delete(updateDirectory);
+ else
+ Directory.Delete(updateDirectory, true);
}
catch (Exception ex)
{
diff --git a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs
index 855870d4c8..6faf32f95e 100644
--- a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs
+++ b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs
@@ -39,15 +39,25 @@ namespace Tgstation.Server.Host.Service.Tests
var childStarted = false;
ISignalChecker signalChecker = null;
- mockWatchdog.Setup(x => x.RunAsync(false, It.IsNotNull(), It.IsAny())).Callback((bool x, string[] _, CancellationToken token) =>
+ var hostVersionTcs = new TaskCompletionSource();
+ var hostLifetimeTcs = new TaskCompletionSource();
+
+ mockWatchdog.Setup(x => x.RunAsync(false, It.IsNotNull(), It.IsAny())).Returns(async (bool x, string[] _, CancellationToken token) =>
{
+ hostVersionTcs.SetResult(typeof(ServerService).Assembly.GetName().Version);
+
cancellationToken = token;
+ cancellationToken.Register(() => hostLifetimeTcs.SetResult(true));
signalCheckerTask = signalChecker.CheckSignals(additionalArgs =>
{
childStarted = true;
- return (123, Task.CompletedTask);
+ return (123, hostLifetimeTcs.Task);
}, cancellationToken).AsTask();
- }).ReturnsAsync(true).Verifiable();
+
+ await signalCheckerTask;
+ return true;
+ }).Verifiable();
+ mockWatchdog.SetupGet(x => x.InitialHostVersion).Returns(hostVersionTcs.Task);
var mockWatchdogFactory = new Mock();
mockWatchdogFactory.Setup(x => x.CreateWatchdog(It.IsNotNull(), It.IsNotNull()))
@@ -68,6 +78,7 @@ namespace Tgstation.Server.Host.Service.Tests
mockWatchdogFactory.VerifyAll();
Assert.IsTrue(signalCheckerTask.IsCompleted);
+ Assert.IsTrue(cancellationToken.IsCancellationRequested);
}
}
}