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

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

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/src/Transports/AsyncResponse.Transports.SQS/SqsSubscriberOptions.cs

#LineLine coverage
 1namespace AsyncResponse.Transports.SQS;
 2
 3/// <summary>Controls when an SQS message is deleted relative to AsyncResponse handling.</summary>
 4public enum SqsAckMode
 5{
 6    /// <summary>
 7    /// Delete the message only after the AsyncResponse handler completes successfully. Handler
 8    /// failures leave the message invisible until its visibility timeout expires (or shorten the
 9    /// wait via <see cref="SqsSubscriberOptions.RedeliveryDelay"/>); SQS then redelivers it, and the
 10    /// queue's redrive policy dead-letters it after <c>maxReceiveCount</c> receives.
 11    /// </summary>
 12    AckAfterHandlerCompletes = 0,
 13
 14    /// <summary>
 15    /// Delete the message immediately after it is accepted into a bounded in-process background
 16    /// queue. Handler failures are logged and reported through
 17    /// <see cref="SqsSubscriberOptions.OnBackgroundFailure"/> because SQS can no longer redeliver a
 18    /// deleted message.
 19    /// </summary>
 20    AckAfterEnqueue = 1
 21}
 22
 23/// <summary>Describes a handler failure that happened after an SQS message was already deleted.</summary>
 24public sealed class SqsBackgroundFailureContext
 25{
 226    internal SqsBackgroundFailureContext(
 227        string queue,
 228        string subscriberRole,
 229        string messageId,
 230        int receiveCount,
 231        string? correlationId,
 232        Exception exception)
 33    {
 234        Queue = queue;
 235        SubscriberRole = subscriberRole;
 236        MessageId = messageId;
 237        ReceiveCount = receiveCount;
 238        CorrelationId = correlationId;
 239        Exception = exception;
 240    }
 41
 42    /// <summary>The SQS queue the message came from.</summary>
 43    public string Queue { get; }
 44
 45    /// <summary>The logical subscriber role, such as <c>Worker</c> or <c>ResponseIngress</c>.</summary>
 46    public string SubscriberRole { get; }
 47
 48    /// <summary>The SQS message id of the already-deleted message.</summary>
 49    public string MessageId { get; }
 50
 51    /// <summary>The SQS <c>ApproximateReceiveCount</c> observed when the message was received.</summary>
 52    public int ReceiveCount { get; }
 53
 54    /// <summary>The AsyncResponse correlation id, when one was available.</summary>
 55    public string? CorrelationId { get; }
 56
 57    /// <summary>The exception thrown by the background handler.</summary>
 58    public Exception Exception { get; }
 59}
 60
 61/// <summary>Per-queue SQS subscriber behavior.</summary>
 62public sealed class SqsSubscriberOptions
 63{
 64    /// <summary>Controls when a message is deleted. Defaults to <see cref="SqsAckMode.AckAfterHandlerCompletes"/>.</sum
 65    public SqsAckMode AckMode { get; set; } = SqsAckMode.AckAfterHandlerCompletes;
 66
 67    /// <summary>
 68    /// Visibility timeout applied per receive. <c>null</c> (the default) uses the queue's configured
 69    /// visibility timeout. Must exceed the slowest expected handler in
 70    /// <see cref="SqsAckMode.AckAfterHandlerCompletes"/> so an in-flight message is not redelivered
 71    /// while still being handled.
 72    /// </summary>
 73    public TimeSpan? VisibilityTimeout { get; set; }
 74
 75    /// <summary>
 76    /// When set, a failed handler shortens the message's remaining invisibility to this delay via
 77    /// <c>ChangeMessageVisibility</c>, scheduling a faster redelivery than waiting out the full
 78    /// visibility timeout. <c>null</c> (the default) lets the visibility timeout expire naturally.
 79    /// Redelivery accounting stays native either way: every receive increments
 80    /// <c>ApproximateReceiveCount</c>, and the queue's redrive policy dead-letters the message after
 81    /// <c>maxReceiveCount</c>.
 82    /// </summary>
 83    public TimeSpan? RedeliveryDelay { get; set; }
 84
 85    /// <summary>
 86    /// Heartbeat cadence for extending the visibility of received-but-unprocessed messages while a
 87    /// <see cref="SqsAckMode.AckAfterHandlerCompletes"/> batch is worked through serially. Each beat
 88    /// resets every unprocessed message's invisibility to <see cref="VisibilityTimeout"/> via
 89    /// <c>ChangeMessageVisibility</c>, so a slow handler does not let later batch messages become
 90    /// visible (and be processed twice) before their turn. Requires <see cref="VisibilityTimeout"/>
 91    /// to be set and must be shorter than it. Renewal failures are logged and processing continues —
 92    /// the message simply redelivers, preserving at-least-once semantics.
 93    /// <c>null</c> (the default) disables renewal: it is off by default because extending visibility
 94    /// silently changes redrive timing operators tune on the queue, and on FIFO queues an extended
 95    /// message keeps its whole message group blocked if the consumer wedges, delaying failover.
 96    /// Ignored in <see cref="SqsAckMode.AckAfterEnqueue"/> (messages are already deleted).
 97    /// </summary>
 98    public TimeSpan? VisibilityRenewalInterval { get; set; }
 99
 100    /// <summary>Number of background workers used by <see cref="SqsAckMode.AckAfterEnqueue"/>.</summary>
 101    public int BackgroundWorkerCount { get; set; }
 102
 103    /// <summary>Maximum number of deleted messages waiting in the background queue.</summary>
 104    public int BackgroundQueueCapacity { get; set; }
 105
 106    /// <summary>Maximum time to wait for queued/running background handlers while stopping.</summary>
 107    public TimeSpan BackgroundDrainTimeout { get; set; } = TimeSpan.FromSeconds(20);
 108
 109    /// <summary>
 110    /// Optional callback invoked when a background handler fails after the message was already
 111    /// deleted. Use it to publish to a dead-letter path, increment operator-visible metrics, or
 112    /// alert on already-ACKed work that SQS cannot redeliver.
 113    /// </summary>
 114    public Func<SqsBackgroundFailureContext, ValueTask>? OnBackgroundFailure { get; set; }
 115
 116    /// <summary>Explicitly opts this subscriber into delete-after-enqueue behavior.</summary>
 117    public SqsSubscriberOptions UseAckAfterEnqueue(
 118        int backgroundWorkerCount,
 119        int backgroundQueueCapacity,
 120        TimeSpan? backgroundDrainTimeout = null)
 121    {
 122        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundWorkerCount);
 123        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundQueueCapacity);
 124        if (backgroundDrainTimeout is { } timeout && timeout <= TimeSpan.Zero)
 125            throw new ArgumentOutOfRangeException(nameof(backgroundDrainTimeout), timeout, "Drain timeout must be positi
 126
 127        AckMode = SqsAckMode.AckAfterEnqueue;
 128        BackgroundWorkerCount = backgroundWorkerCount;
 129        BackgroundQueueCapacity = backgroundQueueCapacity;
 130        if (backgroundDrainTimeout is not null)
 131            BackgroundDrainTimeout = backgroundDrainTimeout.Value;
 132        return this;
 133    }
 134}