Shard coordinator is not indepentent from the shard 0
This commit is contained in:
parent
0bacb1f780
commit
db6fa9af1a
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
|
||||||
{
|
|
||||||
public class Shard0Precondition : PreconditionAttribute
|
|
||||||
{
|
|
||||||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
|
|
||||||
{
|
|
||||||
var c = (DiscordSocketClient)context.Client;
|
|
||||||
if (c.ShardId != 0)
|
|
||||||
return Task.FromResult(PreconditionResult.FromError("Must be ran from shard #0"));
|
|
||||||
|
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
using StackExchange.Redis;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.ShardCom
|
|
||||||
{
|
|
||||||
public class ShardComClient
|
|
||||||
{
|
|
||||||
private readonly IDataCache _cache;
|
|
||||||
|
|
||||||
public ShardComClient(IDataCache cache)
|
|
||||||
{
|
|
||||||
_cache = cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Send(ShardComMessage data)
|
|
||||||
{
|
|
||||||
var sub = _cache.Redis.GetSubscriber();
|
|
||||||
var msg = JsonConvert.SerializeObject(data);
|
|
||||||
|
|
||||||
await sub.PublishAsync("shardcoord_send", msg).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -56,8 +56,28 @@ namespace NadekoBot.Modules.Administration
|
|||||||
return;
|
return;
|
||||||
name = name.ToTitleCase();
|
name = name.ToTitleCase();
|
||||||
|
|
||||||
await _bot.LoadPackage(name);
|
if (await _bot.LoadPackage(name))
|
||||||
await ReplyAsync(":ok:");
|
await ReplyAsync(":ok:");
|
||||||
|
else
|
||||||
|
await ReplyAsync(":x:");
|
||||||
|
}
|
||||||
|
|
||||||
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task PackageReload(string name)
|
||||||
|
{
|
||||||
|
if (name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
|
||||||
|
return;
|
||||||
|
name = name.ToTitleCase();
|
||||||
|
|
||||||
|
if (await _bot.UnloadPackage(name))
|
||||||
|
{
|
||||||
|
await _bot.LoadPackage(name);
|
||||||
|
await ReplyAsync(":ok:");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await ReplyAsync(":x:");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using NadekoBot.Common.Attributes;
|
using NadekoBot.Common.Attributes;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NadekoBot.Common.ShardCom;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Administration
|
namespace NadekoBot.Modules.Administration
|
||||||
{
|
{
|
||||||
@ -30,10 +32,11 @@ namespace NadekoBot.Modules.Administration
|
|||||||
private readonly IBotConfigProvider _bc;
|
private readonly IBotConfigProvider _bc;
|
||||||
private readonly NadekoBot _bot;
|
private readonly NadekoBot _bot;
|
||||||
private readonly IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
|
private readonly IDataCache _cache;
|
||||||
|
|
||||||
public SelfCommands(DbService db, NadekoBot bot, DiscordSocketClient client,
|
public SelfCommands(DbService db, NadekoBot bot, DiscordSocketClient client,
|
||||||
IImagesService images, IBotConfigProvider bc,
|
IImagesService images, IBotConfigProvider bc,
|
||||||
IBotCredentials creds)
|
IBotCredentials creds, IDataCache cache)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_client = client;
|
_client = client;
|
||||||
@ -41,6 +44,7 @@ namespace NadekoBot.Modules.Administration
|
|||||||
_bc = bc;
|
_bc = bc;
|
||||||
_bot = bot;
|
_bot = bot;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
|
_cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
@ -217,28 +221,62 @@ namespace NadekoBot.Modules.Administration
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo 2 shard commands
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
//[NadekoCommand, Usage, Description, Aliases]
|
public async Task ShardStats(int page = 1)
|
||||||
//[Shard0Precondition]
|
{
|
||||||
//[OwnerOnly]
|
if (--page < 0)
|
||||||
//public async Task RestartShard(int shardid)
|
return;
|
||||||
//{
|
var db = _cache.Redis.GetDatabase();
|
||||||
// if (shardid == 0 || shardid > b)
|
var statuses = db.ListRange(_creds.RedisKey() + "_shardstats")
|
||||||
// {
|
.Select(x => JsonConvert.DeserializeObject<ShardComMessage>(x));
|
||||||
// await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false);
|
|
||||||
// return;
|
var status = string.Join(", ", statuses
|
||||||
// }
|
.GroupBy(x => x.ConnectionState)
|
||||||
// try
|
.Select(x => $"{x.Count()} {x.Key}")
|
||||||
// {
|
.ToArray());
|
||||||
// await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false);
|
|
||||||
// await shard.StartAsync().ConfigureAwait(false);
|
var allShardStrings = statuses
|
||||||
// await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false);
|
.Select(x =>
|
||||||
// }
|
{
|
||||||
// catch (Exception ex)
|
var timeDiff = DateTime.UtcNow - x.Time;
|
||||||
// {
|
if (timeDiff > TimeSpan.FromSeconds(20))
|
||||||
// _log.Warn(ex);
|
return $"Shard #{Format.Bold(x.ShardId.ToString())} **UNRESPONSIVE** for {timeDiff.ToString(@"hh\:mm\:ss")}";
|
||||||
// }
|
return GetText("shard_stats_txt", x.ShardId.ToString(),
|
||||||
//}
|
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.ToString()), timeDiff.ToString(@"hh\:mm\:ss"));
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
|
||||||
|
{
|
||||||
|
|
||||||
|
var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(str))
|
||||||
|
str = GetText("no_shards_on_page");
|
||||||
|
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.WithAuthor(a => a.WithName(GetText("shard_stats")))
|
||||||
|
.WithTitle(status)
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(str);
|
||||||
|
}, allShardStrings.Length / 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task RestartShard(int shardid)
|
||||||
|
{
|
||||||
|
if (shardid < 0 || shardid >= _creds.TotalShards)
|
||||||
|
{
|
||||||
|
await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pub = _cache.Redis.GetSubscriber();
|
||||||
|
pub.Publish(_creds.RedisKey() + "_shard_restart",
|
||||||
|
JsonConvert.SerializeObject(_client.ShardId),
|
||||||
|
StackExchange.Redis.CommandFlags.FireAndForget);
|
||||||
|
await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
|
@ -13,7 +13,6 @@ namespace NadekoBot.Services
|
|||||||
double MessagesPerSecond { get; }
|
double MessagesPerSecond { get; }
|
||||||
long TextChannels { get; }
|
long TextChannels { get; }
|
||||||
long VoiceChannels { get; }
|
long VoiceChannels { get; }
|
||||||
int GuildCount { get; }
|
|
||||||
|
|
||||||
TimeSpan GetUptime();
|
TimeSpan GetUptime();
|
||||||
string GetUptimeString(string separator = ", ");
|
string GetUptimeString(string separator = ", ");
|
||||||
|
@ -15,11 +15,8 @@ namespace NadekoBot.Services.Impl
|
|||||||
private Logger _log;
|
private Logger _log;
|
||||||
|
|
||||||
public ulong ClientId { get; }
|
public ulong ClientId { get; }
|
||||||
|
|
||||||
public string GoogleApiKey { get; }
|
public string GoogleApiKey { get; }
|
||||||
|
|
||||||
public string MashapeKey { get; }
|
public string MashapeKey { get; }
|
||||||
|
|
||||||
public string Token { get; }
|
public string Token { get; }
|
||||||
|
|
||||||
public ImmutableArray<ulong> OwnerIds { get; }
|
public ImmutableArray<ulong> OwnerIds { get; }
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
using NadekoBot.Common.ShardCom;
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -11,6 +13,7 @@ using System.Security.Cryptography;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
namespace NadekoBot.Services.Impl
|
namespace NadekoBot.Services.Impl
|
||||||
{
|
{
|
||||||
@ -40,15 +43,16 @@ namespace NadekoBot.Services.Impl
|
|||||||
private readonly Timer _carbonitexTimer;
|
private readonly Timer _carbonitexTimer;
|
||||||
private readonly Timer _dataTimer;
|
private readonly Timer _dataTimer;
|
||||||
private readonly ShardsCoordinator _sc;
|
private readonly ShardsCoordinator _sc;
|
||||||
|
private readonly ConnectionMultiplexer _redis;
|
||||||
|
|
||||||
public int GuildCount =>
|
public StatsService(DiscordSocketClient client, CommandHandler cmdHandler,
|
||||||
_sc?.GuildCount ?? _client.Guilds.Count();
|
IBotCredentials creds, NadekoBot nadeko,
|
||||||
|
IDataCache cache)
|
||||||
public StatsService(DiscordSocketClient client, CommandHandler cmdHandler, IBotCredentials creds, NadekoBot nadeko)
|
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
_sc = nadeko.ShardCoord;
|
_sc = nadeko.ShardCoord;
|
||||||
|
_redis = cache.Redis;
|
||||||
|
|
||||||
_started = DateTime.UtcNow;
|
_started = DateTime.UtcNow;
|
||||||
_client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter));
|
_client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter));
|
||||||
@ -142,7 +146,7 @@ namespace NadekoBot.Services.Impl
|
|||||||
{
|
{
|
||||||
using (var content = new FormUrlEncodedContent(
|
using (var content = new FormUrlEncodedContent(
|
||||||
new Dictionary<string, string> {
|
new Dictionary<string, string> {
|
||||||
{ "servercount", _sc.GuildCount.ToString() },
|
{ "servercount", nadeko.GuildCount.ToString() },
|
||||||
{ "key", _creds.CarbonKey }}))
|
{ "key", _creds.CarbonKey }}))
|
||||||
{
|
{
|
||||||
content.Headers.Clear();
|
content.Headers.Clear();
|
||||||
@ -175,7 +179,7 @@ namespace NadekoBot.Services.Impl
|
|||||||
using (var content = new FormUrlEncodedContent(
|
using (var content = new FormUrlEncodedContent(
|
||||||
new Dictionary<string, string> {
|
new Dictionary<string, string> {
|
||||||
{ "id", string.Concat(MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(_creds.ClientId.ToString())).Select(x => x.ToString("X2"))) },
|
{ "id", string.Concat(MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(_creds.ClientId.ToString())).Select(x => x.ToString("X2"))) },
|
||||||
{ "guildCount", _sc.GuildCount.ToString() },
|
{ "guildCount", nadeko.GuildCount.ToString() },
|
||||||
{ "version", BotVersion },
|
{ "version", BotVersion },
|
||||||
{ "platform", platform }}))
|
{ "platform", platform }}))
|
||||||
{
|
{
|
||||||
|
@ -29,7 +29,6 @@ namespace NadekoBot
|
|||||||
private Logger _log;
|
private Logger _log;
|
||||||
|
|
||||||
public BotCredentials Credentials { get; }
|
public BotCredentials Credentials { get; }
|
||||||
|
|
||||||
public DiscordSocketClient Client { get; }
|
public DiscordSocketClient Client { get; }
|
||||||
public CommandService CommandService { get; }
|
public CommandService CommandService { get; }
|
||||||
|
|
||||||
@ -51,11 +50,15 @@ namespace NadekoBot
|
|||||||
|
|
||||||
public ShardsCoordinator ShardCoord { get; private set; }
|
public ShardsCoordinator ShardCoord { get; private set; }
|
||||||
|
|
||||||
private readonly ShardComClient _comClient;
|
|
||||||
|
|
||||||
private readonly BotConfig _botConfig;
|
private readonly BotConfig _botConfig;
|
||||||
public IDataCache Cache { get; private set; }
|
public IDataCache Cache { get; private set; }
|
||||||
|
|
||||||
|
public int GuildCount =>
|
||||||
|
Cache.Redis.GetDatabase()
|
||||||
|
.ListRange(Credentials.RedisKey() + "_shardstats")
|
||||||
|
.Select(x => JsonConvert.DeserializeObject<ShardComMessage>(x))
|
||||||
|
.Sum(x => x.Guilds);
|
||||||
|
|
||||||
public NadekoBot(int shardId, int parentProcessId)
|
public NadekoBot(int shardId, int parentProcessId)
|
||||||
{
|
{
|
||||||
if (shardId < 0)
|
if (shardId < 0)
|
||||||
@ -82,8 +85,6 @@ namespace NadekoBot
|
|||||||
CaseSensitiveCommands = false,
|
CaseSensitiveCommands = false,
|
||||||
DefaultRunMode = RunMode.Sync,
|
DefaultRunMode = RunMode.Sync,
|
||||||
});
|
});
|
||||||
|
|
||||||
_comClient = new ShardComClient(Cache);
|
|
||||||
|
|
||||||
using (var uow = _db.UnitOfWork)
|
using (var uow = _db.UnitOfWork)
|
||||||
{
|
{
|
||||||
@ -105,13 +106,18 @@ namespace NadekoBot
|
|||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
await _comClient.Send(new ShardComMessage()
|
var data = new ShardComMessage()
|
||||||
{
|
{
|
||||||
ConnectionState = Client.ConnectionState,
|
ConnectionState = Client.ConnectionState,
|
||||||
Guilds = Client.ConnectionState == ConnectionState.Connected ? Client.Guilds.Count : 0,
|
Guilds = Client.ConnectionState == ConnectionState.Connected ? Client.Guilds.Count : 0,
|
||||||
ShardId = Client.ShardId,
|
ShardId = Client.ShardId,
|
||||||
Time = DateTime.UtcNow,
|
Time = DateTime.UtcNow,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
var sub = Cache.Redis.GetSubscriber();
|
||||||
|
var msg = JsonConvert.SerializeObject(data);
|
||||||
|
|
||||||
|
await sub.PublishAsync(Credentials.RedisKey() + "_shardcoord_send", msg).ConfigureAwait(false);
|
||||||
await Task.Delay(5000);
|
await Task.Delay(5000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -288,6 +294,7 @@ namespace NadekoBot
|
|||||||
|
|
||||||
Ready.TrySetResult(true);
|
Ready.TrySetResult(true);
|
||||||
HandleStatusChanges();
|
HandleStatusChanges();
|
||||||
|
StartSendingData();
|
||||||
_log.Info($"Shard {Client.ShardId} ready.");
|
_log.Info($"Shard {Client.ShardId} ready.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,13 +310,7 @@ namespace NadekoBot
|
|||||||
public async Task RunAndBlockAsync(params string[] args)
|
public async Task RunAndBlockAsync(params string[] args)
|
||||||
{
|
{
|
||||||
await RunAsync(args).ConfigureAwait(false);
|
await RunAsync(args).ConfigureAwait(false);
|
||||||
StartSendingData();
|
await Task.Delay(-1).ConfigureAwait(false);
|
||||||
if (ShardCoord != null)
|
|
||||||
await ShardCoord.RunAndBlockAsync();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Task.Delay(-1).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TerribleElevatedPermissionCheck()
|
private void TerribleElevatedPermissionCheck()
|
||||||
@ -329,11 +330,6 @@ namespace NadekoBot
|
|||||||
|
|
||||||
private void SetupShard(int parentProcessId)
|
private void SetupShard(int parentProcessId)
|
||||||
{
|
{
|
||||||
if (Client.ShardId == 0)
|
|
||||||
{
|
|
||||||
ShardCoord = new ShardsCoordinator(Cache);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
new Thread(new ThreadStart(() =>
|
new Thread(new ThreadStart(() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -2,75 +2,130 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NadekoBot.Common.ShardCom;
|
using NadekoBot.Common.ShardCom;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NadekoBot.Extensions;
|
||||||
|
|
||||||
namespace NadekoBot.Services
|
namespace NadekoBot.Services
|
||||||
{
|
{
|
||||||
public class ShardsCoordinator
|
public class ShardsCoordinator
|
||||||
{
|
{
|
||||||
private readonly BotCredentials _creds;
|
private readonly BotCredentials _creds;
|
||||||
|
private readonly string _key;
|
||||||
private readonly Process[] _shardProcesses;
|
private readonly Process[] _shardProcesses;
|
||||||
public ShardComMessage[] Statuses { get; }
|
|
||||||
public int GuildCount => Statuses.ToArray()
|
|
||||||
.Where(x => x != null)
|
|
||||||
.Sum(x => x.Guilds);
|
|
||||||
|
|
||||||
private readonly Logger _log;
|
private readonly Logger _log;
|
||||||
private readonly ShardComServer _comServer;
|
|
||||||
private readonly int _curProcessId;
|
private readonly int _curProcessId;
|
||||||
|
private readonly ConnectionMultiplexer _redis;
|
||||||
|
private ShardComMessage _defaultShardState;
|
||||||
|
|
||||||
public ShardsCoordinator(IDataCache cache)
|
public ShardsCoordinator()
|
||||||
{
|
{
|
||||||
|
//load main stuff
|
||||||
LogSetup.SetupLogger();
|
LogSetup.SetupLogger();
|
||||||
|
_log = LogManager.GetCurrentClassLogger();
|
||||||
_creds = new BotCredentials();
|
_creds = new BotCredentials();
|
||||||
_shardProcesses = new Process[_creds.TotalShards];
|
_key = _creds.RedisKey();
|
||||||
Statuses = new ShardComMessage[_creds.TotalShards];
|
|
||||||
|
|
||||||
for (int i = 0; i < Statuses.Length; i++)
|
//setup initial shard statuses
|
||||||
|
_defaultShardState = new ShardComMessage()
|
||||||
{
|
{
|
||||||
Statuses[i] = new ShardComMessage();
|
ConnectionState = Discord.ConnectionState.Disconnected,
|
||||||
var s = Statuses[i];
|
Guilds = 0,
|
||||||
s.ConnectionState = Discord.ConnectionState.Disconnected;
|
Time = DateTime.Now - TimeSpan.FromMinutes(1)
|
||||||
s.Guilds = 0;
|
};
|
||||||
s.ShardId = i;
|
var db = _redis.GetDatabase();
|
||||||
s.Time = DateTime.Now - TimeSpan.FromMinutes(1);
|
_shardProcesses = new Process[_creds.TotalShards];
|
||||||
|
for (int i = 0; i < _creds.TotalShards; i++)
|
||||||
|
{
|
||||||
|
_defaultShardState.ShardId = i;
|
||||||
|
db.ListSetByIndex(_key + "shardstats",
|
||||||
|
i,
|
||||||
|
JsonConvert.SerializeObject(_defaultShardState),
|
||||||
|
CommandFlags.FireAndForget);
|
||||||
}
|
}
|
||||||
|
|
||||||
_log = LogManager.GetCurrentClassLogger();
|
|
||||||
|
|
||||||
_comServer = new ShardComServer(cache);
|
|
||||||
_comServer.Start();
|
|
||||||
|
|
||||||
_comServer.OnDataReceived += _comServer_OnDataReceived;
|
|
||||||
|
|
||||||
_curProcessId = Process.GetCurrentProcess().Id;
|
_curProcessId = Process.GetCurrentProcess().Id;
|
||||||
|
|
||||||
|
_redis = ConnectionMultiplexer.Connect("127.0.0.1");
|
||||||
|
var sub = _redis.GetSubscriber();
|
||||||
|
sub.Subscribe(_key + "_shardcoord_send",
|
||||||
|
OnDataReceived,
|
||||||
|
CommandFlags.FireAndForget);
|
||||||
|
|
||||||
|
sub.Subscribe(_key + "_shardcoord_restart",
|
||||||
|
OnRestart,
|
||||||
|
CommandFlags.FireAndForget);
|
||||||
|
|
||||||
|
sub.Subscribe(_key + "_shardcoord_stop",
|
||||||
|
OnStop,
|
||||||
|
CommandFlags.FireAndForget);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task _comServer_OnDataReceived(ShardComMessage msg)
|
private void OnStop(RedisChannel ch, RedisValue data)
|
||||||
{
|
{
|
||||||
Statuses[msg.ShardId] = msg;
|
var shardId = JsonConvert.DeserializeObject<int>(data);
|
||||||
if (msg.ConnectionState == Discord.ConnectionState.Disconnected || msg.ConnectionState == Discord.ConnectionState.Disconnecting)
|
var db = _redis.GetDatabase();
|
||||||
_log.Error("!!! SHARD {0} IS IN {1} STATE", msg.ShardId, msg.ConnectionState.ToString());
|
_defaultShardState.ShardId = shardId;
|
||||||
return Task.CompletedTask;
|
db.ListSetByIndex(_key + "shardstats",
|
||||||
|
shardId,
|
||||||
|
JsonConvert.SerializeObject(_defaultShardState),
|
||||||
|
CommandFlags.FireAndForget);
|
||||||
|
var p = _shardProcesses[shardId];
|
||||||
|
_shardProcesses[shardId] = null;
|
||||||
|
try { p?.Kill(); } catch { }
|
||||||
|
try { p?.Dispose(); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRestart(RedisChannel ch, RedisValue data)
|
||||||
|
{
|
||||||
|
OnStop(ch, data);
|
||||||
|
var shardId = JsonConvert.DeserializeObject<int>(data);
|
||||||
|
_shardProcesses[shardId] = StartShard(shardId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDataReceived(RedisChannel ch, RedisValue data)
|
||||||
|
{
|
||||||
|
var msg = JsonConvert.DeserializeObject<ShardComMessage>(data);
|
||||||
|
if (msg == null)
|
||||||
|
return;
|
||||||
|
var db = _redis.GetDatabase();
|
||||||
|
db.ListSetByIndex(_key + "shardstats",
|
||||||
|
msg.ShardId,
|
||||||
|
data,
|
||||||
|
CommandFlags.FireAndForget);
|
||||||
|
if (msg.ConnectionState == Discord.ConnectionState.Disconnected
|
||||||
|
|| msg.ConnectionState == Discord.ConnectionState.Disconnecting)
|
||||||
|
{
|
||||||
|
_log.Error("!!! SHARD {0} IS IN {1} STATE !!!", msg.ShardId, msg.ConnectionState.ToString());
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunAsync()
|
public async Task RunAsync()
|
||||||
{
|
{
|
||||||
for (int i = 1; i < _creds.TotalShards; i++)
|
for (int i = 0; i < _creds.TotalShards; i++)
|
||||||
{
|
{
|
||||||
var p = Process.Start(new ProcessStartInfo()
|
var p = StartShard(i);
|
||||||
{
|
|
||||||
FileName = _creds.ShardRunCommand,
|
_shardProcesses[i] = p;
|
||||||
Arguments = string.Format(_creds.ShardRunArguments, i, _curProcessId, "")
|
await Task.Delay(6000);
|
||||||
});
|
|
||||||
// last "" in format is for backwards compatibility
|
|
||||||
// because current startup commands have {2} in them probably
|
|
||||||
await Task.Delay(5000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Process StartShard(int shardId)
|
||||||
|
{
|
||||||
|
return Process.Start(new ProcessStartInfo()
|
||||||
|
{
|
||||||
|
FileName = _creds.ShardRunCommand,
|
||||||
|
Arguments = string.Format(_creds.ShardRunArguments, shardId, _curProcessId, "")
|
||||||
|
});
|
||||||
|
// last "" in format is for backwards compatibility
|
||||||
|
// because current startup commands have {2} in them probably
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RunAndBlockAsync()
|
public async Task RunAndBlockAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -80,14 +135,15 @@ namespace NadekoBot.Services
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_log.Error(ex);
|
_log.Error(ex);
|
||||||
|
foreach (var p in _shardProcesses)
|
||||||
|
{
|
||||||
|
try { p.Kill(); } catch { }
|
||||||
|
try { p.Dispose(); } catch { }
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(-1);
|
await Task.Delay(-1);
|
||||||
foreach (var p in _shardProcesses)
|
|
||||||
{
|
|
||||||
try { p.Kill(); } catch { }
|
|
||||||
try { p.Dispose(); } catch { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,24 +16,15 @@ using System.Threading.Tasks;
|
|||||||
using NadekoBot.Common.Collections;
|
using NadekoBot.Common.Collections;
|
||||||
using SixLabors.Primitives;
|
using SixLabors.Primitives;
|
||||||
using NadekoBot.Common;
|
using NadekoBot.Common;
|
||||||
|
using NadekoBot.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Extensions
|
namespace NadekoBot.Extensions
|
||||||
{
|
{
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
//so ftw
|
public static string RedisKey(this IBotCredentials bc)
|
||||||
public static bool IsSubclassOfRawGeneric(this Type toCheck, Type generic)
|
|
||||||
{
|
{
|
||||||
while (toCheck != null && toCheck != typeof(object))
|
return bc.Token.Substring(0, 10);
|
||||||
{
|
|
||||||
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
|
|
||||||
if (generic == cur)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
toCheck = toCheck.BaseType;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> ReplaceAsync(this Regex regex, string input, Func<Match, Task<string>> replacementFn)
|
public static async Task<string> ReplaceAsync(this Regex regex, string input, Func<Match, Task<string>> replacementFn)
|
||||||
@ -223,7 +214,7 @@ namespace NadekoBot.Extensions
|
|||||||
var xOffset = 0;
|
var xOffset = 0;
|
||||||
for (int i = 0; i < imgs.Length; i++)
|
for (int i = 0; i < imgs.Length; i++)
|
||||||
{
|
{
|
||||||
canvas.DrawImage(imgs[i], 100, default(Size), new Point(xOffset, 0));
|
canvas.DrawImage(imgs[i], 100, default, new Point(xOffset, 0));
|
||||||
xOffset += imgs[i].Bounds.Width;
|
xOffset += imgs[i].Bounds.Width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ namespace NadekoBot.Modules.Utility.Services
|
|||||||
throw new StreamRoleNotFoundException();
|
throw new StreamRoleNotFoundException();
|
||||||
|
|
||||||
await user.RemoveRoleAsync(addRole).ConfigureAwait(false);
|
await user.RemoveRoleAsync(addRole).ConfigureAwait(false);
|
||||||
_log.Info("Removed stream role from a user {0} in {1} server", user.ToString(), user.Guild.ToString());
|
_log.Info("Removed stream role from the user {0} in {1} server", user.ToString(), user.Guild.ToString());
|
||||||
}
|
}
|
||||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||||
{
|
{
|
||||||
@ -272,7 +272,6 @@ namespace NadekoBot.Modules.Utility.Services
|
|||||||
_log.Error(ex);
|
_log.Error(ex);
|
||||||
throw new StreamRolePermissionException();
|
throw new StreamRolePermissionException();
|
||||||
}
|
}
|
||||||
_log.Info("Removed stream role from the user {0} in {1} server", user.ToString(), user.Guild.ToString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,8 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using System.Reflection;
|
|
||||||
using NadekoBot.Services.Impl;
|
using NadekoBot.Services.Impl;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Threading;
|
|
||||||
using ImageSharp;
|
using ImageSharp;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -17,7 +14,6 @@ using Discord.WebSocket;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using NadekoBot.Common;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.Attributes;
|
using NadekoBot.Common.Attributes;
|
||||||
using Color = Discord.Color;
|
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Utility
|
namespace NadekoBot.Modules.Utility
|
||||||
@ -27,14 +23,14 @@ namespace NadekoBot.Modules.Utility
|
|||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly IStatsService _stats;
|
private readonly IStatsService _stats;
|
||||||
private readonly IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
private readonly ShardsCoordinator _shardCoord;
|
private readonly NadekoBot _bot;
|
||||||
|
|
||||||
public Utility(NadekoBot nadeko, DiscordSocketClient client, IStatsService stats, IBotCredentials creds)
|
public Utility(NadekoBot nadeko, DiscordSocketClient client, IStatsService stats, IBotCredentials creds)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_stats = stats;
|
_stats = stats;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
_shardCoord = nadeko.ShardCoord;
|
_bot = nadeko;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
@ -214,47 +210,6 @@ namespace NadekoBot.Modules.Utility
|
|||||||
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} https://discord.gg/{invite.Code}");
|
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} https://discord.gg/{invite.Code}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
|
||||||
[Shard0Precondition]
|
|
||||||
public async Task ShardStats(int page = 1)
|
|
||||||
{
|
|
||||||
if (--page < 0)
|
|
||||||
return;
|
|
||||||
var statuses = _shardCoord.Statuses.ToArray()
|
|
||||||
.Where(x => x != null);
|
|
||||||
|
|
||||||
var status = string.Join(", ", statuses
|
|
||||||
.GroupBy(x => x.ConnectionState)
|
|
||||||
.Select(x => $"{x.Count()} {x.Key}")
|
|
||||||
.ToArray());
|
|
||||||
|
|
||||||
var allShardStrings = statuses
|
|
||||||
.Select(x =>
|
|
||||||
{
|
|
||||||
var timeDiff = DateTime.UtcNow - x.Time;
|
|
||||||
if (timeDiff > TimeSpan.FromSeconds(20))
|
|
||||||
return $"Shard #{Format.Bold(x.ShardId.ToString())} **UNRESPONSIVE** for {timeDiff.ToString(@"hh\:mm\:ss")}";
|
|
||||||
return GetText("shard_stats_txt", x.ShardId.ToString(),
|
|
||||||
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.ToString()), timeDiff.ToString(@"hh\:mm\:ss"));
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25));
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(str))
|
|
||||||
str = GetText("no_shards_on_page");
|
|
||||||
|
|
||||||
return new EmbedBuilder()
|
|
||||||
.WithAuthor(a => a.WithName(GetText("shard_stats")))
|
|
||||||
.WithTitle(status)
|
|
||||||
.WithOkColor()
|
|
||||||
.WithDescription(str);
|
|
||||||
}, allShardStrings.Length / 25);
|
|
||||||
}
|
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
public async Task Stats()
|
public async Task Stats()
|
||||||
{
|
{
|
||||||
@ -273,7 +228,7 @@ namespace NadekoBot.Modules.Utility
|
|||||||
.AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
|
.AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
|
||||||
.AddField(efb => efb.WithName(GetText("presence")).WithValue(
|
.AddField(efb => efb.WithName(GetText("presence")).WithValue(
|
||||||
GetText("presence_txt",
|
GetText("presence_txt",
|
||||||
_stats.GuildCount, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true)));
|
_bot.GuildCount, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using NadekoBot.Services;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot
|
||||||
{
|
{
|
||||||
@ -6,12 +7,18 @@ namespace NadekoBot
|
|||||||
{
|
{
|
||||||
public static Task Main(string[] args)
|
public static Task Main(string[] args)
|
||||||
{
|
{
|
||||||
if (args.Length == 2 && int.TryParse(args[0], out int shardId) && int.TryParse(args[1], out int parentProcessId))
|
if (args.Length == 2
|
||||||
|
&& int.TryParse(args[0], out int shardId)
|
||||||
|
&& int.TryParse(args[1], out int parentProcessId))
|
||||||
{
|
{
|
||||||
return new NadekoBot(shardId, parentProcessId).RunAndBlockAsync(args);
|
return new NadekoBot(shardId, parentProcessId)
|
||||||
|
.RunAndBlockAsync(args);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return new NadekoBot(0, 0).RunAndBlockAsync(args);
|
{
|
||||||
|
return new ShardsCoordinator()
|
||||||
|
.RunAndBlockAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user