diff --git a/src/Kattbot/Infrastructure/NotificationPublisher.cs b/src/Kattbot/Infrastructure/NotificationPublisher.cs index 3f9e140..b98ac82 100644 --- a/src/Kattbot/Infrastructure/NotificationPublisher.cs +++ b/src/Kattbot/Infrastructure/NotificationPublisher.cs @@ -24,7 +24,7 @@ public Task Publish(INotification notification, CancellationToken cancellationTo /// /// Sauce: https://github.com/jbogard/MediatR/blob/master/samples/MediatR.Examples.PublishStrategies/Publisher.cs. /// - private async Task SyncContinueOnException( + private static async Task SyncContinueOnException( IEnumerable handlers, INotification notification, CancellationToken cancellationToken) @@ -41,7 +41,7 @@ private async Task SyncContinueOnException( { exceptions.AddRange(ex.Flatten().InnerExceptions); } - catch (Exception ex) when (!(ex is OutOfMemoryException || ex is StackOverflowException)) + catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException)) { exceptions.Add(ex); } diff --git a/src/Kattbot/Infrastructure/ServiceCollectionExtensions.cs b/src/Kattbot/Infrastructure/ServiceCollectionExtensions.cs index 0e3133c..d6be40f 100644 --- a/src/Kattbot/Infrastructure/ServiceCollectionExtensions.cs +++ b/src/Kattbot/Infrastructure/ServiceCollectionExtensions.cs @@ -36,34 +36,29 @@ public static void AddDiscordClient(this IServiceCollection services, IConfigura var clientBuilder = DiscordClientBuilder.CreateDefault(botToken, DiscordIntents.All, services); + clientBuilder.ConfigureExtraFeatures(cfg => { cfg.LogUnknownEvents = false; }); + clientBuilder.SetLogLevel(logLevel); clientBuilder.RegisterCommands(configuration); clientBuilder.RegisterEventHandlers(); - // This replacement has to happen after the DiscordClientBuilder.CreateDefault call - // and before the DiscordClient is built. - services.Replace(); - // Calling build registers the DiscordClient as a singleton in the service collection clientBuilder.Build(); } public static void AddDbContext(this IServiceCollection services, IConfiguration configuration) { - services.AddDbContext( - builder => - { - var dbConnString = configuration.GetValue("Kattbot:ConnectionString"); - var logLevel = configuration.GetValue("Logging:LogLevel:Default"); + services.AddDbContext(builder => + { + var dbConnString = configuration.GetValue("Kattbot:ConnectionString"); + var logLevel = configuration.GetValue("Logging:LogLevel:Default"); - builder.EnableSensitiveDataLogging(logLevel == "Debug"); + builder.EnableSensitiveDataLogging(logLevel == "Debug"); - builder.UseNpgsql(dbConnString); - }, - ServiceLifetime.Transient, - ServiceLifetime.Singleton); + builder.UseNpgsql(dbConnString); + }); } private static void RegisterCommands(this DiscordClientBuilder builder, IConfiguration configuration) @@ -95,28 +90,21 @@ private static void RegisterCommands(this DiscordClientBuilder builder, IConfigu private static void RegisterEventHandlers(this DiscordClientBuilder builder) { - builder.ConfigureEventHandlers( - cfg => - { - cfg.HandleMessageCreated( - (client, args) => - client.WriteNotification(new MessageCreatedNotification(args))); - cfg.HandleMessageUpdated( - (client, args) => - client.WriteNotification(new MessageUpdatedNotification(args))); - cfg.HandleMessageDeleted( - (client, args) => - client.WriteNotification(new MessageDeletedNotification(args))); - cfg.HandleMessagesBulkDeleted( - (client, args) => - client.WriteNotification(new MessageBulkDeletedNotification(args))); - cfg.HandleMessageReactionAdded( - (client, args) => - client.WriteNotification(new MessageReactionAddedNotification(args))); - cfg.HandleMessageReactionRemoved( - (client, args) => - client.WriteNotification(new MessageReactionRemovedNotification(args))); - }); + builder.ConfigureEventHandlers(cfg => + { + cfg.HandleMessageCreated((client, args) => + client.WriteNotification(new MessageCreatedNotification(args))); + cfg.HandleMessageUpdated((client, args) => + client.WriteNotification(new MessageUpdatedNotification(args))); + cfg.HandleMessageDeleted((client, args) => + client.WriteNotification(new MessageDeletedNotification(args))); + cfg.HandleMessagesBulkDeleted((client, args) => + client.WriteNotification(new MessageBulkDeletedNotification(args))); + cfg.HandleMessageReactionAdded((client, args) => + client.WriteNotification(new MessageReactionAddedNotification(args))); + cfg.HandleMessageReactionRemoved((client, args) => + client.WriteNotification(new MessageReactionRemovedNotification(args))); + }); } private static async Task WriteNotification(this DiscordClient client, T notification) diff --git a/src/Kattbot/NotificationHandlers/Emotes/MessageCreatedNotificationHandler.cs b/src/Kattbot/NotificationHandlers/Emotes/MessageCreatedNotificationHandler.cs index fd54015..07066b1 100644 --- a/src/Kattbot/NotificationHandlers/Emotes/MessageCreatedNotificationHandler.cs +++ b/src/Kattbot/NotificationHandlers/Emotes/MessageCreatedNotificationHandler.cs @@ -23,19 +23,16 @@ namespace Kattbot.NotificationHandlers.Emotes; public class MessageCreatedNotificationHandler : BaseNotificationHandler, INotificationHandler { - private readonly EmoteEntityBuilder _emoteBuilder; private readonly EmotesRepository _kattbotRepo; private readonly IOptions _botOptions; private readonly ILogger _logger; public MessageCreatedNotificationHandler( ILogger logger, - EmoteEntityBuilder emoteBuilder, EmotesRepository kattbotRepo, IOptions botOptions) { _logger = logger; - _emoteBuilder = emoteBuilder; _kattbotRepo = kattbotRepo; _botOptions = botOptions; } @@ -64,7 +61,7 @@ public async Task Handle(MessageCreatedNotification notification, CancellationTo ulong guildId = guild.Id; - List emotes = _emoteBuilder.BuildFromSocketUserMessage(message, guildId); + List emotes = EmoteEntityBuilder.BuildFromSocketUserMessage(message, guildId); if (emotes.Count > 0) { diff --git a/src/Kattbot/NotificationHandlers/Emotes/MessageReactionAddedNotificationHandler.cs b/src/Kattbot/NotificationHandlers/Emotes/MessageReactionAddedNotificationHandler.cs index c243377..c2c8c7f 100644 --- a/src/Kattbot/NotificationHandlers/Emotes/MessageReactionAddedNotificationHandler.cs +++ b/src/Kattbot/NotificationHandlers/Emotes/MessageReactionAddedNotificationHandler.cs @@ -19,17 +19,14 @@ namespace Kattbot.NotificationHandlers.Emotes; public class MessageReactionAddedNotificationHandler : BaseNotificationHandler, INotificationHandler { - private readonly EmoteEntityBuilder _emoteBuilder; private readonly EmotesRepository _kattbotRepo; private readonly ILogger _logger; public MessageReactionAddedNotificationHandler( ILogger logger, - EmoteEntityBuilder emoteBuilder, EmotesRepository kattbotRepo) { _logger = logger; - _emoteBuilder = emoteBuilder; _kattbotRepo = kattbotRepo; } @@ -59,7 +56,7 @@ public Task Handle(MessageReactionAddedNotification notification, CancellationTo return Task.CompletedTask; } - EmoteEntity emoteEntity = _emoteBuilder.BuildFromUserReaction(message, emoji, userId, guild.Id); + EmoteEntity emoteEntity = EmoteEntityBuilder.BuildFromUserReaction(message, emoji, userId, guild.Id); _logger.LogDebug($"Saving reaction emote {emoteEntity}"); diff --git a/src/Kattbot/NotificationHandlers/Emotes/MessageReactionRemovedNotificationHandler.cs b/src/Kattbot/NotificationHandlers/Emotes/MessageReactionRemovedNotificationHandler.cs index 5ce07af..d7d435d 100644 --- a/src/Kattbot/NotificationHandlers/Emotes/MessageReactionRemovedNotificationHandler.cs +++ b/src/Kattbot/NotificationHandlers/Emotes/MessageReactionRemovedNotificationHandler.cs @@ -19,17 +19,14 @@ namespace Kattbot.NotificationHandlers.Emotes; public class MessageReactionRemovedNotificationHandler : BaseNotificationHandler, INotificationHandler { - private readonly EmoteEntityBuilder _emoteBuilder; private readonly EmotesRepository _kattbotRepo; private readonly ILogger _logger; public MessageReactionRemovedNotificationHandler( ILogger logger, - EmoteEntityBuilder emoteBuilder, EmotesRepository kattbotRepo) { _logger = logger; - _emoteBuilder = emoteBuilder; _kattbotRepo = kattbotRepo; } @@ -59,7 +56,7 @@ public Task Handle(MessageReactionRemovedNotification notification, Cancellation return Task.CompletedTask; } - EmoteEntity emoteEntity = _emoteBuilder.BuildFromUserReaction(message, emoji, userId, guild.Id); + EmoteEntity emoteEntity = EmoteEntityBuilder.BuildFromUserReaction(message, emoji, userId, guild.Id); _logger.LogDebug($"Removing reaction emote {emoteEntity}"); diff --git a/src/Kattbot/NotificationHandlers/Emotes/MessageUpdatedNotificationHandler.cs b/src/Kattbot/NotificationHandlers/Emotes/MessageUpdatedNotificationHandler.cs index 0e84994..b05bbc9 100644 --- a/src/Kattbot/NotificationHandlers/Emotes/MessageUpdatedNotificationHandler.cs +++ b/src/Kattbot/NotificationHandlers/Emotes/MessageUpdatedNotificationHandler.cs @@ -21,17 +21,14 @@ namespace Kattbot.NotificationHandlers.Emotes; public class MessageUpdatedNotificationHandler : BaseNotificationHandler, INotificationHandler { - private readonly EmoteEntityBuilder _emoteBuilder; private readonly EmotesRepository _kattbotRepo; private readonly ILogger _logger; public MessageUpdatedNotificationHandler( ILogger logger, - EmoteEntityBuilder emoteBuilder, EmotesRepository kattbotRepo) { _logger = logger; - _emoteBuilder = emoteBuilder; _kattbotRepo = kattbotRepo; } @@ -58,7 +55,7 @@ public async Task Handle(MessageUpdatedNotification notification, CancellationTo await _kattbotRepo.RemoveEmotesForMessage(messageId); - List emotes = _emoteBuilder.BuildFromSocketUserMessage(message, guildId); + List emotes = EmoteEntityBuilder.BuildFromSocketUserMessage(message, guildId); if (emotes.Count > 0) { diff --git a/src/Kattbot/Program.cs b/src/Kattbot/Program.cs index db0f07b..055685d 100644 --- a/src/Kattbot/Program.cs +++ b/src/Kattbot/Program.cs @@ -16,87 +16,80 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace Kattbot; +HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); +ConfigurationManager configuration = builder.Configuration; +IServiceCollection services = builder.Services; -public class Program +services.Configure(configuration.GetSection(BotOptions.OptionsKey)); +services.Configure(configuration.GetSection(KattGptOptions.OptionsKey)); + +services.AddHttpClient(); +services.AddHttpClient(); +services.AddHttpClient(); +services.AddHttpClient(); + +services.AddMediatR(cfg => +{ + cfg.RegisterServicesFromAssemblyContaining(); + cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(CommandRequestPipelineBehaviour<,>)); +}); + +// Registered as Transient to match lifetime of MediatR +services.AddTransient(); + +AddWorkers(services); + +AddChannels(services); + +AddInternalServices(services); + +AddRepositories(services); + +services.AddDbContext(configuration); + +services.AddDiscordClient(configuration); + +IHost app = builder.Build(); + +app.Run(); + +return; + +static void AddInternalServices(IServiceCollection services) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .ConfigureServices((hostContext, services) => - { - IConfiguration configuration = hostContext.Configuration; - - services.Configure(hostContext.Configuration.GetSection(BotOptions.OptionsKey)); - services.Configure(hostContext.Configuration.GetSection(KattGptOptions.OptionsKey)); - - services.AddHttpClient(); - services.AddHttpClient(); - services.AddHttpClient(); - services.AddHttpClient(); - - services.AddMediatR(cfg => - { - cfg.RegisterServicesFromAssemblyContaining(); - cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(CommandRequestPipelineBehaviour<,>)); - }); - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - - AddWorkers(services); - - AddChannels(services); - - AddInternalServices(services); - - AddRepositories(services); - - services.AddDbContext(configuration); - - services.AddDiscordClient(configuration); - }); - } - - private static void AddInternalServices(IServiceCollection services) - { - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - } - - private static void AddRepositories(IServiceCollection services) - { - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - } - - private static void AddWorkers(IServiceCollection services) - { - services.AddHostedService(); - services.AddHostedService(); - services.AddHostedService(); - services.AddHostedService(); - } - - private static void AddChannels(IServiceCollection services) - { - const int channelSize = 1024; - - services.AddSingleton(new CommandQueueChannel(Channel.CreateBounded(channelSize))); - services.AddSingleton(new EventQueueChannel(Channel.CreateBounded(channelSize))); - services.AddSingleton(new DiscordLogChannel(Channel.CreateBounded(channelSize))); - } + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +static void AddRepositories(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +static void AddWorkers(IServiceCollection services) +{ + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); +} + +static void AddChannels(IServiceCollection services) +{ + const int channelSize = 1024; + + services.AddSingleton(new CommandQueueChannel(Channel.CreateBounded(channelSize))); + services.AddSingleton(new EventQueueChannel(Channel.CreateBounded(channelSize))); + services.AddSingleton(new DiscordLoggerChannel(Channel.CreateBounded(channelSize))); } diff --git a/src/Kattbot/Services/DiscordErrorLogger.cs b/src/Kattbot/Services/DiscordErrorLogger.cs index 1f37a1d..e72095f 100644 --- a/src/Kattbot/Services/DiscordErrorLogger.cs +++ b/src/Kattbot/Services/DiscordErrorLogger.cs @@ -12,10 +12,10 @@ namespace Kattbot.Services; public class DiscordErrorLogger { - private readonly DiscordLogChannel _channel; + private readonly DiscordLoggerChannel _channel; private readonly BotOptions _options; - public DiscordErrorLogger(IOptions options, DiscordLogChannel channel) + public DiscordErrorLogger(IOptions options, DiscordLoggerChannel channel) { _channel = channel; _options = options.Value; diff --git a/src/Kattbot/Services/EmoteEntityBuilder.cs b/src/Kattbot/Services/EmoteEntityBuilder.cs index 929de87..f24ff8d 100644 --- a/src/Kattbot/Services/EmoteEntityBuilder.cs +++ b/src/Kattbot/Services/EmoteEntityBuilder.cs @@ -7,9 +7,9 @@ namespace Kattbot.Services; -public class EmoteEntityBuilder +public static class EmoteEntityBuilder { - public List BuildFromSocketUserMessage(DiscordMessage message, ulong guildId) + public static List BuildFromSocketUserMessage(DiscordMessage message, ulong guildId) { List emojiStrings = EmoteHelper.ExtractEmotesFromMessage(message.Content); @@ -43,7 +43,7 @@ public List BuildFromSocketUserMessage(DiscordMessage message, ulon return emotes; } - public EmoteEntity BuildFromUserReaction(DiscordMessage message, DiscordEmoji emote, ulong userId, ulong guildId) + public static EmoteEntity BuildFromUserReaction(DiscordMessage message, DiscordEmoji emote, ulong userId, ulong guildId) { var emoteEntity = new EmoteEntity { diff --git a/src/Kattbot/Workers/Channels.cs b/src/Kattbot/Workers/Channels.cs index 0f3ea17..56be4f6 100644 --- a/src/Kattbot/Workers/Channels.cs +++ b/src/Kattbot/Workers/Channels.cs @@ -48,9 +48,9 @@ public record BaseDiscordLogItem(ulong DiscordGuildId, ulong DiscordChannelId); public record DiscordLogItem(T Message, ulong DiscordGuildId, ulong DiscordChannelId) : BaseDiscordLogItem(DiscordGuildId, DiscordChannelId); -public class DiscordLogChannel : AbstractQueueChannel +public class DiscordLoggerChannel : AbstractQueueChannel { - public DiscordLogChannel(Channel channel) + public DiscordLoggerChannel(Channel channel) : base(channel) { } } diff --git a/src/Kattbot/Workers/CommandQueueWorker.cs b/src/Kattbot/Workers/CommandQueueWorker.cs index f57839f..78aa0c2 100644 --- a/src/Kattbot/Workers/CommandQueueWorker.cs +++ b/src/Kattbot/Workers/CommandQueueWorker.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Kattbot.CommandHandlers; using MediatR; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -11,14 +12,17 @@ namespace Kattbot.Workers; public class CommandQueueWorker : BackgroundService { private readonly CommandQueueChannel _channel; + private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - private readonly IMediator _mediator; - public CommandQueueWorker(ILogger logger, CommandQueueChannel channel, IMediator mediator) + public CommandQueueWorker( + ILogger logger, + CommandQueueChannel channel, + IServiceScopeFactory scopeFactory) { _logger = logger; _channel = channel; - _mediator = mediator; + _scopeFactory = scopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -35,7 +39,14 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) command.GetType().Name, _channel.Reader.Count); - _ = Task.Run(() => _mediator.Send(command, stoppingToken), stoppingToken); + _ = Task.Run( + async () => + { + using var scope = _scopeFactory.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + await mediator.Send(command, stoppingToken); + }, + stoppingToken); } } catch (TaskCanceledException) diff --git a/src/Kattbot/Workers/DiscordLoggerWorker.cs b/src/Kattbot/Workers/DiscordLoggerWorker.cs index 6c8dec7..52bc2d9 100644 --- a/src/Kattbot/Workers/DiscordLoggerWorker.cs +++ b/src/Kattbot/Workers/DiscordLoggerWorker.cs @@ -12,14 +12,14 @@ namespace Kattbot.Workers; public class DiscordLoggerWorker : BackgroundService { - private readonly DiscordLogChannel _channel; + private readonly DiscordLoggerChannel _channel; private readonly DiscordClient _client; private readonly ILogger _logger; private readonly BotOptions _options; public DiscordLoggerWorker( ILogger logger, - DiscordLogChannel channel, + DiscordLoggerChannel channel, DiscordClient client, IOptions options) { diff --git a/src/Kattbot/Workers/EventQueueWorker.cs b/src/Kattbot/Workers/EventQueueWorker.cs index 19b4baf..93c95ae 100644 --- a/src/Kattbot/Workers/EventQueueWorker.cs +++ b/src/Kattbot/Workers/EventQueueWorker.cs @@ -5,6 +5,7 @@ using Kattbot.NotificationHandlers; using Kattbot.Services; using MediatR; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -13,19 +14,19 @@ namespace Kattbot.Workers; public class EventQueueWorker : BackgroundService { private readonly EventQueueChannel _channel; + private readonly IServiceScopeFactory _scopeFactory; private readonly DiscordErrorLogger _discordErrorLogger; private readonly ILogger _logger; - private readonly NotificationPublisher _publisher; public EventQueueWorker( ILogger logger, EventQueueChannel channel, - NotificationPublisher publisher, + IServiceScopeFactory scopeFactory, DiscordErrorLogger discordErrorLogger) { _logger = logger; _channel = channel; - _publisher = publisher; + _scopeFactory = scopeFactory; _discordErrorLogger = discordErrorLogger; } @@ -39,7 +40,10 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) try { - await _publisher.Publish(notification, stoppingToken); + using var scope = _scopeFactory.CreateScope(); + var publisher = scope.ServiceProvider.GetRequiredService(); + + await publisher.Publish(notification, stoppingToken); } catch (AggregateException ex) { diff --git a/src/Kattbot/appsettings.Development.json b/src/Kattbot/appsettings.Development.json index 80c2600..b78fee8 100644 --- a/src/Kattbot/appsettings.Development.json +++ b/src/Kattbot/appsettings.Development.json @@ -9,7 +9,7 @@ }, "Kattbot": { "ConnectionString": "Server=host.docker.internal:5433;Database=kattbot-dev;User Id=kattbot;Password=hunter2", - "CommandPrefix": "!", + "CommandPrefix": ";;", "AlternateCommandPrefix": "kd", "ErrorLogGuildId": "1411437458062835809", "ErrorLogChannelId": "1411441087997808750" diff --git a/src/Kattbot/diag.Dockerfile b/src/Kattbot/diag.Dockerfile index a3f5502..be1fb9a 100644 --- a/src/Kattbot/diag.Dockerfile +++ b/src/Kattbot/diag.Dockerfile @@ -4,6 +4,7 @@ RUN dotnet tool install -g dotnet-counters && \ dotnet tool install -g dotnet-monitor && \ dotnet tool install -g dotnet-trace && \ dotnet tool install -g dotnet-dump && \ + dotnet tool install -g dotnet-gcdump && \ dotnet tool install -g dotnet-stack ENV PATH="/root/.dotnet/tools:$PATH"