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

Information
Class: AsyncResponse.Transports.GooglePubSub.GooglePubSubBackgroundFailureContext
Assembly: AsyncResponse.Transports.GooglePubSub
File(s): /_/src/Transports/AsyncResponse.Transports.GooglePubSub/GooglePubSubSubscriberOptions.cs
Line coverage
100%
Covered lines: 15
Uncovered lines: 0
Coverable lines: 15
Total lines: 184
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_SubscriptionId()100%11100%
get_SubscriberRole()100%11100%
get_Message()100%11100%
get_MessageId()100%11100%
get_Exception()100%11100%

File(s)

/_/src/Transports/AsyncResponse.Transports.GooglePubSub/GooglePubSubSubscriberOptions.cs

#LineLine coverage
 1using Google.Cloud.PubSub.V1;
 2
 3namespace AsyncResponse.Transports.GooglePubSub;
 4
 5/// <summary>
 6/// Controls when a Google Pub/Sub message is acknowledged relative to AsyncResponse handling.
 7/// </summary>
 8public enum GooglePubSubAckMode
 9{
 10    /// <summary>
 11    /// ACK only after the AsyncResponse handler completes successfully; NACK if the handler throws.
 12    /// This is the default and preserves Pub/Sub retry semantics for handler failures.
 13    /// </summary>
 14    AckAfterHandlerCompletes = 0,
 15
 16    /// <summary>
 17    /// ACK immediately after the message is accepted into a bounded in-process background queue.
 18    /// Handler failures are logged and reported through
 19    /// <see cref="GooglePubSubSubscriberOptions.OnBackgroundFailure"/> because Pub/Sub has already
 20    /// been ACKed.
 21    /// </summary>
 22    AckAfterEnqueue = 1
 23}
 24
 25/// <summary>
 26/// Describes a handler failure that happened after a Google Pub/Sub message was already ACKed by
 27/// <see cref="GooglePubSubAckMode.AckAfterEnqueue"/>.
 28/// </summary>
 29public sealed class GooglePubSubBackgroundFailureContext
 30{
 831    internal GooglePubSubBackgroundFailureContext(
 832        string subscriptionId,
 833        string subscriberRole,
 834        PubsubMessage message,
 835        Exception exception)
 36    {
 837        SubscriptionId = subscriptionId;
 838        SubscriberRole = subscriberRole;
 839        Message = message;
 840        Exception = exception;
 841    }
 42
 43    /// <summary>The subscription whose background worker was handling the message.</summary>
 244    public string SubscriptionId { get; }
 45
 46    /// <summary>The logical subscriber role, such as <c>Worker</c> or <c>ResponseIngress</c>.</summary>
 247    public string SubscriberRole { get; }
 48
 49    /// <summary>The Pub/Sub message that failed after being ACKed.</summary>
 450    public PubsubMessage Message { get; }
 51
 52    /// <summary>The Pub/Sub message id, when provided by Google Pub/Sub.</summary>
 453    public string MessageId => Message.MessageId;
 54
 55    /// <summary>The exception thrown by the background handler.</summary>
 456    public Exception Exception { get; }
 57}
 58
 59/// <summary>
 60/// Per-subscription Google Pub/Sub subscriber behavior.
 61/// </summary>
 62public sealed class GooglePubSubSubscriberOptions
 63{
 64    /// <summary>
 65    /// Controls when the Pub/Sub callback returns ACK. Defaults to
 66    /// <see cref="GooglePubSubAckMode.AckAfterHandlerCompletes"/>.
 67    /// </summary>
 68    public GooglePubSubAckMode AckMode { get; set; } = GooglePubSubAckMode.AckAfterHandlerCompletes;
 69
 70    /// <summary>
 71    /// Number of background workers used by <see cref="GooglePubSubAckMode.AckAfterEnqueue"/>.
 72    /// Must be explicitly set to a positive value for early ACK mode.
 73    /// Values greater than one allow concurrent handling and therefore do not preserve message
 74    /// ordering.
 75    /// </summary>
 76    public int BackgroundWorkerCount { get; set; }
 77
 78    /// <summary>
 79    /// Maximum number of messages waiting in the background queue for
 80    /// <see cref="GooglePubSubAckMode.AckAfterEnqueue"/>. Must be explicitly set to a positive value.
 81    /// When full, the Pub/Sub callback parks awaiting queue space and ACKs once the message is
 82    /// accepted — it does not NACK on a full queue; NACK is returned only when the write fails
 83    /// (shutdown/disposal), so the message is redelivered rather than lost.
 84    /// </summary>
 85    public int BackgroundQueueCapacity { get; set; }
 86
 87    /// <summary>
 88    /// Maximum time to wait for queued/running background handlers while the hosted subscriber stops.
 89    /// </summary>
 90    public TimeSpan BackgroundDrainTimeout { get; set; } = TimeSpan.FromSeconds(20);
 91
 92    /// <summary>
 93    /// Optional callback invoked when a background handler fails after the message was already ACKed.
 94    /// Use it to publish to a dead-letter path, increment operator-visible metrics, or alert on
 95    /// already-ACKed work that Pub/Sub cannot redeliver.
 96    /// </summary>
 97    public Func<GooglePubSubBackgroundFailureContext, ValueTask>? OnBackgroundFailure { get; set; }
 98
 99    /// <summary>
 100    /// Longest the Pub/Sub client keeps extending one message's ack deadline while it is held in
 101    /// this process (the SDK's <c>SubscriberClient.Settings.MaxTotalAckExtension</c>). Default:
 102    /// <c>60 minutes</c>, the SDK default. Must be at least one minute and at most the .NET timer
 103    /// ceiling (~49.7 days); keep it under the subscription's message retention.
 104    /// <para>
 105    /// This is the transport's <em>in-flight ceiling</em>. The clock starts when the client receives
 106    /// the message, not when its handler starts. Once it lapses the client stops extending the
 107    /// deadline, the current lease (up to 60 seconds) runs out, and Pub/Sub redelivers the
 108    /// <em>same</em> message — to this or another subscriber, counting a delivery attempt against
 109    /// the subscription's <c>DeadLetterPolicy</c> — while the first handler is still running. The
 110    /// first handler is not cancelled, and its late ACK is best-effort: it cannot recall a copy
 111    /// Pub/Sub has already handed out, so the work runs twice.
 112    /// A handler that can legitimately run longer than this must raise it; the worker
 113    /// subscriber's value is what <see cref="GooglePubSubWorkerTransport"/> advertises through
 114    /// <see cref="IWorkerTransportInFlightLimit"/>, so durable-flow timers that wait in process are
 115    /// planned inside it.
 116    /// </para>
 117    /// <para>
 118    /// The floor exists because the first lease already lasts the client's 60-second ack deadline:
 119    /// a smaller value cannot make Pub/Sub redeliver sooner, it would only shrink the ceiling flows
 120    /// plan against.
 121    /// </para>
 122    /// </summary>
 123    public TimeSpan MaxTotalAckExtension { get; set; } = TimeSpan.FromMinutes(60);
 124
 125    /// <summary>
 126    /// Number of streaming-pull connections (SDK <c>SubscriberServiceApiClient</c>s) the subscriber
 127    /// client opens. Must be between 1 and 256 (the SDK's range). Default: <c>1</c>.
 128    /// <para>
 129    /// The SDK's own default is the machine's CPU count, and it applies the flow-control limits to
 130    /// <em>each</em> connection's fetch independently while limiting concurrent handlers once for
 131    /// the whole client: with N connections the process leases up to N ×
 132    /// <see cref="MaxOutstandingMessages"/> messages but runs at most
 133    /// <see cref="MaxOutstandingMessages"/> handlers. The surplus sits leased and idle — withheld
 134    /// from other subscriber processes and spending its <see cref="MaxTotalAckExtension"/> budget
 135    /// before a handler ever starts — which is the wrong trade for job-style handlers that run for
 136    /// seconds to hours. One connection keeps the leased set equal to the running set; raise it only
 137    /// when a single stream's fetch throughput (not handler time) is the bottleneck.
 138    /// </para>
 139    /// </summary>
 140    public int ClientCount { get; set; } = 1;
 141
 142    /// <summary>
 143    /// Flow-control ceiling on messages the client holds un-ACKed at once (SDK
 144    /// <c>FlowControlSettings.MaxOutstandingElementCount</c>). In
 145    /// <see cref="GooglePubSubAckMode.AckAfterHandlerCompletes"/> this is the maximum number of
 146    /// handlers running concurrently in this process — including durable flows parked in process on
 147    /// a timer. Must be positive. Default: <c>1000</c>, the SDK default.
 148    /// Ignored in <see cref="GooglePubSubAckMode.AckAfterEnqueue"/>, where the streaming pull is
 149    /// bounded to <see cref="BackgroundQueueCapacity"/> instead.
 150    /// </summary>
 151    public int MaxOutstandingMessages { get; set; } = 1000;
 152
 153    /// <summary>
 154    /// Flow-control ceiling on the total size, in bytes, of the messages the client holds un-ACKed
 155    /// at once (SDK <c>FlowControlSettings.MaxOutstandingByteCount</c>). Must be positive; a single
 156    /// message larger than the ceiling is still delivered, on its own. Default: <c>100,000,000</c>
 157    /// (100 MB), the SDK default. Ignored in <see cref="GooglePubSubAckMode.AckAfterEnqueue"/>.
 158    /// </summary>
 159    public long MaxOutstandingBytes { get; set; } = 100_000_000;
 160
 161    /// <summary>
 162    /// Explicitly opts this subscriber into ACK-after-enqueue behavior.
 163    /// </summary>
 164    public GooglePubSubSubscriberOptions UseAckAfterEnqueue(
 165        int backgroundWorkerCount,
 166        int backgroundQueueCapacity,
 167        TimeSpan? backgroundDrainTimeout = null)
 168    {
 169        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundWorkerCount);
 170        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundQueueCapacity);
 171
 172        if (backgroundDrainTimeout is { } timeout && timeout <= TimeSpan.Zero)
 173            throw new ArgumentOutOfRangeException(nameof(backgroundDrainTimeout), timeout, "Drain timeout must be positi
 174
 175        AckMode = GooglePubSubAckMode.AckAfterEnqueue;
 176        BackgroundWorkerCount = backgroundWorkerCount;
 177        BackgroundQueueCapacity = backgroundQueueCapacity;
 178
 179        if (backgroundDrainTimeout is not null)
 180            BackgroundDrainTimeout = backgroundDrainTimeout.Value;
 181
 182        return this;
 183    }
 184}