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

Information
Class: AsyncResponse.Channels.MongoDB.MongoDbAsyncResponseChannel
Assembly: AsyncResponse.Channels.MongoDB
File(s): /_/src/Channels/AsyncResponse.Channels.MongoDB/MongoDbAsyncResponseChannel.cs
Line coverage
68%
Covered lines: 31
Uncovered lines: 14
Coverable lines: 45
Total lines: 117
Line coverage: 68.8%
Branch coverage
80%
Covered branches: 8
Total branches: 10
Branch coverage: 80%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ChannelName(...)100%11100%
CurrentPollInterval()100%11100%
CurrentFullSweepInterval()75%44100%
StartWakeListener(...)100%22100%
CreateWaiter(...)100%11100%
ListenLoopAsync()100%3236.36%

File(s)

/_/src/Channels/AsyncResponse.Channels.MongoDB/MongoDbAsyncResponseChannel.cs

#LineLine coverage
 1// Binds the shared database-channel machinery (src/Channels/Shared/DbChannelShared.cs, compiled
 2// into this project) to this provider's concrete seam types. See the note atop the shared file.
 3global using DbChannelStore = AsyncResponse.Channels.MongoDB.MongoDbChannelStore;
 4global using DbChannelMessage = AsyncResponse.Channels.MongoDB.MongoDbChannelMessage;
 5global using DbChannelOptions = AsyncResponse.Channels.MongoDB.MongoDbAsyncResponseChannelOptions;
 6
 7using Microsoft.Extensions.DependencyInjection;
 8using Microsoft.Extensions.Logging;
 9using Microsoft.Extensions.Options;
 10
 11namespace AsyncResponse.Channels.MongoDB;
 12
 13/// <summary>
 14/// MongoDB-backed response channel using change streams for active waiter wakeups and TTL-indexed
 15/// collections for durable recovery state. Requires the server to run as a replica set (a
 16/// single-node replica set is sufficient) for change-stream wakes; without one the channel degrades
 17/// to interval polling.
 18/// </summary>
 19internal sealed class MongoDbAsyncResponseChannel : DbAsyncResponseChannelBase
 20{
 21    /// <summary>Creates a MongoDB-backed async-response channel.</summary>
 22    public MongoDbAsyncResponseChannel(
 23        IServiceScopeFactory scopeFactory,
 24        MongoDbChannelStore store,
 25        IRecoveryStateStore recoveryStateStore,
 26        IOptions<MongoDbAsyncResponseChannelOptions> options,
 27        AsyncResponseContextPropagation propagation,
 28        ILogger<MongoDbAsyncResponseChannel> logger,
 29        TimeProvider? timeProvider = null)
 50330        : base(
 50331            scopeFactory,
 50332            store,
 50333            recoveryStateStore,
 50334            options.Value,
 50335            propagation,
 50336            logger,
 50337            channelTypeName: nameof(MongoDbAsyncResponseChannel),
 50338            providerName: "MongoDB",
 50339            activityTag: "mongodb",
 50340            subscriberRecordNoun: "document",
 50341            localDispatchRetryHint: "listener retry will pick it up",
 50342            timeProvider)
 43    {
 50344    }
 45
 46    /// <inheritdoc />
 739047    protected override string ChannelName(string correlationId) => $"{_options.MessageCollection}:{correlationId}";
 48
 49    /// <inheritdoc />
 659750    protected override TimeSpan CurrentPollInterval() => _options.ListenerPollInterval;
 51
 52    // Set once the server reports change streams unsupported (a standalone). Read on every poll
 53    // tick, so volatile.
 54    private volatile bool _changeStreamsUnavailable;
 55
 56    /// <summary>
 57    /// The throttled sweep applies only while change streams carry normal delivery. With
 58    /// <see cref="MongoDbAsyncResponseChannelOptions.UseChangeStreams"/> off, or once the server
 59    /// has reported them unsupported, the sweep is the ONLY cross-process wake — and the 5s
 60    /// default equalled <c>DeliveryConfirmationTimeout</c>, so the publisher gave up, claimed the
 61    /// message for recovery and fired the lost-subscriber callback a beat before the healthy
 62    /// waiter's sweep found it. Every tick, as the <c>UseChangeStreams</c> doc promises.
 63    /// </summary>
 64    protected override TimeSpan? CurrentFullSweepInterval()
 324865        => _options.UseChangeStreams && !_changeStreamsUnavailable ? _options.FullSweepInterval : null;
 66
 67    /// <inheritdoc />
 68    protected override Task? StartWakeListener(CancellationToken cancellationToken)
 37869        => _options.UseChangeStreams
 35770            ? Task.Run(() => ListenLoopAsync(cancellationToken))
 37871            : Task.CompletedTask;
 72
 73    /// <inheritdoc />
 74    protected override IAsyncResponseWaiter<T> CreateWaiter<T>(Task<T> responseTask, Func<ValueTask> cleanupAsync)
 38775        => new MongoDbAsyncResponseWaiter<T>(responseTask, cleanupAsync);
 76
 77    private async Task ListenLoopAsync(CancellationToken cancellationToken)
 78    {
 35779        var failures = 0;
 35780        while (!cancellationToken.IsCancellationRequested)
 81        {
 82            try
 83            {
 35684                await _store.WatchMessagesAsync(payload =>
 35685                {
 47286                    SignalDispatcher(string.IsNullOrEmpty(payload) ? null : payload);
 47287                    return Task.CompletedTask;
 35688                }, cancellationToken).ConfigureAwait(false);
 089                failures = 0;
 090            }
 35691            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 92            {
 35693                return;
 94            }
 095            catch (Exception ex) when (MongoDbChannelStore.IsChangeStreamUnsupported(ex))
 96            {
 97                // Standalone server: change streams need a replica set. The dispatch loop's
 98                // ListenerPollInterval sweep still delivers, so degrade to polling instead of
 99                // retry-spamming an error the server will keep returning. Flagged so the sweep
 100                // throttle lifts (see CurrentFullSweepInterval): it is now the only wake.
 0101                _changeStreamsUnavailable = true;
 0102                _logger.LogWarning(
 0103                    ex,
 0104                    "MongoDB change streams are unavailable (the server is not a replica set); response wakes fall back 
 0105                    _options.ListenerPollInterval);
 0106                return;
 107            }
 0108            catch (Exception ex)
 109            {
 0110                failures++;
 0111                var delay = AsyncResponseRetry.Backoff(failures, TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(5)
 0112                _logger.LogWarning(ex, "MongoDB change-stream loop failed; retrying in {Delay}.", delay);
 0113                await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
 114            }
 115        }
 357116    }
 117}