- NadekoBot class renamed to Bot

- Implemented grpc based coordinator. Supports restarting, killing single or all shards, as well as getting current shard statuses. (Adaptation of the one used by the public bot)
- Coord is setup via coord.yml file
- Methods from SelfService which deal with shard/bot restart etc have been moved to ICoordinator (with GrpcRemoteCoordinator being the default implementation atm)
- Vastly simplified NadekoBot/Program.cs
This commit is contained in:
Kwoth
2021-06-19 13:13:54 +02:00
parent d8c7cdc7f4
commit c86bf6f300
58 changed files with 1212 additions and 635 deletions

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Grpc.Core;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Coordinator;
using NadekoBot.Core.Services;
using NadekoBot.Extensions;
using Serilog;
namespace NadekoBot.Services
{
public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
{
private readonly Coordinator.Coordinator.CoordinatorClient _coordClient;
private readonly DiscordSocketClient _client;
public RemoteGrpcCoordinator(IBotCredentials creds, DiscordSocketClient client)
{
// todo should use credentials
var channel = Grpc.Net.Client.GrpcChannel.ForAddress("https://localhost:3443");
_coordClient = new(channel);
_client = client;
}
public bool RestartBot()
{
_coordClient.RestartAllShards(new RestartAllRequest
{
});
return true;
}
public void Die()
{
_coordClient.Die(new DieRequest()
{
Graceful = false
});
}
public bool RestartShard(int shardId)
{
_coordClient.RestartShard(new RestartShardRequest
{
ShardId = shardId,
});
return true;
}
public IEnumerable<ShardStatus> GetAllShardStatuses()
{
var res = _coordClient.GetAllStatuses(new GetAllStatusesRequest());
return res.Statuses
.ToArray()
.Map(s => new ShardStatus()
{
ConnectionState = FromCoordConnState(s.State),
Guilds = s.GuildCount,
ShardId = s.ShardId,
Time = s.LastUpdate.ToDateTime(),
});
}
public int GetGuildCount()
{
var res = _coordClient.GetAllStatuses(new GetAllStatusesRequest());
return res.Statuses.Sum(x => x.GuildCount);
}
public Task OnReadyAsync()
{
Task.Run(async () =>
{
var gracefulImminent = false;
while (true)
{
try
{
var reply = await _coordClient.HeartbeatAsync(new HeartbeatRequest
{
State = ToCoordConnState(_client.ConnectionState),
GuildCount = _client.ConnectionState == Discord.ConnectionState.Connected ? _client.Guilds.Count : 0,
ShardId = _client.ShardId,
}, deadline: DateTime.UtcNow + TimeSpan.FromSeconds(10));
gracefulImminent = reply.GracefulImminent;
}
catch (RpcException ex)
{
if (!gracefulImminent)
{
Log.Warning(ex, "Hearbeat failed and graceful shutdown was not expected: {Message}",
ex.Message);
break;
}
await Task.Delay(22500).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Error(ex, "Unexpected heartbeat exception: {Message}", ex.Message);
break;
}
await Task.Delay(7500).ConfigureAwait(false);
}
Environment.Exit(5);
});
return Task.CompletedTask;
}
private ConnState ToCoordConnState(Discord.ConnectionState state)
=> state switch
{
Discord.ConnectionState.Connecting => ConnState.Connecting,
Discord.ConnectionState.Connected => ConnState.Connected,
_ => ConnState.Disconnected
};
private Discord.ConnectionState FromCoordConnState(ConnState state)
=> state switch
{
ConnState.Connecting => Discord.ConnectionState.Connecting,
ConnState.Connected => Discord.ConnectionState.Connected,
_ => Discord.ConnectionState.Disconnected
};
}
}