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
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