mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-24 01:14:12 +00:00
Add support for separate shard ranges per instance
-New config values ShardRange and ShardTotal --ShardTotal replaces ShardCount -New config value QuitOnFails: --Program quits after enough shards have been removed
This commit is contained in:
parent
88c18c4364
commit
eb33e55aad
4 changed files with 79 additions and 37 deletions
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<Version>3.0.3</Version>
|
<Version>3.1.0</Version>
|
||||||
<PackageId>BirthdayBot</PackageId>
|
<PackageId>BirthdayBot</PackageId>
|
||||||
<Authors>NoiTheCat</Authors>
|
<Authors>NoiTheCat</Authors>
|
||||||
<Product>BirthdayBot</Product>
|
<Product>BirthdayBot</Product>
|
||||||
|
@ -20,9 +20,9 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Discord.Net" Version="2.4.0" />
|
<PackageReference Include="Discord.Net" Version="2.4.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="NodaTime" Version="3.0.3" />
|
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||||
<PackageReference Include="Npgsql" Version="4.1.5" />
|
<PackageReference Include="Npgsql" Version="5.0.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Npgsql;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace BirthdayBot
|
namespace BirthdayBot
|
||||||
{
|
{
|
||||||
|
@ -15,7 +16,14 @@ namespace BirthdayBot
|
||||||
public string BotToken { get; }
|
public string BotToken { get; }
|
||||||
public string LogWebhook { get; }
|
public string LogWebhook { get; }
|
||||||
public string DBotsToken { get; }
|
public string DBotsToken { get; }
|
||||||
public int ShardCount { get; }
|
|
||||||
|
public const string ShardLenConfKey = "ShardRange";
|
||||||
|
public int ShardStart { get; }
|
||||||
|
public int ShardAmount { get; }
|
||||||
|
|
||||||
|
public int ShardTotal { get; }
|
||||||
|
|
||||||
|
public bool QuitOnFails { get; }
|
||||||
|
|
||||||
public Configuration()
|
public Configuration()
|
||||||
{
|
{
|
||||||
|
@ -31,15 +39,15 @@ namespace BirthdayBot
|
||||||
|
|
||||||
var jc = JObject.Parse(File.ReadAllText(confPath));
|
var jc = JObject.Parse(File.ReadAllText(confPath));
|
||||||
|
|
||||||
BotToken = jc["BotToken"]?.Value<string>();
|
BotToken = jc[nameof(BotToken)]?.Value<string>();
|
||||||
if (string.IsNullOrWhiteSpace(BotToken))
|
if (string.IsNullOrWhiteSpace(BotToken))
|
||||||
throw new Exception("'BotToken' must be specified.");
|
throw new Exception($"'{nameof(BotToken)}' must be specified.");
|
||||||
|
|
||||||
LogWebhook = jc["LogWebhook"]?.Value<string>();
|
LogWebhook = jc[nameof(LogWebhook)]?.Value<string>();
|
||||||
if (string.IsNullOrWhiteSpace(LogWebhook))
|
if (string.IsNullOrWhiteSpace(LogWebhook))
|
||||||
throw new Exception("'LogWebhook' must be specified.");
|
throw new Exception($"'{nameof(LogWebhook)}' must be specified.");
|
||||||
|
|
||||||
var dbj = jc["DBotsToken"];
|
var dbj = jc[nameof(DBotsToken)];
|
||||||
if (dbj != null)
|
if (dbj != null)
|
||||||
{
|
{
|
||||||
DBotsToken = dbj.Value<string>();
|
DBotsToken = dbj.Value<string>();
|
||||||
|
@ -64,16 +72,41 @@ namespace BirthdayBot
|
||||||
if (sqldb != null) csb.Database = sqldb; // Optional database setting
|
if (sqldb != null) csb.Database = sqldb; // Optional database setting
|
||||||
Database.DBConnectionString = csb.ToString();
|
Database.DBConnectionString = csb.ToString();
|
||||||
|
|
||||||
int? sc = jc["ShardCount"]?.Value<int>();
|
int? sc = jc[nameof(ShardTotal)]?.Value<int>();
|
||||||
if (!sc.HasValue) ShardCount = 1;
|
if (!sc.HasValue) ShardTotal = 1;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ShardCount = sc.Value;
|
ShardTotal = sc.Value;
|
||||||
if (ShardCount <= 0)
|
if (ShardTotal <= 0)
|
||||||
{
|
{
|
||||||
throw new Exception("'ShardCount' must be a positive integer.");
|
throw new Exception($"'{nameof(ShardTotal)}' must be a positive integer.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string srVal = jc[ShardLenConfKey]?.Value<string>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(srVal))
|
||||||
|
{
|
||||||
|
Regex srPicker = new Regex(@"(?<low>\d{1,2})[-,]{1}(?<high>\d{1,2})");
|
||||||
|
var m = srPicker.Match(srVal);
|
||||||
|
if (m.Success)
|
||||||
|
{
|
||||||
|
ShardStart = int.Parse(m.Groups["low"].Value);
|
||||||
|
int high = int.Parse(m.Groups["high"].Value);
|
||||||
|
ShardAmount = high - (ShardStart - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Shard range not properly formatted in '{ShardLenConfKey}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default: this instance handles all shards from ShardTotal
|
||||||
|
ShardStart = 0;
|
||||||
|
ShardAmount = ShardTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuitOnFails = jc[nameof(QuitOnFails)]?.Value<bool>() ?? false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace BirthdayBot
|
||||||
private static void OnCancelKeyPressed(object sender, ConsoleCancelEventArgs e)
|
private static void OnCancelKeyPressed(object sender, ConsoleCancelEventArgs e)
|
||||||
{
|
{
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
|
Log("Shutdown", "Captured cancel key; sending shutdown.");
|
||||||
ProgramStop();
|
ProgramStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ namespace BirthdayBot
|
||||||
{
|
{
|
||||||
if (_stopping) return;
|
if (_stopping) return;
|
||||||
_stopping = true;
|
_stopping = true;
|
||||||
|
Log("Shutdown", "Commencing shutdown...");
|
||||||
|
|
||||||
var dispose = Task.Run(_bot.Dispose);
|
var dispose = Task.Run(_bot.Dispose);
|
||||||
if (!dispose.Wait(90000))
|
if (!dispose.Wait(90000))
|
||||||
|
|
|
@ -29,6 +29,12 @@ namespace BirthdayBot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int MaxDestroyedShards = 5;
|
private const int MaxDestroyedShards = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amount of time without a completed background service run before a shard instance
|
||||||
|
/// is considered "dead" and tasked to be removed.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly TimeSpan DeadShardThreshold = new TimeSpan(0, 20, 0);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dictionary with shard IDs as its keys and shard instances as its values.
|
/// A dictionary with shard IDs as its keys and shard instances as its values.
|
||||||
/// When initialized, all keys will be created as configured. If an instance is removed,
|
/// When initialized, all keys will be created as configured. If an instance is removed,
|
||||||
|
@ -70,8 +76,8 @@ namespace BirthdayBot
|
||||||
foreach (var item in _cmdsMods.Commands) _dispatchCommands.Add(item.Item1, item.Item2);
|
foreach (var item in _cmdsMods.Commands) _dispatchCommands.Add(item.Item1, item.Item2);
|
||||||
|
|
||||||
_shards = new Dictionary<int, ShardInstance>();
|
_shards = new Dictionary<int, ShardInstance>();
|
||||||
// TODO implement more flexible sharding configuration here
|
// Create only the specified shards as needed by this instance
|
||||||
for (int i = 0; i < Config.ShardCount; i++)
|
for (int i = Config.ShardStart; i < (Config.ShardStart + Config.ShardAmount); i++)
|
||||||
{
|
{
|
||||||
_shards.Add(i, null);
|
_shards.Add(i, null);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +90,6 @@ namespace BirthdayBot
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Log("Captured cancel key. Shutting down shard status watcher...");
|
|
||||||
_watchdogCancel.Cancel();
|
_watchdogCancel.Cancel();
|
||||||
_watchdogTask.Wait(5000);
|
_watchdogTask.Wait(5000);
|
||||||
if (!_watchdogTask.IsCompleted)
|
if (!_watchdogTask.IsCompleted)
|
||||||
|
@ -117,7 +122,7 @@ namespace BirthdayBot
|
||||||
var clientConf = new DiscordSocketConfig()
|
var clientConf = new DiscordSocketConfig()
|
||||||
{
|
{
|
||||||
ShardId = shardId,
|
ShardId = shardId,
|
||||||
TotalShards = Config.ShardCount,
|
TotalShards = Config.ShardTotal,
|
||||||
LogLevel = LogSeverity.Info,
|
LogLevel = LogSeverity.Info,
|
||||||
DefaultRetryMode = RetryMode.RetryRatelimit,
|
DefaultRetryMode = RetryMode.RetryRatelimit,
|
||||||
MessageCacheSize = 0, // not needed at all
|
MessageCacheSize = 0, // not needed at all
|
||||||
|
@ -142,7 +147,6 @@ namespace BirthdayBot
|
||||||
// Iterate through shard list, extract data
|
// Iterate through shard list, extract data
|
||||||
var guildInfo = new Dictionary<int, (int, int, TimeSpan)>();
|
var guildInfo = new Dictionary<int, (int, int, TimeSpan)>();
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
ulong? botId = null;
|
|
||||||
var nullShards = new List<int>();
|
var nullShards = new List<int>();
|
||||||
foreach (var item in _shards)
|
foreach (var item in _shards)
|
||||||
{
|
{
|
||||||
|
@ -152,7 +156,6 @@ namespace BirthdayBot
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var shard = item.Value;
|
var shard = item.Value;
|
||||||
botId ??= shard.DiscordClient.CurrentUser?.Id;
|
|
||||||
|
|
||||||
var guildCount = shard.DiscordClient.Guilds.Count;
|
var guildCount = shard.DiscordClient.Guilds.Count;
|
||||||
var connScore = shard.ConnectionScore;
|
var connScore = shard.ConnectionScore;
|
||||||
|
@ -181,7 +184,7 @@ namespace BirthdayBot
|
||||||
badShards.Add(item.Key);
|
badShards.Add(item.Key);
|
||||||
|
|
||||||
// Consider a shard dead after a long span without background activity
|
// Consider a shard dead after a long span without background activity
|
||||||
if (lastRun > new TimeSpan(0, 30, 0))
|
if (lastRun > DeadShardThreshold)
|
||||||
deadShards.Add(item.Key);
|
deadShards.Add(item.Key);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -199,9 +202,8 @@ namespace BirthdayBot
|
||||||
if (detailedInfo)
|
if (detailedInfo)
|
||||||
{
|
{
|
||||||
result.Remove(result.Length - 1, 1);
|
result.Remove(result.Length - 1, 1);
|
||||||
result.Append($"[{guildInfo[item].Item2:+000;-000}");
|
result.Append($"[{guildInfo[item].Item2:+0;-0}");
|
||||||
result.Append($" {Math.Floor(guildInfo[item].Item3.TotalMinutes):00}m");
|
result.Append($" {Math.Floor(guildInfo[item].Item3.TotalSeconds):000}s] ");
|
||||||
result.Append($"{guildInfo[item].Item3.Seconds:00}s] ");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result.Length > 0) result.Remove(result.Length - 1, 1);
|
if (result.Length > 0) result.Remove(result.Length - 1, 1);
|
||||||
|
@ -213,14 +215,18 @@ namespace BirthdayBot
|
||||||
if (nullShards.Count > 0) Log("Inactive shards: " + statusDisplay(nullShards, false));
|
if (nullShards.Count > 0) Log("Inactive shards: " + statusDisplay(nullShards, false));
|
||||||
|
|
||||||
// Remove dead shards
|
// Remove dead shards
|
||||||
foreach (var dead in deadShards)
|
foreach (var dead in deadShards) {
|
||||||
{
|
// TODO investigate - has this been hanging here?
|
||||||
_shards[dead].Dispose();
|
_shards[dead].Dispose();
|
||||||
_shards[dead] = null;
|
_shards[dead] = null;
|
||||||
_destroyedShards++;
|
_destroyedShards++;
|
||||||
}
|
}
|
||||||
if (_destroyedShards > MaxDestroyedShards) Program.ProgramStop();
|
if (Config.QuitOnFails && _destroyedShards > MaxDestroyedShards)
|
||||||
|
{
|
||||||
|
Program.ProgramStop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Start up any missing shards
|
// Start up any missing shards
|
||||||
int startAllowance = 4;
|
int startAllowance = 4;
|
||||||
foreach (var id in nullShards)
|
foreach (var id in nullShards)
|
||||||
|
@ -233,6 +239,7 @@ namespace BirthdayBot
|
||||||
}
|
}
|
||||||
else break;
|
else break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// All done for now
|
// All done for now
|
||||||
await Task.Delay(WatchdogInterval * 1000, _watchdogCancel.Token).ConfigureAwait(false);
|
await Task.Delay(WatchdogInterval * 1000, _watchdogCancel.Token).ConfigureAwait(false);
|
||||||
|
|
Loading…
Reference in a new issue