refactor: only pass shard ID to event handlers instead of full shard object

This commit is contained in:
spiral 2022-01-14 18:39:03 -05:00
parent bf80dd0988
commit 50a24f03a7
No known key found for this signature in database
GPG Key ID: A6059F0CA0E1BD31
13 changed files with 74 additions and 70 deletions

View File

@ -53,15 +53,15 @@ public static class DiscordCacheExtensions
return default;
}
public static ValueTask TryUpdateSelfMember(this IDiscordCache cache, Shard shard, IGatewayEvent evt)
public static ValueTask TryUpdateSelfMember(this IDiscordCache cache, ulong userId, IGatewayEvent evt)
{
if (evt is GuildCreateEvent gc)
return cache.SaveSelfMember(gc.Id, gc.Members.FirstOrDefault(m => m.User.Id == shard.User?.Id)!);
if (evt is MessageCreateEvent mc && mc.Member != null && mc.Author.Id == shard.User?.Id)
return cache.SaveSelfMember(gc.Id, gc.Members.FirstOrDefault(m => m.User.Id == userId)!);
if (evt is MessageCreateEvent mc && mc.Member != null && mc.Author.Id == userId)
return cache.SaveSelfMember(mc.GuildId!.Value, mc.Member);
if (evt is GuildMemberAddEvent gma && gma.User.Id == shard.User?.Id)
if (evt is GuildMemberAddEvent gma && gma.User.Id == userId)
return cache.SaveSelfMember(gma.GuildId, gma);
if (evt is GuildMemberUpdateEvent gmu && gmu.User.Id == shard.User?.Id)
if (evt is GuildMemberUpdateEvent gmu && gmu.User.Id == userId)
return cache.SaveSelfMember(gmu.GuildId, gmu);
return default;

View File

@ -56,7 +56,7 @@ public class Bot
public void Init()
{
_cluster.EventReceived += OnEventReceived;
_cluster.EventReceived += (shard, evt) => OnEventReceived(shard.ShardId, evt);
// Init the shard stuff
_services.Resolve<ShardInfoService>().Init();
@ -72,40 +72,43 @@ public class Bot
}, null, timeTillNextWholeMinute, TimeSpan.FromMinutes(1));
}
private async Task OnEventReceived(Shard shard, IGatewayEvent evt)
private async Task OnEventReceived(int shardId, IGatewayEvent evt)
{
await _cache.TryUpdateSelfMember(shard, evt);
// we HandleGatewayEvent **before** getting the own user, because the own user is set in HandleGatewayEvent for ReadyEvent
await _cache.HandleGatewayEvent(evt);
var userId = await _cache.GetOwnUser();
await _cache.TryUpdateSelfMember(userId, evt);
// HandleEvent takes a type parameter, automatically inferred by the event type
// It will then look up an IEventHandler<TypeOfEvent> in the DI container and call that object's handler method
// For registering new ones, see Modules.cs
if (evt is MessageCreateEvent mc)
await HandleEvent(shard, mc);
await HandleEvent(shardId, mc);
if (evt is MessageUpdateEvent mu)
await HandleEvent(shard, mu);
await HandleEvent(shardId, mu);
if (evt is MessageDeleteEvent md)
await HandleEvent(shard, md);
await HandleEvent(shardId, md);
if (evt is MessageDeleteBulkEvent mdb)
await HandleEvent(shard, mdb);
await HandleEvent(shardId, mdb);
if (evt is MessageReactionAddEvent mra)
await HandleEvent(shard, mra);
await HandleEvent(shardId, mra);
if (evt is InteractionCreateEvent ic)
await HandleEvent(shard, ic);
await HandleEvent(shardId, ic);
// Update shard status for shards immediately on connect
if (evt is ReadyEvent re)
await HandleReady(shard, re);
await HandleReady(shardId, re);
if (evt is ResumedEvent)
await HandleResumed(shard);
await HandleResumed(shardId);
}
private Task HandleResumed(Shard shard) => UpdateBotStatus(shard);
private Task HandleResumed(int shardId) => UpdateBotStatus(shardId);
private Task HandleReady(Shard shard, ReadyEvent _)
private Task HandleReady(int shardId, ReadyEvent _)
{
_hasReceivedReady = true;
return UpdateBotStatus(shard);
return UpdateBotStatus(shardId);
}
public async Task Shutdown()
@ -128,7 +131,7 @@ public class Bot
})));
}
private Task HandleEvent<T>(Shard shard, T evt) where T : IGatewayEvent
private Task HandleEvent<T>(int shardId, T evt) where T : IGatewayEvent
{
// We don't want to stall the event pipeline, so we'll "fork" inside here
var _ = HandleEventInner();
@ -157,13 +160,12 @@ public class Bot
var queue = serviceScope.ResolveOptional<HandlerQueue<T>>();
using var _ = LogContext.PushProperty("EventId", Guid.NewGuid());
using var __ = LogContext.Push(await serviceScope.Resolve<SerilogGatewayEnricherFactory>()
.GetEnricher(shard, evt));
using var __ = LogContext.Push(await serviceScope.Resolve<SerilogGatewayEnricherFactory>().GetEnricher(shardId, evt));
_logger.Verbose("Received gateway event: {@Event}", evt);
// Also, find a Sentry enricher for the event type (if one is present), and ask it to put some event data in the Sentry scope
var sentryEnricher = serviceScope.ResolveOptional<ISentryEnricher<T>>();
sentryEnricher?.Enrich(serviceScope.Resolve<Scope>(), shard, evt);
sentryEnricher?.Enrich(serviceScope.Resolve<Scope>(), shardId, evt);
using var timer = _metrics.Measure.Timer.Time(BotMetrics.EventsHandled,
new MetricTags("event", typeof(T).Name.Replace("Event", "")));
@ -172,26 +174,28 @@ public class Bot
// the TryHandle call returns true if it's handled the event
// Usually it won't, so just pass it on to the main handler
if (queue == null || !await queue.TryHandle(evt))
await handler.Handle(shard, evt);
await handler.Handle(shardId, evt);
}
catch (Exception exc)
{
await HandleError(shard, handler, evt, serviceScope, exc);
await HandleError(handler, evt, serviceScope, exc);
}
}
}
private async Task HandleError<T>(Shard shard, IEventHandler<T> handler, T evt, ILifetimeScope serviceScope,
private async Task HandleError<T>(IEventHandler<T> handler, T evt, ILifetimeScope serviceScope,
Exception exc)
where T : IGatewayEvent
{
_metrics.Measure.Meter.Mark(BotMetrics.BotErrors, exc.GetType().FullName);
var ourUserId = await _cache.GetOwnUser();
// Make this beforehand so we can access the event ID for logging
var sentryEvent = new SentryEvent(exc);
// If the event is us responding to our own error messages, don't bother logging
if (evt is MessageCreateEvent mc && mc.Author.Id == shard.User?.Id)
if (evt is MessageCreateEvent mc && mc.Author.Id == ourUserId)
return;
var shouldReport = exc.IsOurProblem();
@ -220,7 +224,6 @@ public class Bot
return;
// Once we've sent it to Sentry, report it to the user (if we have permission to)
var ourUserId = await _cache.GetOwnUser();
var reportChannel = handler.ErrorChannelFor(evt, ourUserId);
if (reportChannel == null)
return;
@ -243,7 +246,7 @@ public class Bot
_logger.Debug("Submitted metrics to backend");
}
private async Task UpdateBotStatus(Shard specificShard = null)
private async Task UpdateBotStatus(int? specificShardId = null)
{
// If we're not on any shards, don't bother (this happens if the periodic timer fires before the first Ready)
if (!_hasReceivedReady) return;
@ -266,8 +269,8 @@ public class Bot
}
});
if (specificShard != null)
await UpdateStatus(specificShard);
if (specificShardId != null)
await UpdateStatus(_cluster.Shards[specificShardId.Value]);
else // Run shard updates concurrently
await Task.WhenAll(_cluster.Shards.Values.Select(UpdateStatus));
}

