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);
}