< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.Transports.RabbitMQ.RabbitMqSubscriberOptions
Assembly: AsyncResponse.Transports.RabbitMQ
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/Transports/AsyncResponse.Transports.RabbitMQ/RabbitMqSubscriberOptions.cs
Line coverage
100%
Covered lines: 12
Uncovered lines: 0
Coverable lines: 12
Total lines: 141
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
UseAckAfterEnqueue(...)100%66100%

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/src/Transports/AsyncResponse.Transports.RabbitMQ/RabbitMqSubscriberOptions.cs

#LineLine coverage
 1namespace AsyncResponse.Transports.RabbitMQ;
 2
 3/// <summary>
 4/// Controls when a RabbitMQ delivery is acknowledged relative to AsyncResponse handling.
 5/// </summary>
 6public enum RabbitMqAckMode
 7{
 8    /// <summary>
 9    /// ACK only after the AsyncResponse handler completes successfully; NACK/requeue if the handler
 10    /// throws. This is the default and preserves broker redelivery for handler failures.
 11    /// </summary>
 12    AckAfterHandlerCompletes = 0,
 13
 14    /// <summary>
 15    /// ACK immediately after the delivery is accepted into a bounded in-process background queue.
 16    /// Handler failures are logged and reported through <see cref="RabbitMqSubscriberOptions.OnBackgroundFailure"/>
 17    /// because RabbitMQ has already been ACKed.
 18    /// </summary>
 19    AckAfterEnqueue = 1
 20}
 21
 22/// <summary>
 23/// Describes a handler failure that happened after a RabbitMQ delivery was already ACKed by
 24/// <see cref="RabbitMqAckMode.AckAfterEnqueue"/>.
 25/// </summary>
 26public sealed class RabbitMqBackgroundFailureContext
 27{
 28    internal RabbitMqBackgroundFailureContext(
 29        string queue,
 30        string subscriberRole,
 31        string exchange,
 32        string routingKey,
 33        ulong deliveryTag,
 34        Exception exception)
 35    {
 36        Queue = queue;
 37        SubscriberRole = subscriberRole;
 38        Exchange = exchange;
 39        RoutingKey = routingKey;
 40        DeliveryTag = deliveryTag;
 41        Exception = exception;
 42    }
 43
 44    /// <summary>The queue whose background worker was handling the delivery.</summary>
 45    public string Queue { get; }
 46
 47    /// <summary>The logical subscriber role, such as <c>Worker</c> or <c>ResponseIngress</c>.</summary>
 48    public string SubscriberRole { get; }
 49
 50    /// <summary>The exchange the delivery came from.</summary>
 51    public string Exchange { get; }
 52
 53    /// <summary>The routing key the delivery came with.</summary>
 54    public string RoutingKey { get; }
 55
 56    /// <summary>The RabbitMQ delivery tag.</summary>
 57    public ulong DeliveryTag { get; }
 58
 59    /// <summary>The exception thrown by the background handler.</summary>
 60    public Exception Exception { get; }
 61}
 62
 63/// <summary>
 64/// Per-queue RabbitMQ subscriber behavior.
 65/// </summary>
 66public sealed class RabbitMqSubscriberOptions
 67{
 68    /// <summary>
 69    /// Controls when the RabbitMQ delivery is ACKed. Defaults to
 70    /// <see cref="RabbitMqAckMode.AckAfterHandlerCompletes"/>.
 71    /// </summary>
 72    public RabbitMqAckMode AckMode { get; set; } = RabbitMqAckMode.AckAfterHandlerCompletes;
 73
 74    /// <summary>
 75    /// Prefetch count set through <c>basic.qos</c>. Default: <c>16</c>.
 76    /// </summary>
 377    public ushort PrefetchCount { get; set; } = 16;
 78
 79    /// <summary>
 80    /// Maximum number of times a delivery may be attempted in <see cref="RabbitMqAckMode.AckAfterHandlerCompletes"/>
 81    /// mode before a failing handler rejects it without requeue (dead-lettering it via
 82    /// <see cref="RabbitMqAsyncResponseOptions.DeadLetterExchange"/> when configured, otherwise dropping it).
 83    /// Default: <c>0</c>, meaning unlimited — a failing handler requeues forever, which can hot-loop on a poison
 84    /// message. Set a positive cap (with a dead-letter exchange) to bound retries. The attempt count is read from
 85    /// the broker's <c>x-death</c> header and the <c>redelivered</c> flag; because <c>basic.nack</c> requeue does
 86    /// not increment <c>x-death</c>, the resolved attempt never exceeds 2 on its own, so values above 2 only take
 87    /// effect when the dead-letter path forms a TTL-retry cycle that re-delivers the message (each dead-letter
 88    /// hop increments <c>x-death</c>). A value above 2 without such a cycle behaves like 2 and logs a startup
 89    /// warning. Ignored for <see cref="RabbitMqAckMode.AckAfterEnqueue"/>, which acknowledges before handling
 90    /// and never redelivers.
 91    /// </summary>
 92    public int MaxDeliveryAttempts { get; set; }
 93
 94    /// <summary>
 95    /// Number of background workers used by <see cref="RabbitMqAckMode.AckAfterEnqueue"/>.
 96    /// Must be explicitly set to a positive value for early ACK mode.
 97    /// </summary>
 98    public int BackgroundWorkerCount { get; set; }
 99
 100    /// <summary>
 101    /// Maximum number of deliveries waiting in the background queue for
 102    /// <see cref="RabbitMqAckMode.AckAfterEnqueue"/>. When full, the delivery is NACKed with requeue.
 103    /// </summary>
 104    public int BackgroundQueueCapacity { get; set; }
 105
 106    /// <summary>
 107    /// Maximum time to wait for queued/running background handlers while the hosted subscriber stops.
 108    /// </summary>
 3109    public TimeSpan BackgroundDrainTimeout { get; set; } = TimeSpan.FromSeconds(20);
 110
 111    /// <summary>
 112    /// Optional callback invoked when a background handler fails after the delivery was already ACKed.
 113    /// Use it to publish to a dead-letter path, increment operator-visible metrics, or alert on
 114    /// already-ACKed work that RabbitMQ cannot redeliver.
 115    /// </summary>
 116    public Func<RabbitMqBackgroundFailureContext, ValueTask>? OnBackgroundFailure { get; set; }
 117
 118    /// <summary>
 119    /// Explicitly opts this subscriber into ACK-after-enqueue behavior.
 120    /// </summary>
 121    public RabbitMqSubscriberOptions UseAckAfterEnqueue(
 122        int backgroundWorkerCount,
 123        int backgroundQueueCapacity,
 124        TimeSpan? backgroundDrainTimeout = null)
 125    {
 3126        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundWorkerCount);
 3127        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundQueueCapacity);
 128
 3129        if (backgroundDrainTimeout is { } timeout && timeout <= TimeSpan.Zero)
 3130            throw new ArgumentOutOfRangeException(nameof(backgroundDrainTimeout), timeout, "Drain timeout must be positi
 131
 3132        AckMode = RabbitMqAckMode.AckAfterEnqueue;
 3133        BackgroundWorkerCount = backgroundWorkerCount;
 3134        BackgroundQueueCapacity = backgroundQueueCapacity;
 135
 3136        if (backgroundDrainTimeout is not null)
 3137            BackgroundDrainTimeout = backgroundDrainTimeout.Value;
 138
 3139        return this;
 140    }
 141}