mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-30 00:22:40 +01:00
Merge pull request #696 from tgstation/688-FixChatBotRestart [NugetDeploy]
Clean up restart handling
This commit is contained in:
@@ -9,12 +9,13 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Chat.Commands;
|
||||
using Tgstation.Server.Host.Components.Chat.Providers;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class Chat : IChat
|
||||
sealed class Chat : IChat, IRestartHandler
|
||||
{
|
||||
const string CommonMention = "!tgs";
|
||||
|
||||
@@ -33,6 +34,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// </summary>
|
||||
readonly ICommandFactory commandFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IRestartRegistration"/> for the <see cref="Chat"/>
|
||||
/// </summary>
|
||||
readonly IRestartRegistration restartRegistration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="Chat"/>
|
||||
/// </summary>
|
||||
@@ -100,15 +106,20 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="commandFactory">The value of <see cref="commandFactory"/></param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> to populate <see cref="restartRegistration"/> with</param>
|
||||
/// <param name="initialChatBots">The <see cref="IEnumerable{T}"/> used to populate <see cref="initialChatBots"/></param>
|
||||
public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, ILogger<Chat> logger, IEnumerable<Models.ChatBot> initialChatBots)
|
||||
public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, ILogger<Chat> logger, IEnumerable<Models.ChatBot> initialChatBots)
|
||||
{
|
||||
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
|
||||
if (serverControl == null)
|
||||
throw new ArgumentNullException(nameof(serverControl));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.initialChatBots = initialChatBots?.ToList() ?? throw new ArgumentNullException(nameof(initialChatBots));
|
||||
|
||||
restartRegistration = serverControl.RegisterForRestart(this);
|
||||
|
||||
builtinCommands = new Dictionary<string, ICommand>();
|
||||
providers = new Dictionary<long, IProvider>();
|
||||
mappedChannels = new Dictionary<ulong, ChannelMapping>();
|
||||
@@ -121,6 +132,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
restartRegistration.Dispose();
|
||||
handlerCts.Dispose();
|
||||
foreach (var I in providers)
|
||||
I.Value.Dispose();
|
||||
@@ -563,8 +575,9 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendBroadcast(string message, CancellationToken cancellationToken)
|
||||
public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
|
||||
{
|
||||
var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion);
|
||||
List<ulong> wdChannels;
|
||||
lock (mappedChannels) //so it doesn't change while we're using it
|
||||
wdChannels = mappedChannels.Select(x => x.Key).ToList();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Host.Components.Chat.Commands;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat
|
||||
@@ -9,42 +10,35 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <inheritdoc />
|
||||
sealed class ChatFactory : IChatFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="ChatFactory"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> for the <see cref="ChatFactory"/>
|
||||
/// </summary>
|
||||
readonly ILoggerFactory loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICommandFactory"/> for the <see cref="ChatFactory"/>
|
||||
/// </summary>
|
||||
readonly ICommandFactory commandFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProviderFactory"/> for the <see cref="ChatFactory"/>
|
||||
/// </summary>
|
||||
readonly IProviderFactory providerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="ChatFactory"/>
|
||||
/// </summary>
|
||||
readonly IServerControl serverControl;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ChatFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
/// <param name="commandFactory">The value of <see cref="commandFactory"/></param>
|
||||
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
|
||||
public ChatFactory(IIOManager ioManager, ILoggerFactory loggerFactory, ICommandFactory commandFactory, IProviderFactory providerFactory)
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
|
||||
public ChatFactory(ILoggerFactory loggerFactory, IProviderFactory providerFactory, IServerControl serverControl)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
|
||||
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChat CreateChat(IEnumerable<Models.ChatBot> initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, loggerFactory.CreateLogger<Chat>(), initialChatBots);
|
||||
public IChat CreateChat(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable<Models.ChatBot> initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, serverControl, loggerFactory.CreateLogger<Chat>(), initialChatBots);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,14 +75,6 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SendUpdateMessage(string message, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat to all channels
|
||||
/// </summary>
|
||||
/// <param name="message">The message being sent</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SendBroadcast(string message, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Start tracking json files for commands and channels
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Host.Components.Chat.Commands;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
@@ -10,8 +12,10 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <summary>
|
||||
/// Create a <see cref="IChat"/>
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="IChat"/></param>
|
||||
/// <param name="commandFactory">The <see cref="ICommandFactory"/> for the <see cref="IChat"/></param>
|
||||
/// <param name="initialChatBots">The initial <see cref="Models.ChatBot"/> for the <see cref="IChat"/></param>
|
||||
/// <returns>A new <see cref="IChat"/></returns>
|
||||
IChat CreateChat(IEnumerable<Models.ChatBot> initialChatBots);
|
||||
IChat CreateChat(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable<Models.ChatBot> initialChatBots);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly IByondTopicSender byondTopicSender;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
readonly IServerControl serverUpdater;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICryptographySuite"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
@@ -71,7 +66,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// The <see cref="IProviderFactory"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
readonly IProviderFactory providerFactory;
|
||||
readonly IChatFactory chatFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessExecutor"/> for the <see cref="InstanceFactory"/>
|
||||
@@ -106,30 +101,28 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="application">The value of <see cref="application"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
|
||||
/// <param name="serverUpdater">The value of <see cref="serverUpdater"/></param>
|
||||
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
|
||||
/// <param name="synchronousIOManager">The value of <see cref="synchronousIOManager"/></param>
|
||||
/// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/></param>
|
||||
/// <param name="byondInstaller">The value of <see cref="byondInstaller"/></param>
|
||||
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
|
||||
/// <param name="chatFactory">The value of <see cref="chatFactory"/></param>
|
||||
/// <param name="processExecutor">The value of <see cref="processExecutor"/></param>
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/></param>
|
||||
/// <param name="watchdogFactory">The value of <see cref="watchdogFactory"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider)
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.application = application ?? throw new ArgumentNullException(nameof(application));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
|
||||
this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater));
|
||||
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
|
||||
this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager));
|
||||
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
|
||||
this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller));
|
||||
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
|
||||
this.chatFactory = chatFactory ?? throw new ArgumentNullException(nameof(chatFactory));
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
|
||||
this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory));
|
||||
@@ -161,9 +154,8 @@ namespace Tgstation.Server.Host.Components
|
||||
var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger<ByondManager>());
|
||||
|
||||
var commandFactory = new CommandFactory(application, byond, repoManager, databaseContextFactory, metadata);
|
||||
var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory, providerFactory);
|
||||
|
||||
var chat = chatFactory.CreateChat(metadata.ChatSettings);
|
||||
var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings);
|
||||
try
|
||||
{
|
||||
var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata());
|
||||
|
||||
@@ -49,16 +49,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly Dictionary<long, IInstance> instances;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="List{T}"/> of <see cref="Task"/>s to finish in <see cref="StopAsync(CancellationToken)"/>
|
||||
/// </summary>
|
||||
readonly List<Task> shutdownTasks;
|
||||
|
||||
/// <summary>
|
||||
/// Used as a temporary <see cref="CancellationTokenSource"/> for <see cref="shutdownTasks"/>
|
||||
/// </summary>
|
||||
readonly CancellationTokenSource shutdownCancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
@@ -67,31 +57,17 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="databaseContextFactory">The value of <paramref name="databaseContextFactory"/></param>
|
||||
/// <param name="application">The value of <see cref="application"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="InstanceManager"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger<InstanceManager> logger)
|
||||
public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, ILogger<InstanceManager> logger)
|
||||
{
|
||||
this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.application = application ?? throw new ArgumentNullException(nameof(application));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
|
||||
if (serverControl == null)
|
||||
throw new ArgumentNullException(nameof(serverControl));
|
||||
|
||||
shutdownCancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = shutdownCancellationTokenSource.Token;
|
||||
serverControl.RegisterForRestart(() =>
|
||||
{
|
||||
lock (this)
|
||||
shutdownTasks.AddRange(instances.Select(x => x.Value.Chat.SendBroadcast("TGS: Restart requested...", cancellationToken)));
|
||||
});
|
||||
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
instances = new Dictionary<long, IInstance>();
|
||||
shutdownTasks = new List<Task>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -99,7 +75,6 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
foreach (var I in instances)
|
||||
I.Value.Dispose();
|
||||
shutdownCancellationTokenSource.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -237,11 +212,7 @@ namespace Tgstation.Server.Host.Components
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await jobManager.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using (cancellationToken.Register(() => shutdownCancellationTokenSource.Cancel()))
|
||||
await Task.WhenAll(shutdownTasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ using Tgstation.Server.Host.Core;
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class Watchdog : IWatchdog, ICustomCommandHandler
|
||||
sealed class Watchdog : IWatchdog, ICustomCommandHandler, IRestartHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The time in seconds to wait from starting <see cref="alphaServer"/> to start <see cref="bravoServer"/>. Does not take responsiveness into account
|
||||
@@ -93,6 +93,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IRestartRegistration"/> for the <see cref="Watchdog"/>
|
||||
/// </summary>
|
||||
readonly IRestartRegistration restartRegistration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> for the <see cref="Watchdog"/>
|
||||
/// </summary>
|
||||
@@ -143,17 +148,17 @@ 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="serverUpdater">The <see cref="IServerControl"/> for the <see cref="Watchdog"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="reattachInfoHandler">The value of <see cref="reattachInfoHandler"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></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="initialLaunchParameters">The initial value of <see cref="ActiveLaunchParameters"/>. May be modified</param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
/// <param name="autoStart">The value of <see cref="autoStart"/></param>
|
||||
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger<Watchdog> logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
|
||||
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ILogger<Watchdog> logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
|
||||
{
|
||||
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
|
||||
@@ -167,10 +172,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
this.autoStart = autoStart;
|
||||
|
||||
if (serverUpdater == null)
|
||||
throw new ArgumentNullException(nameof(serverUpdater));
|
||||
if (serverControl == null)
|
||||
throw new ArgumentNullException(nameof(serverControl));
|
||||
|
||||
serverUpdater.RegisterForRestart(() => releaseServers = true);
|
||||
restartRegistration = serverControl.RegisterForRestart(this);
|
||||
|
||||
chat.RegisterCommandHandler(this);
|
||||
|
||||
@@ -186,6 +191,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
DisposeAndNullControllers();
|
||||
semaphore.Dispose();
|
||||
restartRegistration.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -641,7 +647,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (startMonitor && await StopMonitor().ConfigureAwait(false))
|
||||
chatTask = chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", cancellationToken);
|
||||
else if (announce)
|
||||
chatTask = chat.SendWatchdogMessage("Starting...", cancellationToken);
|
||||
chatTask = chat.SendWatchdogMessage(doReattach ? "Reattaching..." : "Starting...", cancellationToken);
|
||||
else
|
||||
chatTask = Task.CompletedTask;
|
||||
//start both servers
|
||||
@@ -925,5 +931,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
return await activeServer.SendCommand(command, cancellationToken).ConfigureAwait(false) ?? "ERROR: Bad topic exchange!";
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
|
||||
{
|
||||
releaseServers = true;
|
||||
if (Running)
|
||||
await chat.SendWatchdogMessage("Detaching...", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IServerControl serverUpdater;
|
||||
readonly IServerControl serverControl;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> for the <see cref="WatchdogFactory"/>
|
||||
@@ -39,14 +39,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// Construct a <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="serverUpdater">The value of <see cref="serverUpdater"/></param>
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
public WatchdogFactory(IServerControl serverUpdater, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager)
|
||||
public WatchdogFactory(IServerControl serverControl, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager)
|
||||
{
|
||||
this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater));
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
|
||||
@@ -54,6 +54,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger<Watchdog>(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, settings, instance, settings.AutoStart.Value);
|
||||
public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, loggerFactory.CreateLogger<Watchdog>(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, settings, instance, settings.AutoStart.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("Extracting server update...");
|
||||
if (!await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false))
|
||||
if (!await serverUpdater.ApplyUpdate(version, assetBytes, ioManager, cancellationToken).ConfigureAwait(false))
|
||||
return UnprocessableEntity(new ErrorMessage
|
||||
{
|
||||
Message = RestartNotSupportedException
|
||||
@@ -201,23 +201,23 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <inheritdoc />
|
||||
[HttpDelete]
|
||||
[TgsAuthorize(AdministrationRights.RestartHost)]
|
||||
public Task<IActionResult> Delete()
|
||||
public async Task<IActionResult> Delete()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = serverUpdater.Restart();
|
||||
var result = await serverUpdater.Restart().ConfigureAwait(false);
|
||||
if (result)
|
||||
Logger.LogInformation("Restarting host by request...");
|
||||
else
|
||||
Logger.LogDebug("Restart request failed due to lack of host watchdog!");
|
||||
return Task.FromResult(result ? (IActionResult)Ok() : UnprocessableEntity(new ErrorMessage
|
||||
return result ? (IActionResult)Ok() : UnprocessableEntity(new ErrorMessage
|
||||
{
|
||||
Message = RestartNotSupportedException
|
||||
}));
|
||||
});
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return Task.FromResult<IActionResult>(StatusCode((int)HttpStatusCode.ServiceUnavailable));
|
||||
return StatusCode((int)HttpStatusCode.ServiceUnavailable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +215,7 @@ namespace Tgstation.Server.Host.Core
|
||||
SendTimeout = 5000
|
||||
});
|
||||
|
||||
services.AddSingleton<IChatFactory, ChatFactory>();
|
||||
services.AddSingleton<IWatchdogFactory, WatchdogFactory>();
|
||||
services.AddSingleton<IInstanceFactory, InstanceFactory>();
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler for server restarts
|
||||
/// </summary>
|
||||
public interface IRestartHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle a restart of the server
|
||||
/// </summary>
|
||||
/// <param name="updateVersion">The <see cref="Version"/> being updated to, <see langword="null"/> if not being changed</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task HandleRestart(Version updateVersion, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the lifetime of a <see cref="IRestartHandler"/> registration
|
||||
/// </summary>
|
||||
public interface IRestartRegistration : IDisposable
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -13,22 +13,24 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <summary>
|
||||
/// Run a new <see cref="Host"/> assembly and stop the current one. This will likely trigger all active <see cref="CancellationToken"/>s
|
||||
/// </summary>
|
||||
/// <param name="version">The <see cref="Version"/> the <see cref="IServerControl"/> is updating to</param>
|
||||
/// <param name="updateZipData">The <see cref="byte"/>s of the .zip file that contains the new <see cref="Host"/> assembly</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if live updates are supported, <see langword="false"/> otherwise</returns>
|
||||
Task<bool> ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken);
|
||||
Task<bool> ApplyUpdate(Version version, byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Register a given <paramref name="action"/> to run before stopping the server for a restart
|
||||
/// Register a given <paramref name="handler"/> to run before stopping the server for a restart
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to run</param>
|
||||
void RegisterForRestart(Action action);
|
||||
/// <param name="handler">The <see cref="IRestartHandler"/> to register</param>
|
||||
/// <returns>A new <see cref="IRestartRegistration"/> representing the scope of the registration</returns>
|
||||
IRestartRegistration RegisterForRestart(IRestartHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the <see cref="Host"/>
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> if live restarts are supported, <see langword="false"/> otherwise</returns>
|
||||
bool Restart();
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if live restarts are supported, <see langword="false"/> otherwise</returns>
|
||||
Task<bool> Restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using Tgstation.Server.Host.Core;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class RestartRegistration : IRestartRegistration
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Dispose"/> <see cref="Action"/>
|
||||
/// </summary>
|
||||
readonly Action onDispose;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="RestartRegistration"/>
|
||||
/// </summary>
|
||||
/// <param name="onDispose">The value of <see cref="onDispose"/></param>
|
||||
public RestartRegistration(Action onDispose)
|
||||
{
|
||||
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => onDispose();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
@@ -30,6 +32,11 @@ namespace Tgstation.Server.Host
|
||||
/// The absolute path to install updates to
|
||||
/// </summary>
|
||||
readonly string updatePath;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IRestartHandler"/>s to run when the <see cref="Server"/> restarts
|
||||
/// </summary>
|
||||
readonly List<IRestartHandler> restartHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// If a server update has been applied
|
||||
@@ -51,6 +58,7 @@ namespace Tgstation.Server.Host
|
||||
this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder));
|
||||
this.updatePath = updatePath;
|
||||
|
||||
restartHandlers = new List<IRestartHandler>();
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
updated = false;
|
||||
RestartRequested = false;
|
||||
@@ -85,8 +93,15 @@ namespace Tgstation.Server.Host
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken)
|
||||
public async Task<bool> ApplyUpdate(Version version, byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken)
|
||||
{
|
||||
if (version == null)
|
||||
throw new ArgumentNullException(nameof(version));
|
||||
if (updateZipData == null)
|
||||
throw new ArgumentNullException(nameof(updateZipData));
|
||||
if (ioManager == null)
|
||||
throw new ArgumentNullException(nameof(ioManager));
|
||||
|
||||
if (updatePath == null)
|
||||
return false;
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
@@ -98,44 +113,82 @@ namespace Tgstation.Server.Host
|
||||
{
|
||||
await ioManager.ZipToDirectory(updatePath, updateZipData, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
updated = false;
|
||||
try
|
||||
{
|
||||
//important to not leave this directory around if possible
|
||||
await ioManager.DeleteDirectory(updatePath, default).ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
updated = false;
|
||||
catch (Exception e2)
|
||||
{
|
||||
throw new AggregateException(e, e2);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
Restart();
|
||||
await Restart(version).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterForRestart(Action action)
|
||||
public IRestartRegistration RegisterForRestart(IRestartHandler handler)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
if (cancellationTokenSource == null)
|
||||
throw new InvalidOperationException("Tried to register an update action on a non-running Server!");
|
||||
cancellationTokenSource.Token.Register(() =>
|
||||
{
|
||||
if (RestartRequested)
|
||||
action();
|
||||
});
|
||||
lock (this)
|
||||
if (!RestartRequested)
|
||||
{
|
||||
restartHandlers.Add(handler);
|
||||
return new RestartRegistration(() =>
|
||||
{
|
||||
lock (this)
|
||||
if(!RestartRequested)
|
||||
restartHandlers.Remove(handler);
|
||||
});
|
||||
}
|
||||
return new RestartRegistration(() => { });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Restart()
|
||||
public Task<bool> Restart() => Restart(null);
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="Restart()"/>
|
||||
/// </summary>
|
||||
/// <param name="newVersion">The <see cref="Version"/> of any potential updates being applied</param>
|
||||
/// <returns></returns>
|
||||
async Task<bool> Restart(Version newVersion)
|
||||
{
|
||||
if (updatePath == null)
|
||||
return false;
|
||||
if (cancellationTokenSource == null)
|
||||
throw new InvalidOperationException("Tried to restart a non-running Server!");
|
||||
RestartRequested = true;
|
||||
lock (this)
|
||||
{
|
||||
if (RestartRequested)
|
||||
return true;
|
||||
RestartRequested = true;
|
||||
}
|
||||
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
var cancellationToken = cts.Token;
|
||||
var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)));
|
||||
//YA GOT 10 SECONDS
|
||||
var expiryTask = Task.Delay(TimeSpan.FromSeconds(10));
|
||||
await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false);
|
||||
cts.Cancel();
|
||||
try
|
||||
{
|
||||
await eventsTask.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
cancellationTokenSource.Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user