diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index 946613c1b2..5b64376bf5 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -28,5 +28,11 @@ namespace Tgstation.Server.Api.Models /// [Required] public bool? IsWatchdogChannel { get; set; } + + /// + /// If the is an updates channel + /// + [Required] + public bool? IsUpdatesChannel { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs index 5185f033cc..a1d205e6d0 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs @@ -20,6 +20,11 @@ /// public bool IsWatchdogChannel { get; set; } + /// + /// If the is an updates channel + /// + public bool IsUpdatesChannel { get; set; } + /// /// The with the mapped Id /// diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 72f8c2cd78..f1ee4a7a82 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -342,6 +342,7 @@ namespace Tgstation.Server.Host.Components.Chat var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping { IsWatchdogChannel = x.IsWatchdogChannel == true, + IsUpdatesChannel = x.IsUpdatesChannel == true, ProviderChannelId = y.RealId, ProviderId = connectionId, Channel = y @@ -456,11 +457,21 @@ namespace Tgstation.Server.Host.Components.Chat public Task SendWatchdogMessage(string message, CancellationToken cancellationToken) { List wdChannels; + message = String.Format(CultureInfo.InvariantCulture, "Watchdog: {0}", message); lock (mappedChannels) //so it doesn't change while we're using it wdChannels = mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList(); return SendMessage(message, wdChannels, cancellationToken); } + /// + public Task SendUpdateMessage(string message, CancellationToken cancellationToken) + { + List wdChannels; + lock (mappedChannels) //so it doesn't change while we're using it + wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList(); + return SendMessage(message, wdChannels, cancellationToken); + } + /// public async Task StartAsync(CancellationToken cancellationToken) { @@ -532,5 +543,14 @@ namespace Tgstation.Server.Host.Components.Chat provider.Dispose(); } } + + /// + public Task SendBroadcast(string message, CancellationToken cancellationToken) + { + List wdChannels; + lock (mappedChannels) //so it doesn't change while we're using it + wdChannels = mappedChannels.Select(x => x.Key).ToList(); + return SendMessage(message, wdChannels, cancellationToken); + } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/IChat.cs b/src/Tgstation.Server.Host/Components/Chat/IChat.cs index 15e6e939bc..114669bbca 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChat.cs @@ -67,6 +67,22 @@ namespace Tgstation.Server.Host.Components.Chat /// A representing the running operation Task SendWatchdogMessage(string message, CancellationToken cancellationToken); + /// + /// Send a chat to configured update channels + /// + /// The message being sent + /// The for the operation + /// A representing the running operation + Task SendUpdateMessage(string message, CancellationToken cancellationToken); + + /// + /// Send a chat to all channels + /// + /// The message being sent + /// The for the operation + /// A representing the running operation + Task SendBroadcast(string message, CancellationToken cancellationToken); + /// /// Start tracking json files for commands and channels /// diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 46b39b8e8c..ef7fa6019c 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -56,6 +56,16 @@ namespace Tgstation.Server.Host.Components /// readonly Dictionary interopConsumers; + /// + /// of s to finish in + /// + readonly List shutdownTasks; + + /// + /// Used as a temporary for + /// + readonly CancellationTokenSource shutdownCancellationTokenSource; + /// /// Construct an /// @@ -65,17 +75,30 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, ILogger logger) + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger 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)); + + 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(); interopConsumers = new Dictionary(); + shutdownTasks = new List(); + shutdownCancellationTokenSource = new CancellationTokenSource(); } /// @@ -83,6 +106,7 @@ namespace Tgstation.Server.Host.Components { foreach (var I in instances) I.Value.Dispose(); + shutdownCancellationTokenSource.Dispose(); } /// @@ -191,6 +215,8 @@ namespace Tgstation.Server.Host.Components /// public async Task StopAsync(CancellationToken cancellationToken) { + using (cancellationToken.Register(() => shutdownCancellationTokenSource.Cancel())) + await Task.WhenAll(shutdownTasks).ConfigureAwait(false); await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); await jobManager.StopAsync(cancellationToken).ConfigureAwait(false); }