mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-30 00:22:40 +01:00
An overly complicated change to make sure a task is awaited.
- IDisposable moved from IProcessBase to IProcess - IInstanceManager, IInstance, IWatchdog, and ISessionController switched from IDisposable to IAsyncDisposable - Process lifetime continuations in SessionController now handled correctly
This commit is contained in:
@@ -215,30 +215,34 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider
|
||||
var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout);
|
||||
|
||||
using var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job);
|
||||
using var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken).ConfigureAwait(false);
|
||||
var launchResult = await controller.LaunchResult.ConfigureAwait(false);
|
||||
|
||||
var now = DateTimeOffset.Now;
|
||||
if (now < timeoutAt && launchResult.StartupTime.HasValue)
|
||||
ApiValidationStatus validationStatus;
|
||||
using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job))
|
||||
await using (var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeoutAt - now, cancellationToken);
|
||||
var launchResult = await controller.LaunchResult.ConfigureAwait(false);
|
||||
|
||||
await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var now = DateTimeOffset.Now;
|
||||
if (now < timeoutAt && launchResult.StartupTime.HasValue)
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeoutAt - now, cancellationToken);
|
||||
|
||||
await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
if (!controller.Lifetime.IsCompleted)
|
||||
{
|
||||
if (requireValidate)
|
||||
throw new JobException(ErrorCode.DreamMakerNeverValidated);
|
||||
await controller.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
validationStatus = controller.ApiValidationStatus;
|
||||
logger.LogTrace("API validation status: {0}", validationStatus);
|
||||
|
||||
job.DMApiVersion = controller.DMApiVersion;
|
||||
}
|
||||
|
||||
if (!controller.Lifetime.IsCompleted)
|
||||
{
|
||||
if (requireValidate)
|
||||
throw new JobException(ErrorCode.DreamMakerNeverValidated);
|
||||
controller.Dispose();
|
||||
}
|
||||
|
||||
var validationStatus = controller.ApiValidationStatus;
|
||||
logger.LogTrace("API validation status: {0}", validationStatus);
|
||||
|
||||
job.DMApiVersion = controller.DMApiVersion;
|
||||
switch (validationStatus)
|
||||
{
|
||||
case ApiValidationStatus.RequiresUltrasafe:
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// For interacting with the instance services
|
||||
/// </summary>
|
||||
public interface IInstance : ILatestCompileJobProvider, IHostedService, IRenameNotifyee, IDisposable
|
||||
public interface IInstance : ILatestCompileJobProvider, IHostedService, IRenameNotifyee, IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IRepositoryManager"/> for the <see cref="IInstance"/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Interop.Bridge;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
@@ -13,7 +14,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
/// <param name="bridgeRegistrar">The <see cref="IBridgeRegistrar"/> to use.</param>
|
||||
/// <param name="metadata">The <see cref="Models.Instance"/></param>
|
||||
/// <returns>A new <see cref="IInstance"/></returns>
|
||||
IInstance CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IInstance"/>.</returns>
|
||||
Task<IInstance> CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata);
|
||||
}
|
||||
}
|
||||
@@ -133,14 +133,14 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
using (LogContext.PushProperty("Instance", metadata.Id))
|
||||
{
|
||||
timerCts?.Dispose();
|
||||
Configuration.Dispose();
|
||||
Chat.Dispose();
|
||||
Watchdog.Dispose();
|
||||
await Watchdog.DisposeAsync().ConfigureAwait(false);
|
||||
dmbFactory.Dispose();
|
||||
RepositoryManager.Dispose();
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
public IInstance CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata)
|
||||
public async Task<IInstance> CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata)
|
||||
{
|
||||
// Create the ioManager for the instance
|
||||
var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path);
|
||||
@@ -294,7 +294,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
catch
|
||||
{
|
||||
watchdog.Dispose();
|
||||
await watchdog.DisposeAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ using Tgstation.Server.Host.System;
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class InstanceManager : IInstanceManager, IRestartHandler, IHostedService, IBridgeRegistrar, IDisposable
|
||||
sealed class InstanceManager : IInstanceManager, IRestartHandler, IHostedService, IBridgeRegistrar, IAsyncDisposable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task Ready => readyTcs.Task;
|
||||
@@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Components
|
||||
Version downgradeVersion;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="InstanceManager"/> has been <see cref="Dispose"/>d
|
||||
/// If the <see cref="InstanceManager"/> has been <see cref="DisposeAsync"/>'d
|
||||
/// </summary>
|
||||
bool disposed;
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
lock (instances)
|
||||
{
|
||||
@@ -178,7 +178,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
foreach (var I in instances)
|
||||
I.Value.Dispose();
|
||||
await I.Value.DisposeAsync().ConfigureAwait(false);
|
||||
|
||||
lazyRestartRegistration.Value.Dispose();
|
||||
|
||||
@@ -261,7 +261,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
finally
|
||||
{
|
||||
instance.Dispose();
|
||||
await instance.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ namespace Tgstation.Server.Host.Components
|
||||
if (metadata == null)
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
logger.LogInformation("Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path);
|
||||
var instance = instanceFactory.CreateInstance(this, metadata);
|
||||
var instance = await instanceFactory.CreateInstance(this, metadata).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
lock (instances)
|
||||
@@ -283,7 +283,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
catch
|
||||
{
|
||||
instance.Dispose();
|
||||
await instance.DisposeAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// <summary>
|
||||
/// Handles communication with a DreamDaemon <see cref="IProcess"/>
|
||||
/// </summary>
|
||||
interface ISessionController : IRenameNotifyee, IProcessBase
|
||||
interface ISessionController : IProcessBase, IRenameNotifyee, IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Task"/> that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb or when it crashes
|
||||
@@ -70,8 +70,8 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// <summary>
|
||||
/// Releases the <see cref="IProcess"/> without terminating it. Also calls <see cref="IDisposable.Dispose"/>
|
||||
/// </summary>
|
||||
/// <returns><see cref="ReattachInformation"/> which can be used to create a new <see cref="ISessionController"/> similar to this one</returns>
|
||||
ReattachInformation Release();
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see cref="ReattachInformation"/> which can be used to create a new <see cref="ISessionController"/>.</returns>
|
||||
Task<ReattachInformation> Release();
|
||||
|
||||
/// <summary>
|
||||
/// Sends a command to DreamDaemon through /world/Topic()
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
public Task<LaunchResult> LaunchResult { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> Lifetime => process.Lifetime;
|
||||
public Task<int> Lifetime { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task OnReboot => rebootTcs.Task;
|
||||
@@ -202,6 +202,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// <param name="chatTrackingContext">The value of <see cref="chatTrackingContext"/></param>
|
||||
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="SessionController"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="postLifetimeCallback">The <see cref="Func{TResult}"/> returning a <see cref="Task"/> to be run after the <paramref name="process"/> ends.</param>
|
||||
/// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/></param>
|
||||
/// <param name="reattached">If this is a reattached session.</param>
|
||||
/// <param name="apiValidate">If this is a DMAPI validation session.</param>
|
||||
@@ -216,6 +217,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
IChatManager chat,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
ILogger<SessionController> logger,
|
||||
Func<Task> postLifetimeCallback,
|
||||
uint? startupTimeout,
|
||||
bool reattached,
|
||||
bool apiValidate)
|
||||
@@ -253,15 +255,14 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
reattachTopicCts = new CancellationTokenSource();
|
||||
synchronizationLock = new object();
|
||||
|
||||
_ = process.Lifetime.ContinueWith(
|
||||
x =>
|
||||
{
|
||||
lock (synchronizationLock)
|
||||
if (!disposed)
|
||||
reattachTopicCts.Cancel();
|
||||
chatTrackingContext.Active = false;
|
||||
},
|
||||
TaskScheduler.Current);
|
||||
async Task<int> WrapLifetime()
|
||||
{
|
||||
var exitCode = await process.Lifetime.ConfigureAwait(false);
|
||||
await postLifetimeCallback().ConfigureAwait(false);
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
Lifetime = WrapLifetime();
|
||||
|
||||
LaunchResult = GetLaunchResult(
|
||||
assemblyInformationProvider,
|
||||
@@ -271,58 +272,31 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
logger.LogDebug("Created session controller. CommsKey: {0}, Port: {1}", reattachInformation.AccessIdentifier, Port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="SessionController"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>The finalizer dispose pattern is necessary so we don't accidentally leak the executable</remarks>
|
||||
#pragma warning disable CA1821 // Remove empty Finalizers TODO: remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed
|
||||
~SessionController() => Dispose(false);
|
||||
#pragma warning restore CA1821 // Remove empty Finalizers
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the <see cref="IDisposable"/> pattern
|
||||
/// </summary>
|
||||
/// <param name="disposing">If this function was NOT called by the finalizer</param>
|
||||
void Dispose(bool disposing)
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
lock (synchronizationLock)
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
disposed = true;
|
||||
logger.LogTrace("Disposing...");
|
||||
if (disposing)
|
||||
{
|
||||
if (!released)
|
||||
{
|
||||
process.Terminate();
|
||||
byondLock.Dispose();
|
||||
}
|
||||
|
||||
process.Dispose();
|
||||
bridgeRegistration?.Dispose();
|
||||
reattachInformation.Dmb?.Dispose(); // will be null when released
|
||||
chatTrackingContext.Dispose();
|
||||
reattachTopicCts.Dispose();
|
||||
}
|
||||
else
|
||||
logger.LogTrace("Disposing...");
|
||||
if (!released)
|
||||
{
|
||||
if (logger != null)
|
||||
logger.LogError("Being disposed via finalizer!");
|
||||
if (!released)
|
||||
if (process != null)
|
||||
process.Terminate();
|
||||
else if (logger != null)
|
||||
logger.LogCritical("Unable to terminate active DreamDaemon session due to finalizer ordering!");
|
||||
process.Terminate();
|
||||
byondLock.Dispose();
|
||||
}
|
||||
|
||||
process.Dispose();
|
||||
bridgeRegistration?.Dispose();
|
||||
reattachInformation.Dmb?.Dispose(); // will be null when released
|
||||
chatTrackingContext.Dispose();
|
||||
reattachTopicCts.Dispose();
|
||||
}
|
||||
|
||||
// finish the async callback
|
||||
await Lifetime.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -530,7 +504,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ObjectDisposedException"/> if <see cref="Dispose(bool)"/> has been called
|
||||
/// Throws an <see cref="ObjectDisposedException"/> if <see cref="DisposeAsync"/> has been called
|
||||
/// </summary>
|
||||
void CheckDisposed()
|
||||
{
|
||||
@@ -542,7 +516,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
public void EnableCustomChatCommands() => chatTrackingContext.Active = DMApiAvailable;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReattachInformation Release()
|
||||
public async Task<ReattachInformation> Release()
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
@@ -550,7 +524,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
var tmpProvider = reattachInformation.Dmb;
|
||||
reattachInformation.Dmb = null;
|
||||
released = true;
|
||||
Dispose();
|
||||
await DisposeAsync().ConfigureAwait(false);
|
||||
byondLock.DoNotDeleteThisSession();
|
||||
tmpProvider.KeepAlive();
|
||||
reattachInformation.Dmb = tmpProvider;
|
||||
|
||||
@@ -297,22 +297,20 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
}
|
||||
|
||||
// Log DD output
|
||||
_ = process.Lifetime.ContinueWith(
|
||||
async x =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var ddOutput = await GetDDOutput().ConfigureAwait(false);
|
||||
logger.LogTrace(
|
||||
"DreamDaemon Output:{0}{1}",
|
||||
Environment.NewLine, ddOutput);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning("Error reading DreamDaemon output: {0}", ex);
|
||||
}
|
||||
},
|
||||
TaskScheduler.Current);
|
||||
async Task PostLifetime()
|
||||
{
|
||||
try
|
||||
{
|
||||
var ddOutput = await GetDDOutput().ConfigureAwait(false);
|
||||
logger.LogTrace(
|
||||
"DreamDaemon Output:{0}{1}",
|
||||
Environment.NewLine, ddOutput);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning("Error reading DreamDaemon output: {0}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -342,6 +340,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
chat,
|
||||
assemblyInformationProvider,
|
||||
loggerFactory.CreateLogger<SessionController>(),
|
||||
PostLifetime,
|
||||
launchParameters.StartupTimeout,
|
||||
false,
|
||||
apiValidate);
|
||||
@@ -417,6 +416,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
chat,
|
||||
assemblyInformationProvider,
|
||||
loggerFactory.CreateLogger<SessionController>(),
|
||||
() => Task.CompletedTask,
|
||||
null,
|
||||
true,
|
||||
false);
|
||||
|
||||
@@ -163,9 +163,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DisposeAndNullControllersImpl()
|
||||
protected override async Task DisposeAndNullControllersImpl()
|
||||
{
|
||||
Server?.Dispose();
|
||||
var disposeTask = Server?.DisposeAsync();
|
||||
if (!disposeTask.HasValue)
|
||||
return;
|
||||
|
||||
await disposeTask.Value.ConfigureAwait(false);
|
||||
Server = null;
|
||||
gracefulRebootRequired = false;
|
||||
}
|
||||
@@ -227,7 +231,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
// kill the controllers
|
||||
bool serverWasActive = Server != null;
|
||||
DisposeAndNullControllers();
|
||||
await DisposeAndNullControllers(default).ConfigureAwait(false);
|
||||
|
||||
// server didn't get control of this dmb
|
||||
if (dmbToUse != null && !serverWasActive)
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// Runs and monitors the twin server controllers
|
||||
/// </summary>
|
||||
public interface IWatchdog : IHostedService, IDisposable, IEventConsumer, IRenameNotifyee
|
||||
public interface IWatchdog : IHostedService, IAsyncDisposable, IEventConsumer, IRenameNotifyee
|
||||
{
|
||||
/// <summary>
|
||||
/// The current <see cref="WatchdogStatus"/>.
|
||||
|
||||
@@ -59,11 +59,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
protected TaskCompletionSource<object> ActiveParametersUpdated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> for the <see cref="WatchdogBase"/>.
|
||||
/// </summary>
|
||||
protected SemaphoreSlim Semaphore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.
|
||||
/// </summary>
|
||||
@@ -94,6 +89,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly Api.Models.Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> for the <see cref="WatchdogBase"/>.
|
||||
/// </summary>
|
||||
readonly SemaphoreSlim synchronizationSemaphore;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SemaphoreSlim"/> used for <see cref="DisposeAndNullControllers"/>.
|
||||
/// </summary>
|
||||
readonly SemaphoreSlim controllerDisposeSemaphore;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>
|
||||
/// </summary>
|
||||
@@ -124,11 +129,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="lock"/> <see cref="object"/> used for <see cref="DisposeAndNullControllers"/>.
|
||||
/// </summary>
|
||||
readonly object controllerDisposeLock;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="WatchdogBase"/> should <see cref="LaunchNoLock(bool, bool, bool, ReattachInformation, CancellationToken)"/> in <see cref="StartAsync(CancellationToken)"/>
|
||||
/// </summary>
|
||||
@@ -165,7 +165,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
bool releaseServers;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="WatchdogBase"/> has been <see cref="Dispose"/>d.
|
||||
/// If the <see cref="WatchdogBase"/> has been <see cref="DisposeAsync"/>'d.
|
||||
/// </summary>
|
||||
bool disposed;
|
||||
|
||||
@@ -224,16 +224,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ActiveLaunchParameters = initialLaunchParameters;
|
||||
releaseServers = false;
|
||||
ActiveParametersUpdated = new TaskCompletionSource<object>();
|
||||
controllerDisposeLock = new object();
|
||||
|
||||
restartRegistration = serverControl.RegisterForRestart(this);
|
||||
try
|
||||
{
|
||||
Semaphore = new SemaphoreSlim(1);
|
||||
synchronizationSemaphore = new SemaphoreSlim(1);
|
||||
controllerDisposeSemaphore = new SemaphoreSlim(1);
|
||||
}
|
||||
catch
|
||||
{
|
||||
restartRegistration.Dispose();
|
||||
synchronizationSemaphore?.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -241,18 +242,19 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
Logger.LogTrace("Disposing...");
|
||||
Semaphore.Dispose();
|
||||
synchronizationSemaphore.Dispose();
|
||||
restartRegistration.Dispose();
|
||||
DisposeAndNullControllers();
|
||||
await DisposeAndNullControllers(default).ConfigureAwait(false);
|
||||
controllerDisposeSemaphore.Dispose();
|
||||
monitorCts?.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="Terminate(bool, CancellationToken)"/>. Does not lock <see cref="Semaphore"/>
|
||||
/// Implementation of <see cref="Terminate(bool, CancellationToken)"/>. Does not lock <see cref="synchronizationSemaphore"/>
|
||||
/// </summary>
|
||||
/// <param name="graceful">If <see langword="true"/> the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately</param>
|
||||
/// <param name="announce">If <see langword="true"/> the termination will be announced using <see cref="Chat"/></param>
|
||||
@@ -272,8 +274,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
await StopMonitor().ConfigureAwait(false);
|
||||
|
||||
DisposeAndNullControllers();
|
||||
|
||||
LastLaunchParameters = null;
|
||||
|
||||
await chatTask.ConfigureAwait(false);
|
||||
@@ -330,7 +330,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var message4 = $"DEFCON 1: Four heartbeats have been missed! {actionTaken}...";
|
||||
Logger.LogWarning(message4);
|
||||
await Chat.SendWatchdogMessage(message4, false, cancellationToken).ConfigureAwait(false);
|
||||
DisposeAndNullControllers();
|
||||
await DisposeAndNullControllers(cancellationToken).ConfigureAwait(false);
|
||||
return shouldShutdown ? MonitorAction.Exit : MonitorAction.Restart;
|
||||
default:
|
||||
Logger.LogError("Invalid heartbeats missed count: {0}", heartbeatsMissed);
|
||||
@@ -477,7 +477,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
protected async Task ReattachFailure(Task chatTask, CancellationToken cancellationToken)
|
||||
{
|
||||
// we lost the server, just restart entirely
|
||||
DisposeAndNullControllers();
|
||||
await DisposeAndNullControllers(default).ConfigureAwait(false);
|
||||
const string FailReattachMessage = "Unable to properly reattach to server! Restarting watchdog...";
|
||||
Logger.LogWarning(FailReattachMessage);
|
||||
|
||||
@@ -493,16 +493,19 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// Call <see cref="IDisposable.Dispose"/> and null the fields for all <see cref="ISessionController"/>s.
|
||||
/// </summary>
|
||||
protected abstract void DisposeAndNullControllersImpl();
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected abstract Task DisposeAndNullControllersImpl();
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for <see cref="DisposeAndNullControllersImpl"/> under a locked context.
|
||||
/// </summary>
|
||||
protected void DisposeAndNullControllers()
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected async Task DisposeAndNullControllers(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("DisposeAndNullControllers");
|
||||
lock (controllerDisposeLock)
|
||||
DisposeAndNullControllersImpl();
|
||||
using (await SemaphoreSlimContext.Lock(controllerDisposeSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
await DisposeAndNullControllersImpl().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -529,14 +532,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
private async Task MonitorRestart(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("Monitor restart!");
|
||||
DisposeAndNullControllers();
|
||||
await DisposeAndNullControllers(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var chatTask = Task.CompletedTask;
|
||||
for (var retryAttempts = 1; ; ++retryAttempts)
|
||||
{
|
||||
Status = WatchdogStatus.Restoring;
|
||||
Exception launchException;
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
// use LaunchImplNoLock without announcements or restarting the monitor
|
||||
@@ -633,7 +636,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
Logger.LogTrace("Monitor activated");
|
||||
|
||||
// always run HandleMonitorWakeup from the context of the semaphore lock
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// Set this sooner so chat sends don't hold us up
|
||||
if (activeServerLifetime.IsCompleted)
|
||||
@@ -736,11 +739,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (releaseServers)
|
||||
{
|
||||
Logger.LogTrace("Detaching servers...");
|
||||
releasedReattachInformation = GetActiveController().Release();
|
||||
releasedReattachInformation = await GetActiveController().Release().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
DisposeAndNullControllers();
|
||||
await DisposeAndNullControllers(default).ConfigureAwait(false);
|
||||
Status = WatchdogStatus.Offline;
|
||||
|
||||
Logger.LogTrace("Monitor exiting...");
|
||||
@@ -758,7 +761,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
bool match = launchParameters.CanApplyWithoutReboot(ActiveLaunchParameters);
|
||||
ActiveLaunchParameters = launchParameters;
|
||||
@@ -808,7 +811,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
public async Task<string> HandleChatCommand(string commandName, string arguments, ChatUser sender, CancellationToken cancellationToken)
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (Status == WatchdogStatus.Offline)
|
||||
return "TGS: Server offline!";
|
||||
@@ -836,14 +839,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
if (Status != WatchdogStatus.Offline)
|
||||
throw new JobException(ErrorCode.WatchdogRunning);
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
await LaunchNoLock(true, true, true, null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task ResetRebootState(CancellationToken cancellationToken)
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (Status == WatchdogStatus.Offline)
|
||||
return;
|
||||
@@ -860,7 +863,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
throw new JobException(ErrorCode.WatchdogNotRunning);
|
||||
|
||||
Logger.LogTrace("Begin Restart. Graceful: {0}", graceful);
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (!graceful)
|
||||
{
|
||||
@@ -912,7 +915,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
};
|
||||
await jobManager.RegisterOperation(job, async (j, databaseContextFactory, progressFunction, ct) =>
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, ct).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, ct).ConfigureAwait(false))
|
||||
await LaunchNoLock(true, true, true, reattachInfo, ct).ConfigureAwait(false);
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -942,7 +945,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
public async Task Terminate(bool graceful, CancellationToken cancellationToken)
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken).ConfigureAwait(false))
|
||||
await TerminateNoLock(graceful, !releaseServers, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,15 +102,15 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
var _ = DisposeAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DisposeAndNullControllersImpl()
|
||||
protected override async Task DisposeAndNullControllersImpl()
|
||||
{
|
||||
base.DisposeAndNullControllersImpl();
|
||||
await base.DisposeAndNullControllersImpl().ConfigureAwait(false);
|
||||
|
||||
// If we reach this point, we can guarantee PrepServerForLaunch will be called before starting again.
|
||||
ActiveSwappable = null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
@@ -6,7 +7,7 @@ namespace Tgstation.Server.Host.System
|
||||
/// <summary>
|
||||
/// Abstraction over a <see cref="global::System.Diagnostics.Process"/>
|
||||
/// </summary>
|
||||
interface IProcess : IProcessBase
|
||||
interface IProcess : IProcessBase, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IProcess"/>' ID
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
@@ -7,7 +6,7 @@ namespace Tgstation.Server.Host.System
|
||||
/// <summary>
|
||||
/// Represents process lifetime
|
||||
/// </summary>
|
||||
interface IProcessBase : IDisposable
|
||||
interface IProcessBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Task{TResult}"/> resulting in the exit code of the process
|
||||
|
||||
Reference in New Issue
Block a user