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

Information
Class: AsyncResponse.Transports.NATS.NatsSubscriberOptions
Assembly: AsyncResponse.Transports.NATS
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/Transports/AsyncResponse.Transports.NATS/NatsSubscriberOptions.cs
Line coverage
100%
Covered lines: 14
Uncovered lines: 0
Coverable lines: 14
Total lines: 124
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.NATS/NatsSubscriberOptions.cs

#LineLine coverage
 1namespace AsyncResponse.Transports.NATS;
 2
 3/// <summary>
 4/// Controls when a NATS JetStream message is acknowledged relative to AsyncResponse handling.
 5/// </summary>
 6public enum NatsAckMode
 7{
 8    /// <summary>
 9    /// ACK only after the AsyncResponse handler completes successfully. If the handler throws, the
 10    /// message is NAKed and redelivered after <see cref="NatsSubscriberOptions.RedeliveryDelay"/>
 11    /// until <see cref="NatsSubscriberOptions.MaxDeliveryAttempts"/> is reached.
 12    /// </summary>
 13    AckAfterHandlerCompletes = 0,
 14
 15    /// <summary>
 16    /// ACK immediately after the message is accepted into a bounded in-process background queue.
 17    /// Handler failures are logged, reported through
 18    /// <see cref="NatsSubscriberOptions.OnBackgroundFailure"/>, and dead-lettered when enabled because
 19    /// JetStream has already been ACKed.
 20    /// </summary>
 21    AckAfterEnqueue = 1
 22}
 23
 24/// <summary>
 25/// Describes a handler failure that happened after a NATS JetStream message was already ACKed by
 26/// <see cref="NatsAckMode.AckAfterEnqueue"/>.
 27/// </summary>
 28public sealed class NatsBackgroundFailureContext
 29{
 30    internal NatsBackgroundFailureContext(
 31        string subject,
 32        string consumer,
 33        string subscriberRole,
 34        long numDelivered,
 35        string? correlationId,
 36        Exception exception)
 37    {
 38        Subject = subject;
 39        Consumer = consumer;
 40        SubscriberRole = subscriberRole;
 41        NumDelivered = numDelivered;
 42        CorrelationId = correlationId;
 43        Exception = exception;
 44    }
 45
 46    /// <summary>The NATS subject the message came from.</summary>
 47    public string Subject { get; }
 48
 49    /// <summary>The durable JetStream consumer that received the message.</summary>
 50    public string Consumer { get; }
 51
 52    /// <summary>The logical subscriber role, such as <c>Worker</c> or <c>ResponseIngress</c>.</summary>
 53    public string SubscriberRole { get; }
 54
 55    /// <summary>The JetStream delivery count for the message.</summary>
 56    public long NumDelivered { get; }
 57
 58    /// <summary>The AsyncResponse correlation id, when one was available.</summary>
 59    public string? CorrelationId { get; }
 60
 61    /// <summary>The exception thrown by the background handler.</summary>
 62    public Exception Exception { get; }
 63}
 64
 65/// <summary>Per-subject NATS JetStream subscriber behavior.</summary>
 66public sealed class NatsSubscriberOptions
 67{
 68    /// <summary>Controls when a message is ACKed. Defaults to <see cref="NatsAckMode.AckAfterHandlerCompletes"/>.</summ
 69    public NatsAckMode AckMode { get; set; } = NatsAckMode.AckAfterHandlerCompletes;
 70
 71    /// <summary>Maximum number of in-flight messages pulled from the consumer at once. Default: <c>16</c>.</summary>
 372    public int BatchSize { get; set; } = 16;
 73
 74    /// <summary>
 75    /// Maximum number of delivery attempts before a failing message is ACKed and written to the
 76    /// dead-letter subject. <c>0</c> means unlimited retries. Default: <c>5</c>.
 77    /// </summary>
 378    public int MaxDeliveryAttempts { get; set; } = 5;
 79
 80    /// <summary>Delay requested when NAKing a failed message, so redelivery is not immediate. Default: <c>5s</c>.</summ
 381    public TimeSpan RedeliveryDelay { get; set; } = TimeSpan.FromSeconds(5);
 82
 83    /// <summary>Number of background workers used by <see cref="NatsAckMode.AckAfterEnqueue"/>. Must be positive for ea
 84    public int BackgroundWorkerCount { get; set; }
 85
 86    /// <summary>
 87    /// Maximum number of messages waiting in the background queue for
 88    /// <see cref="NatsAckMode.AckAfterEnqueue"/>. When full, the consume loop pauses pulling new
 89    /// messages until a background worker frees capacity; a message caught waiting when the
 90    /// subscriber stops is NAKed so JetStream redelivers it.
 91    /// </summary>
 92    public int BackgroundQueueCapacity { get; set; }
 93
 94    /// <summary>Maximum time to wait for queued/running background handlers while the hosted subscriber stops.</summary
 395    public TimeSpan BackgroundDrainTimeout { get; set; } = TimeSpan.FromSeconds(20);
 96
 97    /// <summary>
 98    /// Optional callback invoked when a background handler fails after the JetStream message was
 99    /// already ACKed. Use it to increment operator-visible metrics or alert on already-ACKed work.
 100    /// </summary>
 101    public Func<NatsBackgroundFailureContext, ValueTask>? OnBackgroundFailure { get; set; }
 102
 103    /// <summary>Explicitly opts this subscriber into ACK-after-enqueue (early ACK) behavior.</summary>
 104    public NatsSubscriberOptions UseAckAfterEnqueue(
 105        int backgroundWorkerCount,
 106        int backgroundQueueCapacity,
 107        TimeSpan? backgroundDrainTimeout = null)
 108    {
 3109        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundWorkerCount);
 3110        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundQueueCapacity);
 111
 3112        if (backgroundDrainTimeout is { } timeout && timeout <= TimeSpan.Zero)
 3113            throw new ArgumentOutOfRangeException(nameof(backgroundDrainTimeout), timeout, "Drain timeout must be positi
 114
 3115        AckMode = NatsAckMode.AckAfterEnqueue;
 3116        BackgroundWorkerCount = backgroundWorkerCount;
 3117        BackgroundQueueCapacity = backgroundQueueCapacity;
 118
 3119        if (backgroundDrainTimeout is not null)
 3120            BackgroundDrainTimeout = backgroundDrainTimeout.Value;
 121
 3122        return this;
 123    }
 124}