Session persistence happens at launch

This commit is contained in:
Jordan Brown
2021-08-11 02:56:47 -04:00
parent ea0a1b5039
commit b2df9802cc
7 changed files with 124 additions and 96 deletions
@@ -39,6 +39,11 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
CompileJob CompileJob { get; }
/// <summary>
/// Gets the <see cref="Session.ReattachInformation"/> associated with the <see cref="ISessionController"/>.
/// </summary>
ReattachInformation ReattachInformation { get; }
/// <summary>
/// If the port should be rotated off when the world reboots.
/// </summary>
@@ -67,8 +72,8 @@ namespace Tgstation.Server.Host.Components.Session
/// <summary>
/// Releases the <see cref="IProcess"/> without terminating it. Also calls <see cref="IDisposable.Dispose"/>.
/// </summary>
/// <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();
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task Release();
/// <summary>
/// Sends a command to DreamDaemon through /world/Topic().
@@ -22,5 +22,12 @@ namespace Tgstation.Server.Host.Components.Session
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the stored <see cref="ReattachInformation"/> if any.</returns>
Task<ReattachInformation> Load(CancellationToken cancellationToken);
/// <summary>
/// Clear any stored <see cref="ReattachInformation"/>.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task Clear(CancellationToken cancellationToken);
}
}
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Session
sealed class SessionController : ISessionController, IBridgeHandler, IChannelSink
{
/// <inheritdoc />
public DMApiParameters DMApiParameters => reattachInformation;
public DMApiParameters DMApiParameters => ReattachInformation;
/// <inheritdoc />
public ApiValidationStatus ApiValidationStatus
@@ -41,10 +41,10 @@ namespace Tgstation.Server.Host.Components.Session
}
/// <inheritdoc />
public Models.CompileJob CompileJob => reattachInformation.Dmb.CompileJob;
public Models.CompileJob CompileJob => ReattachInformation.Dmb.CompileJob;
/// <inheritdoc />
public RebootState RebootState => reattachInformation.RebootState;
public RebootState RebootState => ReattachInformation.RebootState;
/// <inheritdoc />
public Version DMApiVersion { get; private set; }
@@ -68,12 +68,12 @@ namespace Tgstation.Server.Host.Components.Session
public Task OnPrime => primeTcs.Task;
/// <inheritdoc />
public bool DMApiAvailable => reattachInformation.Dmb.CompileJob.DMApiVersion?.Major == DMApiConstants.InteropVersion.Major;
public bool DMApiAvailable => ReattachInformation.Dmb.CompileJob.DMApiVersion?.Major == DMApiConstants.InteropVersion.Major;
/// <summary>
/// The up to date <see cref="ReattachInformation"/>.
/// The up to date <see cref="Session.ReattachInformation"/>.
/// </summary>
readonly ReattachInformation reattachInformation;
public ReattachInformation ReattachInformation { get; }
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> that completes when DD makes it's first bridge request.
@@ -173,7 +173,7 @@ namespace Tgstation.Server.Host.Components.Session
/// <summary>
/// Initializes a new instance of the <see cref="SessionController"/> class.
/// </summary>
/// <param name="reattachInformation">The value of <see cref="reattachInformation"/>.</param>
/// <param name="reattachInformation">The value of <see cref="ReattachInformation"/>.</param>
/// <param name="metadata">The owning <see cref="Instance"/>.</param>
/// <param name="process">The value of <see cref="process"/>.</param>
/// <param name="byondLock">The value of <see cref="byondLock"/>.</param>
@@ -203,7 +203,7 @@ namespace Tgstation.Server.Host.Components.Session
bool reattached,
bool apiValidate)
{
this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
ReattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
this.process = process ?? throw new ArgumentNullException(nameof(process));
this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock));
@@ -277,7 +277,7 @@ namespace Tgstation.Server.Host.Components.Session
process.Dispose();
bridgeRegistration?.Dispose();
reattachInformation.Dmb?.Dispose(); // will be null when released
ReattachInformation.Dmb?.Dispose(); // will be null when released
chatTrackingContext.Dispose();
reattachTopicCts.Dispose();
@@ -346,13 +346,13 @@ namespace Tgstation.Server.Host.Components.Session
var currentPort = parameters.CurrentPort.Value;
if (!nextPort.HasValue)
reattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
ReattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
else
{
// nextPort is ready, tell DD to switch to that
// if it fails it'll kill itself
response.NewPort = nextPort.Value;
reattachInformation.Port = nextPort.Value;
ReattachInformation.Port = nextPort.Value;
nextPort = null;
// we'll also get here from SetPort so complete that task
@@ -398,12 +398,12 @@ namespace Tgstation.Server.Host.Components.Session
response.RuntimeInformation = new RuntimeInformation(
chatTrackingContext,
reattachInformation.Dmb,
reattachInformation.RuntimeInformation.ServerVersion,
reattachInformation.RuntimeInformation.InstanceName,
reattachInformation.RuntimeInformation.SecurityLevel,
reattachInformation.RuntimeInformation.ServerPort,
reattachInformation.RuntimeInformation.ApiValidateOnly);
ReattachInformation.Dmb,
ReattachInformation.RuntimeInformation.ServerVersion,
ReattachInformation.RuntimeInformation.InstanceName,
ReattachInformation.RuntimeInformation.SecurityLevel,
ReattachInformation.RuntimeInformation.ServerPort,
ReattachInformation.RuntimeInformation.ApiValidateOnly);
// Load custom commands
chatTrackingContext.CustomCommands = parameters.CustomCommands;
@@ -436,19 +436,18 @@ namespace Tgstation.Server.Host.Components.Session
public void EnableCustomChatCommands() => chatTrackingContext.Active = DMApiAvailable;
/// <inheritdoc />
public async Task<ReattachInformation> Release()
public async Task Release()
{
CheckDisposed();
// we still don't want to dispose the dmb yet, even though we're keeping it alive
var tmpProvider = reattachInformation.Dmb;
reattachInformation.Dmb = null;
var tmpProvider = ReattachInformation.Dmb;
ReattachInformation.Dmb = null;
released = true;
await DisposeAsync().ConfigureAwait(false);
byondLock.DoNotDeleteThisSession();
tmpProvider.KeepAlive();
reattachInformation.Dmb = tmpProvider;
return reattachInformation;
ReattachInformation.Dmb = tmpProvider;
}
/// <inheritdoc />
@@ -471,7 +470,7 @@ namespace Tgstation.Server.Host.Components.Session
return null;
}
parameters.AccessIdentifier = reattachInformation.AccessIdentifier;
parameters.AccessIdentifier = ReattachInformation.AccessIdentifier;
var json = JsonConvert.SerializeObject(parameters, DMApiConstants.SerializerSettings);
logger.LogTrace("Topic request: {0}", json);
@@ -483,7 +482,7 @@ namespace Tgstation.Server.Host.Components.Session
byondTopicSender.SanitizeString(DMApiConstants.TopicData),
byondTopicSender.SanitizeString(json));
var targetPort = reattachInformation.Port;
var targetPort = ReattachInformation.Port;
var topicResponse = await byondTopicSender.SendTopic(
new IPEndPoint(IPAddress.Loopback, targetPort),
@@ -547,7 +546,7 @@ namespace Tgstation.Server.Host.Components.Session
if (commandResult.InteropResponse?.ErrorMessage != null)
return false;
reattachInformation.Port = port;
ReattachInformation.Port = port;
return true;
}
@@ -572,7 +571,7 @@ namespace Tgstation.Server.Host.Components.Session
logger.LogTrace("Changing reboot state to {0}", newRebootState);
reattachInformation.RebootState = newRebootState;
ReattachInformation.RebootState = newRebootState;
var result = await SendCommand(
new TopicParameters(newRebootState),
cancellationToken)
@@ -586,7 +585,7 @@ namespace Tgstation.Server.Host.Components.Session
{
CheckDisposed();
logger.LogTrace("Resetting reboot state...");
reattachInformation.RebootState = RebootState.Normal;
ReattachInformation.RebootState = RebootState.Normal;
}
/// <inheritdoc />
@@ -601,15 +600,15 @@ namespace Tgstation.Server.Host.Components.Session
/// <inheritdoc />
public void ReplaceDmbProvider(IDmbProvider dmbProvider)
{
var oldDmb = reattachInformation.Dmb;
reattachInformation.Dmb = dmbProvider ?? throw new ArgumentNullException(nameof(dmbProvider));
var oldDmb = ReattachInformation.Dmb;
ReattachInformation.Dmb = dmbProvider ?? throw new ArgumentNullException(nameof(dmbProvider));
oldDmb.Dispose();
}
/// <inheritdoc />
public Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
{
reattachInformation.RuntimeInformation.InstanceName = newInstanceName;
ReattachInformation.RuntimeInformation.InstanceName = newInstanceName;
return SendCommand(new TopicParameters(newInstanceName), cancellationToken);
}
@@ -667,7 +666,7 @@ namespace Tgstation.Server.Host.Components.Session
var reattachResponse = await SendCommand(
new TopicParameters(
assemblyInformationProvider.Version,
reattachInformation.RuntimeInformation.ServerPort),
ReattachInformation.RuntimeInformation.ServerPort),
reattachTopicCts.Token)
.ConfigureAwait(false);
@@ -72,12 +72,7 @@ namespace Tgstation.Server.Host.Components.Session
logger.LogDebug("Saving reattach information: {0}...", reattachInformation);
await db
.ReattachInformations
.AsQueryable()
.Where(x => x.CompileJob.Job.Instance.Id == metadata.Id)
.DeleteAsync(cancellationToken)
.ConfigureAwait(false);
await ClearImpl(db, false, cancellationToken).ConfigureAwait(false);
var dbReattachInfo = new Models.ReattachInformation
{
@@ -129,23 +124,25 @@ namespace Tgstation.Server.Host.Components.Session
bool first = true;
foreach (var reattachInfo in dbReattachInfos)
{
if (!first)
if (first)
{
logger.LogWarning("Killing PID {0} associated with extra reattach information...", reattachInfo.ProcessId);
try
{
using var process = processExecutor.GetProcess(reattachInfo.ProcessId);
process.Terminate();
await process.Lifetime.ConfigureAwait(false);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to kill process!");
}
first = false;
continue;
}
logger.LogWarning("Killing PID {0} associated with extra reattach information...", reattachInfo.ProcessId);
try
{
using var process = processExecutor.GetProcess(reattachInfo.ProcessId);
process.Terminate();
await process.Lifetime.ConfigureAwait(false);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to kill process!");
}
db.ReattachInformations.Remove(reattachInfo);
first = false;
}
await db.Save(cancellationToken).ConfigureAwait(false);
@@ -173,5 +170,40 @@ namespace Tgstation.Server.Host.Components.Session
return info;
}
/// <inheritdoc />
public Task Clear(CancellationToken cancellationToken) => databaseContextFactory
.UseContext(
db =>
{
logger.LogDebug("Clearing reattach information");
return ClearImpl(db, true, cancellationToken);
});
/// <summary>
/// Clear any stored <see cref="ReattachInformation"/>.
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to use.</param>
/// <param name="instant">If an SQL DELETE WHERE command should be used rather than an Entity Framework transaction.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task ClearImpl(IDatabaseContext databaseContext, bool instant, CancellationToken cancellationToken)
{
var baseQuery = databaseContext
.ReattachInformations
.AsQueryable()
.Where(x => x.CompileJob.Job.Instance.Id == metadata.Id);
if (instant)
await baseQuery
.DeleteAsync(cancellationToken)
.ConfigureAwait(false);
else
{
var results = await baseQuery.ToListAsync(cancellationToken).ConfigureAwait(false);
foreach (var result in results)
databaseContext.ReattachInformations.Remove(result);
}
}
}
}
@@ -199,7 +199,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
protected sealed override ISessionController GetActiveController() => Server;
/// <inheritdoc />
protected override async Task InitControllers(
protected override async Task InitController(
Task chatTask,
ReattachInformation reattachInfo,
CancellationToken cancellationToken)
@@ -249,6 +249,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
Server.SetHighPriority();
if (!reattachInProgress)
await SessionPersistor.Save(Server.ReattachInformation, cancellationToken).ConfigureAwait(false);
await CheckLaunchResult(Server, "Server", cancellationToken).ConfigureAwait(false);
Server.EnableCustomChatCommands();
@@ -105,11 +105,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <inheritdoc />
protected override async Task InitControllers(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken)
protected override async Task InitController(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken)
{
try
{
await base.InitControllers(chatTask, reattachInfo, cancellationToken).ConfigureAwait(false);
await base.InitController(chatTask, reattachInfo, cancellationToken).ConfigureAwait(false);
}
finally
{
@@ -60,6 +60,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
protected TaskCompletionSource<object> ActiveParametersUpdated { get; set; }
/// <summary>
/// The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>.
/// </summary>
protected ISessionPersistor SessionPersistor { get; }
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.
/// </summary>
@@ -105,11 +110,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
readonly SemaphoreSlim controllerDisposeSemaphore;
/// <summary>
/// The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>.
/// </summary>
readonly ISessionPersistor sessionPersistor;
/// <summary>
/// The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.
/// </summary>
@@ -135,11 +135,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
readonly bool autoStart;
/// <summary>
/// Used when detaching servers.
/// </summary>
ReattachInformation releasedReattachInformation;
/// <summary>
/// The <see cref="CancellationTokenSource"/> for the monitor loop.
/// </summary>
@@ -176,7 +171,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="chat">The value of <see cref="Chat"/>.</param>
/// <param name="sessionControllerFactory">The value of <see cref="SessionControllerFactory"/>.</param>
/// <param name="dmbFactory">The value of <see cref="DmbFactory"/>.</param>
/// <param name="sessionPersistor">The value of <see cref="sessionPersistor"/>.</param>
/// <param name="sessionPersistor">The value of <see cref="SessionPersistor"/>.</param>
/// <param name="jobManager">The value of <see cref="jobManager"/>.</param>
/// <param name="serverControl">The <see cref="IServerControl"/> to populate <see cref="restartRegistration"/> with.</param>
/// <param name="asyncDelayer">The value of <see cref="AsyncDelayer"/>.</param>
@@ -206,7 +201,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
SessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
DmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
this.sessionPersistor = sessionPersistor ?? throw new ArgumentNullException(nameof(sessionPersistor));
SessionPersistor = sessionPersistor ?? throw new ArgumentNullException(nameof(sessionPersistor));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.diagnosticsIOManager = diagnosticsIOManager ?? throw new ArgumentNullException(nameof(diagnosticsIOManager));
@@ -347,7 +342,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
var reattachInfo = await sessionPersistor.Load(cancellationToken).ConfigureAwait(false);
var reattachInfo = await SessionPersistor.Load(cancellationToken).ConfigureAwait(false);
if (!autoStart && reattachInfo == null)
return;
@@ -375,26 +370,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <inheritdoc />
public async Task StopAsync(CancellationToken cancellationToken)
{
await TerminateNoLock(false, !releaseServers, cancellationToken).ConfigureAwait(false);
if (releasedReattachInformation != null)
{
try
{
await sessionPersistor.Save(releasedReattachInformation, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogCritical(
ex,
"Failed to persist session reattach information! To repair this, DreamDaemon will need to be manully stopped and then relaunched with TGS.");
}
releasedReattachInformation = null;
releaseServers = false;
}
}
public Task StopAsync(CancellationToken cancellationToken) =>
TerminateNoLock(false, !releaseServers, cancellationToken);
/// <inheritdoc />
public async Task Terminate(bool graceful, CancellationToken cancellationToken)
@@ -477,7 +454,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="reattachInfo"><see cref="ReattachInformation"/> to use, if any.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
protected abstract Task InitControllers(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken);
protected abstract Task InitController(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken);
/// <summary>
/// Launches the watchdog.
@@ -525,11 +502,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
try
{
await InitControllers(announceTask, reattachInfo, cancellationToken).ConfigureAwait(false);
await InitController(announceTask, reattachInfo, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
catch (OperationCanceledException ex)
{
Logger.LogTrace("Controller initialization canceled!");
Logger.LogTrace(ex, "Controller initialization cancelled!");
throw;
}
catch (Exception e)
@@ -610,7 +587,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <summary>
/// Call from <see cref="InitControllers(Task, ReattachInformation, CancellationToken)"/> when a reattach operation fails to attempt a fresh start.
/// Call from <see cref="InitController(Task, ReattachInformation, CancellationToken)"/> when a reattach operation fails to attempt a fresh start.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
@@ -623,7 +600,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
Logger.LogWarning(FailReattachMessage);
var chatTask = Chat.QueueWatchdogMessage(FailReattachMessage, cancellationToken);
await InitControllers(chatTask, null, cancellationToken).ConfigureAwait(false);
await InitController(chatTask, null, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@@ -641,7 +618,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
Logger.LogTrace("DisposeAndNullControllers");
using (await SemaphoreSlimContext.Lock(controllerDisposeSemaphore, cancellationToken).ConfigureAwait(false))
{
await DisposeAndNullControllersImpl().ConfigureAwait(false);
if (!releaseServers)
await SessionPersistor.Clear(cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
@@ -931,8 +912,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (releaseServers)
{
Logger.LogTrace("Detaching servers...");
releasedReattachInformation = await GetActiveController().Release().ConfigureAwait(false);
Logger.LogTrace("Detaching server...");
var controller = GetActiveController();
await controller.Release().ConfigureAwait(false);
}
}