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

Information
Class: AsyncResponse.Transports.MongoDB.MongoDbBackgroundFailureContext
Assembly: AsyncResponse.Transports.MongoDB
File(s): /_/src/Transports/AsyncResponse.Transports.MongoDB/MongoDbAsyncResponseTransportOptions.cs
Line coverage
91%
Covered lines: 11
Uncovered lines: 1
Coverable lines: 12
Total lines: 252
Line coverage: 91.6%
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_Attempt()100%210%
get_CorrelationId()100%11100%
get_Exception()100%11100%

File(s)

/_/src/Transports/AsyncResponse.Transports.MongoDB/MongoDbAsyncResponseTransportOptions.cs

#LineLine coverage
 1namespace AsyncResponse.Transports.MongoDB;
 2
 3/// <summary>Options for the MongoDB AsyncResponse transport.</summary>
 4public sealed class MongoDbAsyncResponseTransportOptions
 5{
 6    /// <summary>The transport name reported to reply targets and the startup validator.</summary>
 7    public const string TransportName = "MongoDB";
 8
 9    /// <summary>
 10    /// Optional MongoDB connection string used when no <c>IMongoDatabase</c> or <c>IMongoClient</c>
 11    /// is registered with the host.
 12    /// </summary>
 13    public string? ConnectionString { get; set; }
 14
 15    /// <summary>Optional database name used when no <c>IMongoDatabase</c> is registered.</summary>
 16    public string? DatabaseName { get; set; }
 17
 18    /// <summary>Collection storing worker, response-ingress, and dead-letter queue documents.</summary>
 19    public string MessageCollection { get; set; } = "asyncresponse_transport_messages";
 20
 21    /// <summary>Creates the claim and housekeeping indexes on first use. Disable when provisioning owns index DDL.</sum
 22    public bool AutoCreateIndexes { get; set; } = true;
 23
 24    /// <summary>
 25    /// Claims the queue collection in the persisted cross-component ownership ledger
 26    /// (<c>asyncresponse_ownership</c>) at first use, so another AsyncResponse component — in
 27    /// this or any other process — misconfigured onto the same collection fails startup instead
 28    /// of silently corrupting data. Independent of <see cref="AutoCreateIndexes"/>. Disable only
 29    /// for least-privilege deployments that cannot write the ledger collection. Default: <c>true</c>.
 30    /// </summary>
 31    public bool UseOwnershipLedger { get; set; } = true;
 32
 33    /// <summary>
 34    /// Wakes idle subscribers with a change stream watching inserts to the queue collection.
 35    /// Requires a replica set; when disabled — or when the server reports change streams as
 36    /// unsupported — subscribers fall back to <see cref="MongoDbSubscriberOptions.EmptyPollDelay"/>
 37    /// polling. Default: <c>true</c>.
 38    /// </summary>
 39    public bool UseChangeStreamWake { get; set; } = true;
 40
 41    /// <summary>Logical queue name used by <see cref="MongoDbWorkerTransport"/>.</summary>
 42    public string WorkerQueue { get; set; } = "worker";
 43
 44    /// <summary>Worker queue handling options.</summary>
 45    public MongoDbSubscriberOptions WorkerSubscriber { get; } = new();
 46
 47    /// <summary>Logical queue name consumed by the hosted response-ingress subscriber.</summary>
 48    public string ResponseQueue { get; set; } = "response";
 49
 50    /// <summary>Response queue handling options.</summary>
 51    public MongoDbSubscriberOptions ResponseSubscriber { get; } = new();
 52
 53    /// <summary>Logical queue name that receives poison messages and already-ACKed background failures.</summary>
 54    public string DeadLetterQueue { get; set; } = "deadletter";
 55
 56    /// <summary>Enables dead-lettering when a message exhausts attempts or fails after early ACK.</summary>
 57    public bool DeadLetterEnabled { get; set; } = true;
 58
 59    /// <summary>
 60    /// How long dead-letter documents are retained before being pruned opportunistically. The
 61    /// dead-letter queue has no consumer by default, so without a retention its documents accumulate
 62    /// indefinitely. Leave <c>null</c> (the default) to keep them forever for manual inspection.
 63    /// </summary>
 64    public TimeSpan? DeadLetterRetention { get; set; }
 65
 66    /// <summary>How long a claimed document remains locked before another subscriber may retry it.</summary>
 67    public TimeSpan LockTimeout { get; set; } = TimeSpan.FromSeconds(30);
 68
 69    /// <summary>The logical reply target name used by <c>WithReplyTarget()</c>. Default: <c>default</c>.</summary>
 70    public string DefaultReplyTargetName { get; set; } = "default";
 71
 72    /// <summary>
 73    /// Named reply targets exposed to Core through <see cref="IAsyncResponseReplyTargetProvider"/>.
 74    /// When empty, <see cref="ResponseQueue"/> becomes the default target.
 75    /// </summary>
 76    public Dictionary<string, MongoDbReplyTargetOptions> ReplyTargets { get; } = new(StringComparer.Ordinal);
 77
 78    /// <summary>
 79    /// Header key stored in the document metadata for the AsyncResponse correlation id. Response
 80    /// messages may omit it when the id is present in the JSON body via <see cref="CorrelationIdJsonPaths"/>.
 81    /// </summary>
 82    public string CorrelationIdHeader { get; set; } = "AR-Correlation-Id";
 83
 84    /// <summary>
 85    /// JSON paths inspected when a response message does not carry the correlation id in
 86    /// <see cref="CorrelationIdHeader"/>. Paths are case-insensitive and support nested JSON strings.
 87    /// </summary>
 88    public string[] CorrelationIdJsonPaths { get; set; } =
 89    [
 90        "CorrelationId",
 91        "CustomParameters",
 92        "CustomParameters.CorrelationId",
 93        "PubSubParams.CustomParameters",
 94        "PubSubParams.CustomParameters.CorrelationId",
 95        "DagJsonParameters.CorrelationId"
 96    ];
 97
 98    /// <summary>Maximum attempts for MongoDB publish commands. Set to 1 to disable publish retries.</summary>
 99    public int PublishMaxAttempts { get; set; } = 3;
 100
 101    /// <summary>Initial delay before retrying a failed publish command.</summary>
 102    public TimeSpan PublishRetryBaseDelay { get; set; } = TimeSpan.FromMilliseconds(50);
 103
 104    /// <summary>Maximum delay between publish retry attempts.</summary>
 105    public TimeSpan PublishRetryMaxDelay { get; set; } = TimeSpan.FromSeconds(1);
 106
 107    /// <summary>Initial delay after a subscriber loop failure.</summary>
 108    public TimeSpan SubscriberRetryBaseDelay { get; set; } = TimeSpan.FromMilliseconds(100);
 109
 110    /// <summary>Maximum delay after repeated subscriber loop failures.</summary>
 111    public TimeSpan SubscriberRetryMaxDelay { get; set; } = TimeSpan.FromSeconds(5);
 112
 113    /// <summary>
 114    /// Bounds the wait for the change-stream listen task to join while a hosted subscriber stops.
 115    /// The join completes in milliseconds when healthy; when it does not, the task is abandoned
 116    /// anyway, so keep this short — it counts against the host's shutdown budget. Default: <c>5s</c>.
 117    /// </summary>
 118    public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
 119
 120    /// <summary>
 121    /// The hosting shutdown budget that must contain MongoDB subscriber shutdown plus
 122    /// <see cref="MongoDbSubscriberOptions.BackgroundDrainTimeout"/> when a subscriber uses
 123    /// <see cref="MongoDbAckMode.AckAfterEnqueue"/>. Defaults to the Generic Host default of
 124    /// 30 seconds. Set to <c>null</c> only when this budget is validated externally.
 125    /// </summary>
 126    public TimeSpan? HostShutdownTimeout { get; set; } = TimeSpan.FromSeconds(30);
 127
 128    /// <summary>Adds or replaces a named MongoDB reply target.</summary>
 129    public MongoDbAsyncResponseTransportOptions AddReplyTarget(string name, string responseQueue)
 130    {
 131        ArgumentException.ThrowIfNullOrWhiteSpace(name);
 132        ArgumentException.ThrowIfNullOrWhiteSpace(responseQueue);
 133
 134        ReplyTargets[name] = new MongoDbReplyTargetOptions { ResponseQueue = responseQueue };
 135        return this;
 136    }
 137}
 138
 139/// <summary>Options for one named MongoDB async-response reply target.</summary>
 140public sealed class MongoDbReplyTargetOptions
 141{
 142    /// <summary>Queue remote systems should insert response payload documents into.</summary>
 143    public string? ResponseQueue { get; set; }
 144
 145    /// <summary>Additional values copied to the transport-neutral reply target.</summary>
 146    public Dictionary<string, string> Properties { get; } = new(StringComparer.Ordinal);
 147}
 148
 149/// <summary>Controls when a MongoDB transport document is acknowledged relative to handling.</summary>
 150public enum MongoDbAckMode
 151{
 152    /// <summary>
 153    /// Delete the document only after the AsyncResponse handler completes successfully. Handler
 154    /// failures reschedule the document until <see cref="MongoDbSubscriberOptions.MaxDeliveryAttempts"/>
 155    /// is reached.
 156    /// </summary>
 157    AckAfterHandlerCompletes = 0,
 158
 159    /// <summary>
 160    /// Delete the document immediately after it is accepted into a bounded in-process background
 161    /// queue. Handler failures are logged, reported, and dead-lettered when enabled because the
 162    /// document has already been acknowledged.
 163    /// </summary>
 164    AckAfterEnqueue = 1
 165}
 166
 167/// <summary>Describes a handler failure that happened after a MongoDB document was already acknowledged.</summary>
 168public sealed class MongoDbBackgroundFailureContext
 169{
 18170    internal MongoDbBackgroundFailureContext(string queue, string subscriberRole, int attempt, string? correlationId, Ex
 171    {
 18172        Queue = queue;
 18173        SubscriberRole = subscriberRole;
 18174        Attempt = attempt;
 18175        CorrelationId = correlationId;
 18176        Exception = exception;
 18177    }
 178
 179    /// <summary>The logical queue the document came from.</summary>
 2180    public string Queue { get; }
 181
 182    /// <summary>The logical subscriber role, such as <c>Worker</c> or <c>ResponseIngress</c>.</summary>
 2183    public string SubscriberRole { get; }
 184
 185    /// <summary>The delivery attempt count for the document.</summary>
 0186    public int Attempt { get; }
 187
 188    /// <summary>The AsyncResponse correlation id, when one was available.</summary>
 2189    public string? CorrelationId { get; }
 190
 191    /// <summary>The exception thrown by the background handler.</summary>
 2192    public Exception Exception { get; }
 193}
 194
 195/// <summary>Per-queue MongoDB subscriber behavior.</summary>
 196public sealed class MongoDbSubscriberOptions
 197{
 198    /// <summary>Controls when a document is acknowledged. Defaults to <see cref="MongoDbAckMode.AckAfterHandlerComplete
 199    public MongoDbAckMode AckMode { get; set; } = MongoDbAckMode.AckAfterHandlerCompletes;
 200
 201    /// <summary>
 202    /// Maximum documents claimed per subscriber loop pass. In the default
 203    /// <see cref="MongoDbAckMode.AckAfterHandlerCompletes"/> mode the claimed documents are handled
 204    /// one at a time, so this bounds claim round-trips, not handler concurrency. Use
 205    /// <see cref="MongoDbAckMode.AckAfterEnqueue"/> (or run multiple subscriber instances) to
 206    /// process messages in parallel. Default: <c>16</c>.
 207    /// </summary>
 208    public int BatchSize { get; set; } = 16;
 209
 210    /// <summary>
 211    /// Maximum delivery attempts before a failing document is deleted and written to the dead-letter
 212    /// queue. <c>0</c> means unlimited retries. Default: <c>5</c>.
 213    /// </summary>
 214    public int MaxDeliveryAttempts { get; set; } = 5;
 215
 216    /// <summary>Delay before a failed document becomes available for redelivery. Default: <c>5s</c>.</summary>
 217    public TimeSpan RedeliveryDelay { get; set; } = TimeSpan.FromSeconds(5);
 218
 219    /// <summary>Delay after an empty poll before checking again. Default: <c>250ms</c>.</summary>
 220    public TimeSpan EmptyPollDelay { get; set; } = TimeSpan.FromMilliseconds(250);
 221
 222    /// <summary>Number of background workers used by <see cref="MongoDbAckMode.AckAfterEnqueue"/>.</summary>
 223    public int BackgroundWorkerCount { get; set; }
 224
 225    /// <summary>Maximum number of ACKed documents waiting in the background queue.</summary>
 226    public int BackgroundQueueCapacity { get; set; }
 227
 228    /// <summary>Maximum time to wait for queued/running background handlers while stopping.</summary>
 229    public TimeSpan BackgroundDrainTimeout { get; set; } = TimeSpan.FromSeconds(20);
 230
 231    /// <summary>Optional callback invoked when a background handler fails after the document was already acknowledged.<
 232    public Func<MongoDbBackgroundFailureContext, ValueTask>? OnBackgroundFailure { get; set; }
 233
 234    /// <summary>Explicitly opts this subscriber into ACK-after-enqueue behavior.</summary>
 235    public MongoDbSubscriberOptions UseAckAfterEnqueue(
 236        int backgroundWorkerCount,
 237        int backgroundQueueCapacity,
 238        TimeSpan? backgroundDrainTimeout = null)
 239    {
 240        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundWorkerCount);
 241        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backgroundQueueCapacity);
 242        if (backgroundDrainTimeout is { } timeout && timeout <= TimeSpan.Zero)
 243            throw new ArgumentOutOfRangeException(nameof(backgroundDrainTimeout), timeout, "Drain timeout must be positi
 244
 245        AckMode = MongoDbAckMode.AckAfterEnqueue;
 246        BackgroundWorkerCount = backgroundWorkerCount;
 247        BackgroundQueueCapacity = backgroundQueueCapacity;
 248        if (backgroundDrainTimeout is not null)
 249            BackgroundDrainTimeout = backgroundDrainTimeout.Value;
 250        return this;
 251    }
 252}