< Summary

Information
Class: AsiBackbone.Core.Outbox.AsiBackboneGovernanceOutboxDrain
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Outbox/AsiBackboneGovernanceOutboxDrain.cs
Line coverage
98%
Covered lines: 280
Uncovered lines: 3
Coverable lines: 283
Total lines: 552
Line coverage: 98.9%
Branch coverage
92%
Covered branches: 100
Total branches: 108
Branch coverage: 92.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%66100%
.cctor()100%11100%
DrainAsync()100%88100%
DrainClaimedAsync()83.33%66100%
DrainEntriesAsync()100%44100%
DrainClaimsAsync()100%44100%
MergeEntries(...)91.66%121293.33%
MergeClaims(...)100%1212100%
DrainEntryAsync()100%11100%
DrainClaimAsync()100%1188.23%
ApplyEmissionResultAsync()90%2020100%
ApplyEmissionResultAsync()85%2020100%
ApplyFailureAsync()100%22100%
ApplyClaimFailureAsync()100%22100%
ShouldDeadLetter(...)100%22100%
CreateMaxRetryError(...)100%11100%
LogEmissionException(...)100%11100%
CreateExceptionError(...)100%11100%
GetRetryUtc(...)100%11100%
GetDeferredUtc(...)100%11100%
ResolveOptions(...)100%66100%
ResolveEmitterProvider(...)75%44100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Outbox/AsiBackboneGovernanceOutboxDrain.cs