View File

@ -28,11 +28,11 @@ public class Context
private Command? _currentCommand;
public Context(ILifetimeScope provider, Shard shard, Guild? guild, Channel channel, MessageCreateEvent message, int commandParseOffset,
public Context(ILifetimeScope provider, int shardId, Guild? guild, Channel channel, MessageCreateEvent message, int commandParseOffset,
PKSystem senderSystem, SystemConfig config, MessageContext messageContext)
{
Message = (Message)message;
Shard = shard;
ShardId = shardId;
Guild = guild;
Channel = channel;
System = senderSystem;
@ -58,7 +58,7 @@ public class Context
public readonly Message Message;
public readonly Guild Guild;
public readonly Shard Shard;
public readonly int ShardId;
public readonly Cluster Cluster;
public readonly MessageContext MessageContext;

View File

@ -4,7 +4,7 @@ namespace PluralKit.Bot;
public interface IEventHandler<in T> where T : IGatewayEvent
{
Task Handle(Shard shard, T evt);
Task Handle(int shardId, T evt);
ulong? ErrorChannelFor(T evt, ulong userId) => null;
}

View File

@ -16,7 +16,7 @@ public class InteractionCreated: IEventHandler<InteractionCreateEvent>
_services = services;
}
public async Task Handle(Shard shard, InteractionCreateEvent evt)
public async Task Handle(int shardId, InteractionCreateEvent evt)
{
if (evt.Type == Interaction.InteractionType.MessageComponent)
{

View File

@ -63,9 +63,9 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
// We consider a message duplicate if it has the same ID as the previous message that hit the gateway
_lastMessageCache.GetLastMessage(msg.ChannelId)?.Current.Id == msg.Id;
public async Task Handle(Shard shard, MessageCreateEvent evt)
public async Task Handle(int shardId, MessageCreateEvent evt)
{
if (evt.Author.Id == shard.User?.Id) return;
if (evt.Author.Id == await _cache.GetOwnUser()) return;
if (evt.Type != Message.MessageType.Default && evt.Type != Message.MessageType.Reply) return;
if (IsDuplicateMessage(evt)) return;
@ -90,9 +90,9 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
if (evt.Author.Bot || evt.WebhookId != null || evt.Author.System == true)
return;
if (await TryHandleCommand(shard, evt, guild, channel, ctx))
if (await TryHandleCommand(shardId, evt, guild, channel, ctx))
return;
await TryHandleProxy(shard, evt, guild, channel, ctx);
await TryHandleProxy(evt, guild, channel, ctx);
}
private async ValueTask<bool> TryHandleLogClean(MessageCreateEvent evt, MessageContext ctx)
@ -105,14 +105,16 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
return true;
}
private async ValueTask<bool> TryHandleCommand(Shard shard, MessageCreateEvent evt, Guild? guild,
private async ValueTask<bool> TryHandleCommand(int shardId, MessageCreateEvent evt, Guild? guild,
Channel channel, MessageContext ctx)
{
var content = evt.Content;
if (content == null) return false;
var ourUserId = await _cache.GetOwnUser();
// Check for command prefix
if (!HasCommandPrefix(content, shard.User?.Id ?? default, out var cmdStart) || cmdStart == content.Length)
if (!HasCommandPrefix(content, ourUserId, out var cmdStart) || cmdStart == content.Length)
return false;
// Trim leading whitespace from command without actually modifying the string
@ -125,7 +127,7 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
{
var system = ctx.SystemId != null ? await _repo.GetSystem(ctx.SystemId.Value) : null;
var config = ctx.SystemId != null ? await _repo.GetSystemConfig(ctx.SystemId.Value) : null;
await _tree.ExecuteCommand(new Context(_services, shard, guild, channel, evt, cmdStart, system, config, ctx));
await _tree.ExecuteCommand(new Context(_services, shardId, guild, channel, evt, cmdStart, system, config, ctx));
}
catch (PKError)
{
@ -156,14 +158,14 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
return false;
}
private async ValueTask<bool> TryHandleProxy(Shard shard, MessageCreateEvent evt, Guild guild, Channel channel,
private async ValueTask<bool> TryHandleProxy(MessageCreateEvent evt, Guild guild, Channel channel,
MessageContext ctx)
{
var botPermissions = await _cache.PermissionsIn(channel.Id);
try
{
return await _proxy.HandleIncomingMessage(shard, evt, ctx, guild, channel, ctx.AllowAutoproxy,
return await _proxy.HandleIncomingMessage(evt, ctx, guild, channel, ctx.AllowAutoproxy,
botPermissions);
}

View File

@ -24,7 +24,7 @@ public class MessageDeleted: IEventHandler<MessageDeleteEvent>, IEventHandler<Me
_logger = logger.ForContext<MessageDeleted>();
}
public Task Handle(Shard shard, MessageDeleteEvent evt)
public Task Handle(int shardId, MessageDeleteEvent evt)
{
// Delete deleted webhook messages from the data store
// Most of the data in the given message is wrong/missing, so always delete just to be sure.
@ -43,7 +43,7 @@ public class MessageDeleted: IEventHandler<MessageDeleteEvent>, IEventHandler<Me
return Task.CompletedTask;
}
public Task Handle(Shard shard, MessageDeleteBulkEvent evt)
public Task Handle(int shardId, MessageDeleteBulkEvent evt)
{
// Same as above, but bulk
async Task Inner()

View File

@ -41,7 +41,7 @@ public class MessageEdited: IEventHandler<MessageUpdateEvent>
_logger = logger.ForContext<MessageEdited>();
}
public async Task Handle(Shard shard, MessageUpdateEvent evt)
public async Task Handle(int shardId, MessageUpdateEvent evt)
{
if (evt.Author.Value?.Id == await _cache.GetOwnUser()) return;
@ -69,7 +69,7 @@ public class MessageEdited: IEventHandler<MessageUpdateEvent>
try
{
await _proxy.HandleIncomingMessage(shard, equivalentEvt, ctx, allowAutoproxy: false, guild: guild,
await _proxy.HandleIncomingMessage(equivalentEvt, ctx, allowAutoproxy: false, guild: guild,
channel: channel, botPermissions: botPermissions);
}
// Catch any failed proxy checks so they get ignored in the global error handler

View File

@ -42,7 +42,7 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
_logger = logger.ForContext<ReactionAdded>();
}
public async Task Handle(Shard shard, MessageReactionAddEvent evt)
public async Task Handle(int shardId, MessageReactionAddEvent evt)
{
await TryHandleProxyMessageReactions(evt);
}

View File

@ -50,7 +50,7 @@ public class ProxyService
_logger = logger.ForContext<ProxyService>();
}
public async Task<bool> HandleIncomingMessage(Shard shard, MessageCreateEvent message, MessageContext ctx,
public async Task<bool> HandleIncomingMessage(MessageCreateEvent message, MessageContext ctx,
Guild guild, Channel channel, bool allowAutoproxy, PermissionSet botPermissions)
{
if (!ShouldProxy(channel, message, ctx))
@ -80,7 +80,7 @@ public class ProxyService
var allowEmbeds = senderPermissions.HasFlag(PermissionSet.EmbedLinks);
// Everything's in order, we can execute the proxy!
await ExecuteProxy(shard, message, ctx, match, allowEveryone, allowEmbeds);
await ExecuteProxy(message, ctx, match, allowEveryone, allowEmbeds);
return true;
}
@ -119,7 +119,7 @@ public class ProxyService
return true;
}
private async Task ExecuteProxy(Shard shard, Message trigger, MessageContext ctx,
private async Task ExecuteProxy(Message trigger, MessageContext ctx,
ProxyMatch match, bool allowEveryone, bool allowEmbeds)
{
// Create reply embed
@ -160,7 +160,7 @@ public class ProxyService
Embeds = embeds.ToArray(),
AllowEveryone = allowEveryone
});
await HandleProxyExecutedActions(shard, ctx, trigger, proxyMessage, match);
await HandleProxyExecutedActions(ctx, trigger, proxyMessage, match);
}
private async Task<(string?, string?)> FetchReferencedMessageAuthorInfo(Message trigger, Message referenced)
@ -279,8 +279,7 @@ public class ProxyService
private string FixSameNameInner(string name)
=> $"{name}\u17b5";
private async Task HandleProxyExecutedActions(Shard shard, MessageContext ctx,
Message triggerMessage, Message proxyMessage, ProxyMatch match)
private async Task HandleProxyExecutedActions(MessageContext ctx, Message triggerMessage, Message proxyMessage, ProxyMatch match)
{
var sentMessage = new PKMessage
{

View File

@ -138,7 +138,7 @@ public class ShardInfoService
}
}
public ShardInfo GetShardInfo(Shard shard) => _shardInfo[shard.ShardId];
public ShardInfo GetShardInfo(int shardId) => _shardInfo[shardId];
public class ShardInfo
{

View File

@ -6,7 +6,7 @@ namespace PluralKit.Bot;
public interface ISentryEnricher<T> where T : IGatewayEvent
{
void Enrich(Scope scope, Shard shard, T evt);
void Enrich(Scope scope, int shardId, T evt);
}
public class SentryEnricher:
@ -26,7 +26,7 @@ public class SentryEnricher:
// TODO: should this class take the Scope by dependency injection instead?
// Would allow us to create a centralized "chain of handlers" where this class could just be registered as an entry in
public void Enrich(Scope scope, Shard shard, MessageCreateEvent evt)
public void Enrich(Scope scope, int shardId, MessageCreateEvent evt)
{
scope.AddBreadcrumb(evt.Content, "event.message",
data: new Dictionary<string, string>
@ -36,7 +36,7 @@ public class SentryEnricher:
{"guild", evt.GuildId.ToString()},
{"message", evt.Id.ToString()}
});
scope.SetTag("shard", shard.ShardId.ToString());
scope.SetTag("shard", shardId.ToString());
// Also report information about the bot's permissions in the channel
// We get a lot of permission errors so this'll be useful for determining problems
@ -46,7 +46,7 @@ public class SentryEnricher:
// scope.AddBreadcrumb(perms.ToPermissionString(), "permissions");
}
public void Enrich(Scope scope, Shard shard, MessageDeleteBulkEvent evt)
public void Enrich(Scope scope, int shardId, MessageDeleteBulkEvent evt)
{
scope.AddBreadcrumb("", "event.messageDelete",
data: new Dictionary<string, string>
@ -55,10 +55,10 @@ public class SentryEnricher:
{"guild", evt.GuildId.ToString()},
{"messages", string.Join(",", evt.Ids)}
});
scope.SetTag("shard", shard.ShardId.ToString());
scope.SetTag("shard", shardId.ToString());
}
public void Enrich(Scope scope, Shard shard, MessageUpdateEvent evt)
public void Enrich(Scope scope, int shardId, MessageUpdateEvent evt)
{
scope.AddBreadcrumb(evt.Content.Value ?? "<unknown>", "event.messageEdit",
data: new Dictionary<string, string>
@ -67,10 +67,10 @@ public class SentryEnricher:
{"guild", evt.GuildId.Value.ToString()},
{"message", evt.Id.ToString()}
});
scope.SetTag("shard", shard.ShardId.ToString());
scope.SetTag("shard", shardId.ToString());
}
public void Enrich(Scope scope, Shard shard, MessageDeleteEvent evt)
public void Enrich(Scope scope, int shardId, MessageDeleteEvent evt)
{
scope.AddBreadcrumb("", "event.messageDelete",
data: new Dictionary<string, string>
@ -79,10 +79,10 @@ public class SentryEnricher:
{"guild", evt.GuildId.ToString()},
{"message", evt.Id.ToString()}
});
scope.SetTag("shard", shard.ShardId.ToString());
scope.SetTag("shard", shardId.ToString());
}
public void Enrich(Scope scope, Shard shard, MessageReactionAddEvent evt)
public void Enrich(Scope scope, int shardId, MessageReactionAddEvent evt)
{
scope.AddBreadcrumb("", "event.reaction",
data: new Dictionary<string, string>
@ -93,6 +93,6 @@ public class SentryEnricher:
{"message", evt.MessageId.ToString()},
{"reaction", evt.Emoji.Name}
});
scope.SetTag("shard", shard.ShardId.ToString());
scope.SetTag("shard", shardId.ToString());
}
}

View File

@ -20,9 +20,9 @@ public class SerilogGatewayEnricherFactory
_botConfig = botConfig;
}
public async Task<ILogEventEnricher> GetEnricher(Shard shard, IGatewayEvent evt)
public async Task<ILogEventEnricher> GetEnricher(int shardId, IGatewayEvent evt)
{
var props = new List<LogEventProperty> { new("ShardId", new ScalarValue(shard.ShardId)) };
var props = new List<LogEventProperty> { new("ShardId", new ScalarValue(shardId)) };
if (_botConfig.Cluster != null)
props.Add(new LogEventProperty("ClusterId", new ScalarValue(_botConfig.Cluster.NodeName)));