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

Information
Class: AsyncResponse.Transports.RabbitMQ.RabbitMqBackgroundFailureContext
Assembly: AsyncResponse.Transports.RabbitMQ
File(s): /_/src/Transports/AsyncResponse.Transports.RabbitMQ/RabbitMqSubscriberOptions.cs
Line coverage
100%
Covered lines: 20
Uncovered lines: 0
Coverable lines: 20
Total lines: 142
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Queue()100%11100%
get_SubscriberRole()100%11100%
get_Exchange()100%11100%
get_RoutingKey()100%11100%
get_DeliveryTag()100%11100%
get_Exception()100%11100%

File(s)

/_/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{
 828    internal RabbitMqBackgroundFailureContext(
 829        string queue,
 830        string subscriberRole,
 831        string exchange,
 832        string routingKey,
 833        ulong deliveryTag,
 834        Exception exception)
 35    {
 836        Queue = queue;
 837        SubscriberRole = subscriberRole;
 838        Exchange = exchange;
 839        RoutingKey = routingKey;
 840        DeliveryTag = deliveryTag;
 841        Exception = exception;
 842    }
 43
 44    /// <summary>The queue whose background worker was handling the delivery.</summary>
 245    public string Queue { get; }
 46
 47    /// <summary>The logical subscriber role, such as <c>Worker</c> or <c>ResponseIngress</c>.</summary>
 248    public string SubscriberRole { get; }
 49
 50    /// <summary>The exchange the delivery came from.</summary>
 251    public string Exchange { get; }
 52
 53    /// <summary>The routing key the delivery came with.</summary>
 254    public string RoutingKey { get; }
 55
 56    /// <summary>The RabbitMQ delivery tag.</summary>
 257    public ulong DeliveryTag { get; }
 58
 59    /// <summary>The exception thrown by the background handler.</summary>
 460    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>
 77    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 channel's delivery loop pauses
 103    /// (deliveries are dispatched per channel, sequentially) until a worker frees capacity.
 104    /// </summary>
 105    public int BackgroundQueueCapacity { get; set; }
 106
 107    /// <summary>
 108    /// Maximum time to wait for queued/running background handlers while the hosted subscriber stops.
 109    /// </summary>
 110    public TimeSpan BackgroundDrainTimeout { get; set; } = TimeSpan.FromSeconds(20);
 111
 112    /// <summary>
 113    /// Optional callback invoked when a background handler fails after the delivery was already ACKed.
 114    /// Use it to publish to a dead-letter path, increment operator-visible metrics, or alert on
 115    /// already-ACKed work that RabbitMQ cannot redeliver.
 116    /// </summary>
 117    public Func<RabbitMqBackgroundFailureContext, ValueTask>? OnBackgroundFailure { get; set; }
 118
 119    /// <summary>
 120    /// Explicitly opts this subscriber into ACK-after-enqueue behavior.
 121    /// </summary>
 122    public RabbitMqSubscriberOptions UseAckAfterEnqueue(
 123        int backgroundWorkerCount,
 124        int backgroundQueueCapacity,
 125        TimeSpan? backgroundDrainTimeout = null)
 126    {
 127        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundWorkerCount);
 128        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundQueueCapacity);
 129
 130        if (backgroundDrainTimeout is { } timeout && timeout <= TimeSpan.Zero)
 131            throw new ArgumentOutOfRangeException(nameof(backgroundDrainTimeout), timeout, "Drain timeout must be positi
 132
 133        AckMode = RabbitMqAckMode.AckAfterEnqueue;
 134        BackgroundWorkerCount = backgroundWorkerCount;
 135        BackgroundQueueCapacity = backgroundQueueCapacity;
 136
 137        if (backgroundDrainTimeout is not null)
 138            BackgroundDrainTimeout = backgroundDrainTimeout.Value;
 139
 140        return this;
 141    }
 142}