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

Information
Class: AsyncResponse.Channels.MongoDB.MongoDbAsyncResponseChannel
Assembly: AsyncResponse.Channels.MongoDB
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/Channels/AsyncResponse.Channels.MongoDB/MongoDbAsyncResponseChannel.cs
Line coverage
73%
Covered lines: 31
Uncovered lines: 11
Coverable lines: 42
Total lines: 98
Line coverage: 73.8%
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
.ctor(...)100%11100%
ChannelName(...)100%11100%
CurrentPollInterval()100%11100%
StartWakeListener(...)100%22100%
CreateWaiter<T>(...)100%11100%
ListenLoopAsync()100%3247.62%

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/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)
 329        : base(
 330            scopeFactory,
 331            store,
 332            recoveryStateStore,
 333            options.Value,
 334            propagation,
 335            logger,
 336            channelTypeName: nameof(MongoDbAsyncResponseChannel),
 337            providerName: "MongoDB",
 338            activityTag: "mongodb",
 339            subscriberRecordNoun: "document",
 340            localDispatchRetryHint: "listener retry will pick it up")
 41    {
 342    }
 43
 44    /// <inheritdoc />
 345    protected override string ChannelName(string correlationId) => $"{_options.MessageCollection}:{correlationId}";
 46
 47    /// <inheritdoc />
 348    protected override TimeSpan CurrentPollInterval() => _options.ListenerPollInterval;
 49
 50    /// <inheritdoc />
 51    protected override Task? StartWakeListener(CancellationToken cancellationToken)
 352        => _options.UseChangeStreams
 153            ? Task.Run(() => ListenLoopAsync(cancellationToken))
 354            : Task.CompletedTask;
 55
 56    /// <inheritdoc />
 57    protected override IAsyncResponseWaiter<T> CreateWaiter<T>(Task<T> responseTask, Func<ValueTask> cleanupAsync)
 358        => new MongoDbAsyncResponseWaiter<T>(responseTask, cleanupAsync);
 59
 60    private async Task ListenLoopAsync(CancellationToken cancellationToken)
 61    {
 162        var failures = 0;
 163        while (!cancellationToken.IsCancellationRequested)
 64        {
 65            try
 66            {
 167                await _store.WatchMessagesAsync(payload =>
 168                {
 169                    SignalDispatcher(string.IsNullOrEmpty(payload) ? null : payload);
 170                    return Task.CompletedTask;
 171                }, cancellationToken).ConfigureAwait(false);
 172                failures = 0;
 173            }
 174            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 75            {
 176                return;
 77            }
 078            catch (Exception ex) when (MongoDbChannelStore.IsChangeStreamUnsupported(ex))
 79            {
 80                // Standalone server: change streams need a replica set. The dispatch loop's
 81                // ListenerPollInterval sweep still delivers, so degrade to polling instead of
 82                // retry-spamming an error the server will keep returning.
 083                _logger.LogWarning(
 084                    ex,
 085                    "MongoDB change streams are unavailable (the server is not a replica set); response wakes fall back 
 086                    _options.ListenerPollInterval);
 087                return;
 88            }
 089            catch (Exception ex)
 90            {
 091                failures++;
 092                var delay = AsyncResponseRetry.Backoff(failures, TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(5)
 093                _logger.LogWarning(ex, "MongoDB change-stream loop failed; retrying in {Delay}.", delay);
 094                await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
 95            }
 96        }
 197    }
 98}