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

Information
Class: AsyncResponse.Transports.GooglePubSub.GooglePubSubSubscriberOptions
Assembly: AsyncResponse.Transports.GooglePubSub
File(s): /_/src/Transports/AsyncResponse.Transports.GooglePubSub/GooglePubSubSubscriberOptions.cs
Line coverage
100%
Covered lines: 19
Uncovered lines: 0
Coverable lines: 19
Total lines: 184
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
get_AckMode()100%11100%
get_BackgroundWorkerCount()100%11100%
get_BackgroundQueueCapacity()100%11100%
get_BackgroundDrainTimeout()100%11100%
get_OnBackgroundFailure()100%11100%
get_MaxTotalAckExtension()100%11100%
get_ClientCount()100%11100%
get_MaxOutstandingMessages()100%11100%
get_MaxOutstandingBytes()100%11100%
UseAckAfterEnqueue(...)100%66100%

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{
 31    internal GooglePubSubBackgroundFailureContext(
 32        string subscriptionId,
 33        string subscriberRole,
 34        PubsubMessage message,
 35        Exception exception)
 36    {
 37        SubscriptionId = subscriptionId;
 38        SubscriberRole = subscriberRole;
 39        Message = message;
 40        Exception = exception;
 41    }
 42
 43    /// <summary>The subscription whose background worker was handling the message.</summary>
 44    public string SubscriptionId { get; }
 45
 46    /// <summary>The logical subscriber role, such as <c>Worker</c> or <c>ResponseIngress</c>.</summary>
 47    public string SubscriberRole { get; }
 48
 49    /// <summary>The Pub/Sub message that failed after being ACKed.</summary>
 50    public PubsubMessage Message { get; }
 51
 52    /// <summary>The Pub/Sub message id, when provided by Google Pub/Sub.</summary>
 53    public string MessageId => Message.MessageId;
 54
 55    /// <summary>The exception thrown by the background handler.</summary>
 56    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>
 291868    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>
 28276    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>
 25085    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>
 123290    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>
 6497    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>
 4014123    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>
 2370140    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>
 2350151    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>
 2344159    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    {
 84169        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundWorkerCount);
 82170        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundQueueCapacity);
 171
 80172        if (backgroundDrainTimeout is { } timeout && timeout <= TimeSpan.Zero)
 2173            throw new ArgumentOutOfRangeException(nameof(backgroundDrainTimeout), timeout, "Drain timeout must be positi
 174
 78175        AckMode = GooglePubSubAckMode.AckAfterEnqueue;
 78176        BackgroundWorkerCount = backgroundWorkerCount;
 78177        BackgroundQueueCapacity = backgroundQueueCapacity;
 178
 78179        if (backgroundDrainTimeout is not null)
 38180            BackgroundDrainTimeout = backgroundDrainTimeout.Value;
 181
 78182        return this;
 183    }
 184}