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:
Jordan Brown
2020-07-11 10:41:13 -04:00
parent 308e71d8c9
commit ae252591ed
15 changed files with 142 additions and 156 deletions
@@ -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;
+3 -2
View File
@@ -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