Enhanced RabbitMQ email processing by introducing a `SendingEmailConsumerPool` to enable the competing consumers pattern. Each consumer operates on its own channel, improving scalability and thread safety. - Added `SendingEmailConsumerPool` to manage multiple consumers. - Updated `DependencyInjection` to register the consumer pool. - Refactored `SendingEmailConsumer` for better logging and error handling. - Updated `AsyncInitWorker` to initialize the consumer pool. - Added `ConsumerConcurrency` to RabbitMQ configuration. - Improved error handling in `LimilabsEmailService` with detailed SMTP error messages.
52 lines
1.9 KiB
C#
52 lines
1.9 KiB
C#
using DigitalData.MessagingService.Application.Common.Interfaces;
|
|
using DigitalData.MessagingService.RabbitMQ;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace DigitalData.MessagingService.Infrastructure.Queue;
|
|
|
|
/// <summary>
|
|
/// Manages a pool of <see cref="SendingEmailConsumer"/> instances that compete for messages
|
|
/// on the same RabbitMQ queue (competing consumers pattern).
|
|
/// Each consumer owns a dedicated channel, so they process messages fully in parallel
|
|
/// without any shared locking or synchronization primitives.
|
|
/// </summary>
|
|
public sealed class SendingEmailConsumerPool : IAsyncDisposable
|
|
{
|
|
private readonly List<SendingEmailConsumer> _consumers;
|
|
private readonly ILogger<SendingEmailConsumerPool>? _logger;
|
|
private readonly int _concurrency;
|
|
|
|
public SendingEmailConsumerPool(
|
|
IOptions<RabbitMqConfiguration> config,
|
|
IEmailService emailService,
|
|
RabbitMqConnectionFactory cnnFactory,
|
|
ILogger<SendingEmailConsumerPool>? logger = null,
|
|
ILogger<SendingEmailConsumer>? consumerLogger = null)
|
|
{
|
|
_logger = logger;
|
|
_concurrency = config.Value.ConsumerConcurrency;
|
|
|
|
_consumers = [.. Enumerable
|
|
.Range(0, _concurrency)
|
|
.Select(_ => new SendingEmailConsumer(config.Value.QueueName, emailService, cnnFactory, consumerLogger))];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts all consumers in parallel. Each consumer opens its own channel and begins listening.
|
|
/// </summary>
|
|
public async Task InitAsync()
|
|
{
|
|
_logger?.LogInformation("Starting {Count} competing email consumers.", _concurrency);
|
|
|
|
await Task.WhenAll(_consumers.Select(c => c.InitAsync()));
|
|
|
|
_logger?.LogInformation("All {Count} email consumers started.", _concurrency);
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await Task.WhenAll(_consumers.Select(async c => await c.DisposeAsync().AsTask()));
|
|
}
|
|
}
|