From ae252591ed50f6dbe22364949b0add03db8f9b2b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 11 Jul 2020 10:41:13 -0400 Subject: [PATCH] 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 --- .../Components/Deployment/DreamMaker.cs | 44 +++++----- .../Components/IInstance.cs | 2 +- .../Components/IInstanceFactory.cs | 5 +- .../Components/Instance.cs | 4 +- .../Components/InstanceFactory.cs | 4 +- .../Components/InstanceManager.cs | 14 ++-- .../Components/Session/ISessionController.cs | 6 +- .../Components/Session/SessionController.cs | 82 +++++++------------ .../Session/SessionControllerFactory.cs | 32 ++++---- .../Components/Watchdog/BasicWatchdog.cs | 10 ++- .../Components/Watchdog/IWatchdog.cs | 2 +- .../Components/Watchdog/WatchdogBase.cs | 77 ++++++++--------- .../Components/Watchdog/WindowsWatchdog.cs | 6 +- src/Tgstation.Server.Host/System/IProcess.cs | 5 +- .../System/IProcessBase.cs | 5 +- 15 files changed, 142 insertions(+), 156 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 02842f16cf..4c4508a45a 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -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: diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 475c625482..05a0c2fa91 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Components /// /// For interacting with the instance services /// - public interface IInstance : ILatestCompileJobProvider, IHostedService, IRenameNotifyee, IDisposable + public interface IInstance : ILatestCompileJobProvider, IHostedService, IRenameNotifyee, IAsyncDisposable { /// /// The for the diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index ce13cdf11e..473837ccf2 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -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 /// /// The to use. /// The - /// A new - IInstance CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata); + /// A resulting in a new . + Task CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 078e3814e4..33058744c2 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -133,14 +133,14 @@ namespace Tgstation.Server.Host.Components } /// - 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(); } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index a1a34295aa..4866ab4987 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -192,7 +192,7 @@ namespace Tgstation.Server.Host.Components /// #pragma warning disable CA1506 // TODO: Decomplexify - public IInstance CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata) + public async Task 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; } } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 5fe7cb053a..5c7606b403 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -23,7 +23,7 @@ using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components { /// - sealed class InstanceManager : IInstanceManager, IRestartHandler, IHostedService, IBridgeRegistrar, IDisposable + sealed class InstanceManager : IInstanceManager, IRestartHandler, IHostedService, IBridgeRegistrar, IAsyncDisposable { /// public Task Ready => readyTcs.Task; @@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Components Version downgradeVersion; /// - /// If the has been d + /// If the has been 'd /// bool disposed; @@ -168,7 +168,7 @@ namespace Tgstation.Server.Host.Components } /// - 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; } diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs index 21439f6cc2..3a42879935 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Session /// /// Handles communication with a DreamDaemon /// - interface ISessionController : IRenameNotifyee, IProcessBase + interface ISessionController : IProcessBase, IRenameNotifyee, IAsyncDisposable { /// /// A 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 /// /// Releases the without terminating it. Also calls /// - /// which can be used to create a new similar to this one - ReattachInformation Release(); + /// A resulting in which can be used to create a new . + Task Release(); /// /// Sends a command to DreamDaemon through /world/Topic() diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index bdda5618d3..3c867094f5 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -83,7 +83,7 @@ namespace Tgstation.Server.Host.Components.Session public Task LaunchResult { get; } /// - public Task Lifetime => process.Lifetime; + public Task Lifetime { get; } /// public Task OnReboot => rebootTcs.Task; @@ -202,6 +202,7 @@ namespace Tgstation.Server.Host.Components.Session /// The value of /// The for the . /// The value of + /// The returning a to be run after the ends. /// The optional time to wait before failing the /// If this is a reattached session. /// If this is a DMAPI validation session. @@ -216,6 +217,7 @@ namespace Tgstation.Server.Host.Components.Session IChatManager chat, IAssemblyInformationProvider assemblyInformationProvider, ILogger logger, + Func 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 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); } - /// - /// Finalizes an instance of the class. - /// - /// The finalizer dispose pattern is necessary so we don't accidentally leak the executable -#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 - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Implements the pattern - /// - /// If this function was NOT called by the finalizer - 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); } /// @@ -530,7 +504,7 @@ namespace Tgstation.Server.Host.Components.Session } /// - /// Throws an if has been called + /// Throws an if has been called /// void CheckDisposed() { @@ -542,7 +516,7 @@ namespace Tgstation.Server.Host.Components.Session public void EnableCustomChatCommands() => chatTrackingContext.Active = DMApiAvailable; /// - public ReattachInformation Release() + public async Task 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; diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index e1e77a2098..be29252054 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -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(), + PostLifetime, launchParameters.StartupTimeout, false, apiValidate); @@ -417,6 +416,7 @@ namespace Tgstation.Server.Host.Components.Session chat, assemblyInformationProvider, loggerFactory.CreateLogger(), + () => Task.CompletedTask, null, true, false); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 90791ab63e..31ad687b96 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -163,9 +163,13 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - 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) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index b593667a5b..1b822a7e65 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Runs and monitors the twin server controllers /// - public interface IWatchdog : IHostedService, IDisposable, IEventConsumer, IRenameNotifyee + public interface IWatchdog : IHostedService, IAsyncDisposable, IEventConsumer, IRenameNotifyee { /// /// The current . diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 0699f96772..20b5b6c628 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -59,11 +59,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// protected TaskCompletionSource ActiveParametersUpdated { get; set; } - /// - /// The for the . - /// - protected SemaphoreSlim Semaphore { get; } - /// /// The for the . /// @@ -94,6 +89,16 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly Api.Models.Instance instance; + /// + /// The for the . + /// + readonly SemaphoreSlim synchronizationSemaphore; + + /// + /// used for . + /// + readonly SemaphoreSlim controllerDisposeSemaphore; + /// /// The for the /// @@ -124,11 +129,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IEventConsumer eventConsumer; - /// - /// used for . - /// - readonly object controllerDisposeLock; - /// /// If the should in /// @@ -165,7 +165,7 @@ namespace Tgstation.Server.Host.Components.Watchdog bool releaseServers; /// - /// If the has been d. + /// If the has been 'd. /// bool disposed; @@ -224,16 +224,17 @@ namespace Tgstation.Server.Host.Components.Watchdog ActiveLaunchParameters = initialLaunchParameters; releaseServers = false; ActiveParametersUpdated = new TaskCompletionSource(); - 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 } /// - 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; } /// - /// Implementation of . Does not lock + /// Implementation of . Does not lock /// /// If the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately /// If the termination will be announced using @@ -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 /// /// Call and null the fields for all s. /// - protected abstract void DisposeAndNullControllersImpl(); + /// A representing the running operation. + protected abstract Task DisposeAndNullControllersImpl(); /// /// Wrapper for under a locked context. /// - protected void DisposeAndNullControllers() + /// The for the operation. + /// A representing the running operation. + protected async Task DisposeAndNullControllers(CancellationToken cancellationToken) { Logger.LogTrace("DisposeAndNullControllers"); - lock (controllerDisposeLock) - DisposeAndNullControllersImpl(); + using (await SemaphoreSlimContext.Lock(controllerDisposeSemaphore, cancellationToken).ConfigureAwait(false)) + await DisposeAndNullControllersImpl().ConfigureAwait(false); } /// @@ -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 /// 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 /// public async Task 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); } /// 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 /// 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); } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs index e01a610f19..7fa442369d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs @@ -102,15 +102,15 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch { - Dispose(); + var _ = DisposeAsync(); throw; } } /// - 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; diff --git a/src/Tgstation.Server.Host/System/IProcess.cs b/src/Tgstation.Server.Host/System/IProcess.cs index b813d95bbc..d961b65694 100644 --- a/src/Tgstation.Server.Host/System/IProcess.cs +++ b/src/Tgstation.Server.Host/System/IProcess.cs @@ -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 /// /// Abstraction over a /// - interface IProcess : IProcessBase + interface IProcess : IProcessBase, IDisposable { /// /// The ' ID diff --git a/src/Tgstation.Server.Host/System/IProcessBase.cs b/src/Tgstation.Server.Host/System/IProcessBase.cs index c861f8ae5f..3530b619b8 100644 --- a/src/Tgstation.Server.Host/System/IProcessBase.cs +++ b/src/Tgstation.Server.Host/System/IProcessBase.cs @@ -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 /// /// Represents process lifetime /// - interface IProcessBase : IDisposable + interface IProcessBase { /// /// The resulting in the exit code of the process