#LineLine coverage
 1using AsiBackbone.Core.Emissions;
 2using Microsoft.Extensions.Logging;
 3using Microsoft.Extensions.Logging.Abstractions;
 4using Microsoft.Extensions.Options;
 5
 6namespace AsiBackbone.Core.Outbox;
 7
 8/// <summary>
 9/// Drains provider-neutral governance outbox entries through a configured governance emitter.
 10/// </summary>
 11/// <remarks>
 12/// This drain path is provider-neutral. It is suitable for tests, samples, local validation, and host-owned workers tha
 13/// </remarks>
 14/// <remarks>
 15/// Initializes a new instance of the <see cref="AsiBackboneGovernanceOutboxDrain" /> class.
 16/// </remarks>
 17/// <param name="outboxStore">The provider-neutral outbox store.</param>
 18/// <param name="emitter">The provider-neutral governance emitter.</param>
 19/// <param name="logger">The logger used to record local operational diagnostics for drain failures.</param>
 20/// <param name="outboxOptions">The provider-neutral retry, poison-message, and claim options used by the drain.</param>
 10821public sealed class AsiBackboneGovernanceOutboxDrain(
 10822    IAsiBackboneGovernanceOutboxStore outboxStore,
 10823    IAsiBackboneGovernanceEmitter emitter,
 10824    ILogger<AsiBackboneGovernanceOutboxDrain>? logger = null,
 10825    IOptions<AsiBackboneGovernanceOutboxOptions>? outboxOptions = null)
 26{
 327    private static readonly Action<ILogger, string, int, string, DateTimeOffset, string?, string?, Exception?> LogGovern
 328        LogLevel.Warning,
 329        new EventId(19701, nameof(LogGovernanceEmissionException)),
 330        "Governance outbox emission threw an exception for outbox entry {OutboxEntryId} on attempt {AttemptCount}. Emitt
 31
 11632    private readonly IAsiBackboneGovernanceOutboxStore outboxStore = outboxStore ?? throw new ArgumentNullException(name
 11433    private readonly IAsiBackboneGovernanceEmitter emitter = emitter ?? throw new ArgumentNullException(nameof(emitter))
 11234    private readonly ILogger<AsiBackboneGovernanceOutboxDrain> logger = logger ?? NullLogger<AsiBackboneGovernanceOutbox
 11235    private readonly AsiBackboneGovernanceOutboxOptions retryOptions = ResolveOptions(outboxOptions);
 36
 37    /// <summary>
 38    /// Drains pending and retry-ready outbox entries through the configured emitter.
 39    /// </summary>
 40    /// <param name="utcNow">The UTC timestamp used for retry-ready checks.</param>
 41    /// <param name="maxCount">The maximum number of entries to drain.</param>
 42    /// <param name="cancellationToken">A cancellation token.</param>
 43    /// <returns>The updated outbox entries that were attempted by the drain.</returns>
 44    public async ValueTask<IReadOnlyList<GovernanceOutboxEntry>> DrainAsync(
 45        DateTimeOffset? utcNow = null,
 46        int maxCount = 100,
 47        CancellationToken cancellationToken = default)
 48    {
 11249        if (maxCount <= 0)
 50        {
 251            throw new ArgumentOutOfRangeException(nameof(maxCount), maxCount, "Maximum count must be greater than zero."
 52        }
 53
 11054        cancellationToken.ThrowIfCancellationRequested();
 55
 11056        DateTimeOffset drainUtc = (utcNow ?? DateTimeOffset.UtcNow).ToUniversalTime();
 57
 11058        if (retryOptions.UseClaimLeases)
 59        {
 4260            return await DrainClaimedAsync(drainUtc, maxCount, cancellationToken).ConfigureAwait(false);
 61        }
 62
 6863        IReadOnlyList<GovernanceOutboxEntry> pendingEntries = await outboxStore
 6864            .FindPendingAsync(maxCount, cancellationToken)
 6865            .ConfigureAwait(false);
 66
 6567        if (pendingEntries.Count >= maxCount)
 68        {
 1269            return await DrainEntriesAsync(pendingEntries, drainUtc, cancellationToken).ConfigureAwait(false);
 70        }
 71
 5372        IReadOnlyList<GovernanceOutboxEntry> retryReadyEntries = await outboxStore
 5373            .FindRetryReadyAsync(drainUtc, maxCount - pendingEntries.Count, cancellationToken)
 5374            .ConfigureAwait(false);
 75
 5376        IReadOnlyList<GovernanceOutboxEntry> entriesToDrain = MergeEntries(pendingEntries, retryReadyEntries, maxCount);
 5377        return await DrainEntriesAsync(entriesToDrain, drainUtc, cancellationToken).ConfigureAwait(false);
 9978    }
 79
 80    private async ValueTask<IReadOnlyList<GovernanceOutboxEntry>> DrainClaimedAsync(
 81        DateTimeOffset drainUtc,
 82        int maxCount,
 83        CancellationToken cancellationToken)
 84    {
 4285        if (outboxStore is not IAsiBackboneGovernanceOutboxClaimStore claimStore)
 86        {
 487            throw new InvalidOperationException("Claim leases are enabled, but the configured outbox store does not impl
 88        }
 89
 3890        string workerId = retryOptions.ClaimWorkerId ?? throw new InvalidOperationException("ClaimWorkerId is required w
 3891        var pendingRequest = GovernanceOutboxClaimRequest.Create(
 3892            workerId,
 3893            drainUtc,
 3894            retryOptions.ClaimLeaseDuration,
 3895            maxCount);
 3896        IReadOnlyList<GovernanceOutboxClaim> pendingClaims = await claimStore
 3897            .ClaimPendingAsync(pendingRequest, cancellationToken)
 3898            .ConfigureAwait(false);
 99
 38100        if (pendingClaims.Count >= maxCount)
 101        {
 4102            return await DrainClaimsAsync(claimStore, pendingClaims, drainUtc, cancellationToken).ConfigureAwait(false);
 103        }
 104
 34105        var retryRequest = GovernanceOutboxClaimRequest.Create(
 34106            workerId,
 34107            drainUtc,
 34108            retryOptions.ClaimLeaseDuration,
 34109            maxCount - pendingClaims.Count);
 34110        IReadOnlyList<GovernanceOutboxClaim> retryReadyClaims = await claimStore
 34111            .ClaimRetryReadyAsync(retryRequest, cancellationToken)
 34112            .ConfigureAwait(false);
 113
 34114        IReadOnlyList<GovernanceOutboxClaim> claimsToDrain = MergeClaims(pendingClaims, retryReadyClaims, maxCount);
 34115        return await DrainClaimsAsync(claimStore, claimsToDrain, drainUtc, cancellationToken).ConfigureAwait(false);
 38116    }
 117
 118    private async ValueTask<IReadOnlyList<GovernanceOutboxEntry>> DrainEntriesAsync(
 119        IReadOnlyList<GovernanceOutboxEntry> entriesToDrain,
 120        DateTimeOffset drainUtc,
 121        CancellationToken cancellationToken)
 122    {
 65123        if (entriesToDrain.Count == 0)
 124        {
 10125            return Array.Empty<GovernanceOutboxEntry>();
 126        }
 127
 55128        List<GovernanceOutboxEntry> updatedEntries = new(entriesToDrain.Count);
 129
 240130        foreach (GovernanceOutboxEntry entry in entriesToDrain)
 131        {
 67132            cancellationToken.ThrowIfCancellationRequested();
 67133            GovernanceOutboxEntry updatedEntry = await DrainEntryAsync(entry, drainUtc, cancellationToken).ConfigureAwai
 63134            updatedEntries.Add(updatedEntry);
 135        }
 136
 51137        return updatedEntries;
 61138    }
 139
 140    private async ValueTask<IReadOnlyList<GovernanceOutboxEntry>> DrainClaimsAsync(
 141        IAsiBackboneGovernanceOutboxClaimStore claimStore,
 142        IReadOnlyList<GovernanceOutboxClaim> claimsToDrain,
 143        DateTimeOffset drainUtc,
 144        CancellationToken cancellationToken)
 145    {
 38146        if (claimsToDrain.Count == 0)
 147        {
 4148            return Array.Empty<GovernanceOutboxEntry>();
 149        }
 150
 34151        List<GovernanceOutboxEntry> updatedEntries = new(claimsToDrain.Count);
 152
 136153        foreach (GovernanceOutboxClaim claim in claimsToDrain)
 154        {
 34155            cancellationToken.ThrowIfCancellationRequested();
 34156            GovernanceOutboxEntry updatedEntry = await DrainClaimAsync(claimStore, claim, drainUtc, cancellationToken).C
 34157            updatedEntries.Add(updatedEntry);
 158        }
 159
 34160        return updatedEntries;
 38161    }
 162
 163    private static IReadOnlyList<GovernanceOutboxEntry> MergeEntries(
 164        IReadOnlyList<GovernanceOutboxEntry> pendingEntries,
 165        IReadOnlyList<GovernanceOutboxEntry> retryReadyEntries,
 166        int maxCount)
 167    {
 53168        if (pendingEntries.Count == 0)
 169        {
 12170            return retryReadyEntries;
 171        }
 172
 41173        if (retryReadyEntries.Count == 0)
 174        {
 37175            return pendingEntries;
 176        }
 177
 4178        var entriesToDrain = new List<GovernanceOutboxEntry>(Math.Min(maxCount, pendingEntries.Count + retryReadyEntries
 4179        var existingEntryIds = new HashSet<string>(pendingEntries.Count + retryReadyEntries.Count, StringComparer.Ordina
 180
 16181        foreach (GovernanceOutboxEntry pendingEntry in pendingEntries)
 182        {
 4183            _ = existingEntryIds.Add(pendingEntry.OutboxEntryId);
 4184            entriesToDrain.Add(pendingEntry);
 185        }
 186
 20187        foreach (GovernanceOutboxEntry retryReadyEntry in retryReadyEntries)
 188        {
 6189            if (entriesToDrain.Count >= maxCount)
 190            {
 0191                break;
 192            }
 193
 6194            if (existingEntryIds.Add(retryReadyEntry.OutboxEntryId))
 195            {
 4196                entriesToDrain.Add(retryReadyEntry);
 197            }
 198        }
 199
 4200        return entriesToDrain;
 201    }
 202
 203    private static IReadOnlyList<GovernanceOutboxClaim> MergeClaims(
 204        IReadOnlyList<GovernanceOutboxClaim> pendingClaims,
 205        IReadOnlyList<GovernanceOutboxClaim> retryReadyClaims,
 206        int maxCount)
 207    {
 48208        if (pendingClaims.Count == 0)
 209        {
 14210            return retryReadyClaims;
 211        }
 212
 34213        if (retryReadyClaims.Count == 0)
 214        {
 26215            return pendingClaims;
 216        }
 217
 8218        var claimsToDrain = new List<GovernanceOutboxClaim>(Math.Min(maxCount, pendingClaims.Count + retryReadyClaims.Co
 8219        var existingEntryIds = new HashSet<string>(pendingClaims.Count + retryReadyClaims.Count, StringComparer.Ordinal)
 220
 36221        foreach (GovernanceOutboxClaim pendingClaim in pendingClaims)
 222        {
 10223            _ = existingEntryIds.Add(pendingClaim.OutboxEntryId);
 10224            claimsToDrain.Add(pendingClaim);
 225        }
 226
 70227        foreach (GovernanceOutboxClaim retryReadyClaim in retryReadyClaims)
 228        {
 28229            if (claimsToDrain.Count >= maxCount)
 230            {
 2231                break;
 232            }
 233
 26234            if (existingEntryIds.Add(retryReadyClaim.OutboxEntryId))
 235            {
 14236                claimsToDrain.Add(retryReadyClaim);
 237            }
 238        }
 239
 8240        return claimsToDrain;
 241    }
 242
 243    private async ValueTask<GovernanceOutboxEntry> DrainEntryAsync(
 244        GovernanceOutboxEntry entry,
 245        DateTimeOffset drainUtc,
 246        CancellationToken cancellationToken)
 247    {
 248        GovernanceEmissionResult result;
 249
 250        try
 251        {
 67252            result = await emitter.EmitAsync(entry.Envelope, cancellationToken).ConfigureAwait(false);
 58253        }
 2254        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 255        {
 2256            throw;
 257        }
 7258        catch (Exception ex)
 259        {
 7260            DateTimeOffset nextRetryUtc = GetRetryUtc(drainUtc);
 7261            LogEmissionException(entry, nextRetryUtc, ex);
 7262            GovernanceEmissionError governanceEmissionError = CreateExceptionError(ex);
 263
 7264            return await ApplyFailureAsync(
 7265                entry,
 7266                governanceEmissionError,
 7267                nextRetryUtc,
 7268                cancellationToken)
 7269                .ConfigureAwait(false);
 270        }
 271
 58272        return await ApplyEmissionResultAsync(entry, result, drainUtc, cancellationToken).ConfigureAwait(false);
 63273    }
 274
 275    private async ValueTask<GovernanceOutboxEntry> DrainClaimAsync(
 276        IAsiBackboneGovernanceOutboxClaimStore claimStore,
 277        GovernanceOutboxClaim claim,
 278        DateTimeOffset drainUtc,
 279        CancellationToken cancellationToken)
 280    {
 281        GovernanceEmissionResult result;
 282
 283        try
 284        {
 34285            result = await emitter.EmitAsync(claim.Entry.Envelope, cancellationToken).ConfigureAwait(false);
 30286        }
 0287        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 288        {
 0289            throw;
 290        }
 4291        catch (Exception ex)
 292        {
 4293            DateTimeOffset nextRetryUtc = GetRetryUtc(drainUtc);
 4294            LogEmissionException(claim.Entry, nextRetryUtc, ex);
 4295            GovernanceEmissionError governanceEmissionError = CreateExceptionError(ex);
 296
 4297            return await ApplyClaimFailureAsync(
 4298                claimStore,
 4299                claim,
 4300                governanceEmissionError,
 4301                nextRetryUtc,
 4302                cancellationToken)
 4303                .ConfigureAwait(false);
 304        }
 305
 30306        return await ApplyEmissionResultAsync(claimStore, claim, result, drainUtc, cancellationToken).ConfigureAwait(fal
 34307    }
 308
 309    private async ValueTask<GovernanceOutboxEntry> ApplyEmissionResultAsync(
 310        GovernanceOutboxEntry entry,
 311        GovernanceEmissionResult result,
 312        DateTimeOffset drainUtc,
 313        CancellationToken cancellationToken)
 314    {
 58315        ArgumentNullException.ThrowIfNull(result);
 316
 58317        if (result.IsSuccess)
 318        {
 22319            return await outboxStore.MarkDeliveredAsync(entry.OutboxEntryId, result, cancellationToken).ConfigureAwait(f
 320        }
 321
 36322        if (result.Status is GovernanceEmissionStatus.DeadLettered)
 323        {
 4324            GovernanceEmissionError governanceEmissionError = result.Error ?? GovernanceEmissionError.Create(
 4325                "emission.deadlettered",
 4326                "Governance emission returned a dead-lettered result.",
 4327                providerName: result.ProviderName);
 328
 4329            return await outboxStore.MarkDeadLetteredAsync(
 4330                entry.OutboxEntryId,
 4331                governanceEmissionError,
 4332                governanceEmissionError.Message,
 4333                cancellationToken)
 4334                .ConfigureAwait(false);
 335        }
 336
 32337        if (result.Status is GovernanceEmissionStatus.Deferred or GovernanceEmissionStatus.Pending)
 338        {
 14339            GovernanceEmissionError? governanceEmissionError = result.Error ?? (result.Status is GovernanceEmissionStatu
 14340                ? GovernanceEmissionError.Create(
 14341                    "emission.pending",
 14342                    "Governance emission remained pending after the outbox drain attempt.",
 14343                    isRetryable: true,
 14344                    providerName: result.ProviderName)
 14345                : null);
 346
 14347            GovernanceOutboxEntry deferredEntry = entry.MarkDeferred(
 14348                governanceEmissionError,
 14349                result.RetryAfterUtc ?? GetDeferredUtc(drainUtc),
 14350                drainUtc);
 351
 14352            return await outboxStore.SaveAsync(deferredEntry, cancellationToken).ConfigureAwait(false);
 353        }
 354
 18355        GovernanceEmissionError failure = result.Error ?? GovernanceEmissionError.Create(
 18356            "emission.failed",
 18357            "Governance emission returned a failed result without provider-neutral error details.",
 18358            isRetryable: result.ShouldRetry,
 18359            providerName: result.ProviderName);
 360
 18361        return await ApplyFailureAsync(
 18362            entry,
 18363            failure,
 18364            result.RetryAfterUtc,
 18365            cancellationToken)
 18366            .ConfigureAwait(false);
 56367    }
 368
 369    private async ValueTask<GovernanceOutboxEntry> ApplyEmissionResultAsync(
 370        IAsiBackboneGovernanceOutboxClaimStore claimStore,
 371        GovernanceOutboxClaim claim,
 372        GovernanceEmissionResult result,
 373        DateTimeOffset drainUtc,
 374        CancellationToken cancellationToken)
 375    {
 30376        ArgumentNullException.ThrowIfNull(result);
 377
 30378        if (result.IsSuccess)
 379        {
 10380            return await claimStore.MarkClaimDeliveredAsync(claim, result, cancellationToken).ConfigureAwait(false);
 381        }
 382
 20383        if (result.Status is GovernanceEmissionStatus.DeadLettered)
 384        {
 4385            GovernanceEmissionError governanceEmissionError = result.Error ?? GovernanceEmissionError.Create(
 4386                "emission.deadlettered",
 4387                "Governance emission returned a dead-lettered result.",
 4388                providerName: result.ProviderName);
 389
 4390            return await claimStore.MarkClaimDeadLetteredAsync(
 4391                claim,
 4392                governanceEmissionError,
 4393                governanceEmissionError.Message,
 4394                cancellationToken)
 4395                .ConfigureAwait(false);
 396        }
 397
 16398        if (result.Status is GovernanceEmissionStatus.Deferred or GovernanceEmissionStatus.Pending)
 399        {
 8400            GovernanceEmissionError? governanceEmissionError = result.Error ?? (result.Status is GovernanceEmissionStatu
 8401                ? GovernanceEmissionError.Create(
 8402                    "emission.pending",
 8403                    "Governance emission remained pending after the outbox drain attempt.",
 8404                    isRetryable: true,
 8405                    providerName: result.ProviderName)
 8406                : null);
 407
 8408            GovernanceOutboxEntry deferredEntry = claim.Entry.MarkDeferred(
 8409                governanceEmissionError,
 8410                result.RetryAfterUtc ?? GetDeferredUtc(drainUtc),
 8411                drainUtc);
 412
 8413            return await claimStore.SaveClaimAsync(claim, deferredEntry, cancellationToken).ConfigureAwait(false);
 414        }
 415
 8416        GovernanceEmissionError failure = result.Error ?? GovernanceEmissionError.Create(
 8417            "emission.failed",
 8418            "Governance emission returned a failed result without provider-neutral error details.",
 8419            isRetryable: result.ShouldRetry,
 8420            providerName: result.ProviderName);
 421
 8422        return await ApplyClaimFailureAsync(
 8423            claimStore,
 8424            claim,
 8425            failure,
 8426            result.RetryAfterUtc,
 8427            cancellationToken)
 8428            .ConfigureAwait(false);
 30429    }
 430
 431    private async ValueTask<GovernanceOutboxEntry> ApplyFailureAsync(
 432        GovernanceOutboxEntry entry,
 433        GovernanceEmissionError failure,
 434        DateTimeOffset? nextRetryUtc,
 435        CancellationToken cancellationToken)
 436    {
 25437        if (ShouldDeadLetter(entry))
 438        {
 2439            GovernanceEmissionError deadLetterError = CreateMaxRetryError(failure);
 2440            return await outboxStore.MarkDeadLetteredAsync(
 2441                entry.OutboxEntryId,
 2442                deadLetterError,
 2443                retryOptions.DeadLetterReasonMessage,
 2444                cancellationToken)
 2445                .ConfigureAwait(false);
 446        }
 447
 23448        return await outboxStore.MarkFailedAsync(
 23449            entry.OutboxEntryId,
 23450            failure,
 23451            nextRetryUtc,
 23452            cancellationToken)
 23453            .ConfigureAwait(false);
 25454    }
 455
 456    private async ValueTask<GovernanceOutboxEntry> ApplyClaimFailureAsync(
 457        IAsiBackboneGovernanceOutboxClaimStore claimStore,
 458        GovernanceOutboxClaim claim,
 459        GovernanceEmissionError failure,
 460        DateTimeOffset? nextRetryUtc,
 461        CancellationToken cancellationToken)
 462    {
 12463        if (ShouldDeadLetter(claim.Entry))
 464        {
 2465            GovernanceEmissionError deadLetterError = CreateMaxRetryError(failure);
 2466            return await claimStore.MarkClaimDeadLetteredAsync(
 2467                claim,
 2468                deadLetterError,
 2469                retryOptions.DeadLetterReasonMessage,
 2470                cancellationToken)
 2471                .ConfigureAwait(false);
 472        }
 473
 10474        return await claimStore.MarkClaimFailedAsync(
 10475            claim,
 10476            failure,
 10477            nextRetryUtc,
 10478            cancellationToken)
 10479            .ConfigureAwait(false);
 12480    }
 481
 482    private bool ShouldDeadLetter(GovernanceOutboxEntry entry)
 483    {
 37484        return retryOptions.DeadLetterOnMaxRetryAttempts
 37485            && entry.RetryCount + 1 >= retryOptions.MaxRetryAttempts;
 486    }
 487
 488    private GovernanceEmissionError CreateMaxRetryError(GovernanceEmissionError failure)
 489    {
 4490        return GovernanceEmissionError.Create(
 4491            retryOptions.DeadLetterReasonCode,
 4492            retryOptions.DeadLetterReasonMessage,
 4493            providerName: failure.ProviderName,
 4494            providerErrorCode: failure.Code);
 495    }
 496
 497    private void LogEmissionException(GovernanceOutboxEntry entry, DateTimeOffset nextRetryUtc, Exception exception)
 498    {
 11499        LogGovernanceEmissionException(
 11500            logger,
 11501            entry.OutboxEntryId,
 11502            entry.RetryCount + 1,
 11503            ResolveEmitterProvider(entry),
 11504            nextRetryUtc,
 11505            entry.Envelope.CorrelationId,
 11506            entry.Envelope.AuditResidueId,
 11507            exception);
 11508    }
 509
 510    private static GovernanceEmissionError CreateExceptionError(Exception exception)
 511    {
 11512        return GovernanceEmissionError.Create(
 11513            "emission.exception",
 11514            $"Governance emission threw {exception.GetType().Name} during outbox drain.",
 11515            isRetryable: true,
 11516            providerErrorCode: exception.GetType().FullName);
 517    }
 518
 519    private DateTimeOffset GetRetryUtc(DateTimeOffset drainUtc)
 520    {
 11521        return drainUtc.Add(retryOptions.RetryDelay);
 522    }
 523
 524    private DateTimeOffset GetDeferredUtc(DateTimeOffset drainUtc)
 525    {
 12526        return drainUtc.Add(retryOptions.DeferredDelay);
 527    }
 528
 529    private static AsiBackboneGovernanceOutboxOptions ResolveOptions(IOptions<AsiBackboneGovernanceOutboxOptions>? optio
 530    {
 112531        AsiBackboneGovernanceOutboxOptions resolved = options?.Value ?? new AsiBackboneGovernanceOutboxOptions();
 112532        resolved.Validate();
 533
 108534        return new AsiBackboneGovernanceOutboxOptions
 108535        {
 108536            RetryDelay = resolved.RetryDelay,
 108537            DeferredDelay = resolved.DeferredDelay,
 108538            MaxRetryAttempts = resolved.MaxRetryAttempts,
 108539            DeadLetterOnMaxRetryAttempts = resolved.DeadLetterOnMaxRetryAttempts,
 108540            DeadLetterReasonCode = resolved.DeadLetterReasonCode.Trim(),
 108541            DeadLetterReasonMessage = resolved.DeadLetterReasonMessage.Trim(),
 108542            UseClaimLeases = resolved.UseClaimLeases,
 108543            ClaimWorkerId = string.IsNullOrWhiteSpace(resolved.ClaimWorkerId) ? null : resolved.ClaimWorkerId.Trim(),
 108544            ClaimLeaseDuration = resolved.ClaimLeaseDuration
 108545        };
 546    }
 547
 548    private static string ResolveEmitterProvider(GovernanceOutboxEntry entry)
 549    {
 11550        return entry.Envelope.EmitterProvider ?? entry.ProviderName ?? "unspecified";
 551    }
 552}

Methods/Properties

.ctor(AsiBackbone.Core.Outbox.IAsiBackboneGovernanceOutboxStore,AsiBackbone.Core.Emissions.IAsiBackboneGovernanceEmitter,Microsoft.Extensions.Logging.ILogger`1<AsiBackbone.Core.Outbox.AsiBackboneGovernanceOutboxDrain>,Microsoft.Extensions.Options.IOptions`1<AsiBackbone.Core.Outbox.AsiBackboneGovernanceOutboxOptions>)
.cctor()
DrainAsync()
DrainClaimedAsync()
DrainEntriesAsync()
DrainClaimsAsync()
MergeEntries(System.Collections.Generic.IReadOnlyList`1<AsiBackbone.Core.Outbox.GovernanceOutboxEntry>,System.Collections.Generic.IReadOnlyList`1<AsiBackbone.Core.Outbox.GovernanceOutboxEntry>,System.Int32)
MergeClaims(System.Collections.Generic.IReadOnlyList`1<AsiBackbone.Core.Outbox.GovernanceOutboxClaim>,System.Collections.Generic.IReadOnlyList`1<AsiBackbone.Core.Outbox.GovernanceOutboxClaim>,System.Int32)
DrainEntryAsync()
DrainClaimAsync()
ApplyEmissionResultAsync()
ApplyEmissionResultAsync()
ApplyFailureAsync()
ApplyClaimFailureAsync()
ShouldDeadLetter(AsiBackbone.Core.Outbox.GovernanceOutboxEntry)
CreateMaxRetryError(AsiBackbone.Core.Emissions.GovernanceEmissionError)
LogEmissionException(AsiBackbone.Core.Outbox.GovernanceOutboxEntry,System.DateTimeOffset,System.Exception)
CreateExceptionError(System.Exception)
GetRetryUtc(System.DateTimeOffset)
GetDeferredUtc(System.DateTimeOffset)
ResolveOptions(Microsoft.Extensions.Options.IOptions`1<AsiBackbone.Core.Outbox.AsiBackboneGovernanceOutboxOptions>)
ResolveEmitterProvider(AsiBackbone.Core.Outbox.GovernanceOutboxEntry)