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

Information
Class: AsyncResponse.Channels.PostgreSQL.PostgreSqlAsyncResponseChannel
Assembly: AsyncResponse.Channels.PostgreSQL
File(s): /_/src/Channels/AsyncResponse.Channels.PostgreSQL/PostgreSqlAsyncResponseChannel.cs
Line coverage
94%
Covered lines: 33
Uncovered lines: 2
Coverable lines: 35
Total lines: 85
Line coverage: 94.2%
Branch coverage
75%
Covered branches: 3
Total branches: 4
Branch coverage: 75%
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%11100%
CreateWaiter(...)100%11100%
ListenLoopAsync()100%2266.66%

File(s)

/_/src/Channels/AsyncResponse.Channels.PostgreSQL/PostgreSqlAsyncResponseChannel.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.PostgreSQL.PostgreSqlChannelSql;
 4global using DbChannelMessage = AsyncResponse.Channels.PostgreSQL.PostgreSqlChannelMessage;
 5global using DbChannelOptions = AsyncResponse.Channels.PostgreSQL.PostgreSqlAsyncResponseChannelOptions;
 6
 7using Microsoft.Extensions.DependencyInjection;
 8using Microsoft.Extensions.Logging;
 9using Microsoft.Extensions.Options;
 10
 11namespace AsyncResponse.Channels.PostgreSQL;
 12
 13/// <summary>
 14/// PostgreSQL-backed response channel using <c>LISTEN/NOTIFY</c> for active waiter wakeups and
 15/// PostgreSQL tables for durable recovery state.
 16/// </summary>
 17internal sealed class PostgreSqlAsyncResponseChannel : DbAsyncResponseChannelBase
 18{
 19    /// <summary>Creates a PostgreSQL-backed async-response channel.</summary>
 20    public PostgreSqlAsyncResponseChannel(
 21        IServiceScopeFactory scopeFactory,
 22        PostgreSqlChannelSql sql,
 23        IRecoveryStateStore recoveryStateStore,
 24        IOptions<PostgreSqlAsyncResponseChannelOptions> options,
 25        AsyncResponseContextPropagation propagation,
 26        ILogger<PostgreSqlAsyncResponseChannel> logger,
 27        TimeProvider? timeProvider = null)
 41628        : base(
 41629            scopeFactory,
 41630            sql,
 41631            recoveryStateStore,
 41632            options.Value,
 41633            propagation,
 41634            logger,
 41635            channelTypeName: nameof(PostgreSqlAsyncResponseChannel),
 41636            providerName: "PostgreSQL",
 41637            activityTag: "postgresql",
 41638            subscriberRecordNoun: "row",
 41639            localDispatchRetryHint: "listener retry will pick it up",
 41640            timeProvider)
 41    {
 41642    }
 43
 44    /// <inheritdoc />
 212145    protected override string ChannelName(string correlationId) => $"{_options.NotificationChannel}:{correlationId}";
 46
 47    /// <inheritdoc />
 868048    protected override TimeSpan CurrentPollInterval() => _options.ListenerPollInterval;
 49
 50    /// <inheritdoc />
 51    protected override Task? StartWakeListener(CancellationToken cancellationToken)
 72652        => Task.Run(() => ListenLoopAsync(cancellationToken));
 53
 54    /// <inheritdoc />
 55    protected override IAsyncResponseWaiter<T> CreateWaiter<T>(Task<T> responseTask, Func<ValueTask> cleanupAsync)
 40956        => new PostgreSqlAsyncResponseWaiter<T>(responseTask, cleanupAsync);
 57
 58    private async Task ListenLoopAsync(CancellationToken cancellationToken)
 59    {
 36360        var failures = 0;
 36361        while (!cancellationToken.IsCancellationRequested)
 62        {
 63            try
 64            {
 36265                await _store.ExecuteListenAsync(payload =>
 36266                {
 51067                    SignalDispatcher(string.IsNullOrEmpty(payload) ? null : payload);
 51068                    return Task.CompletedTask;
 36269                }, cancellationToken).ConfigureAwait(false);
 070                failures = 0;
 071            }
 35872            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 73            {
 35874                return;
 75            }
 476            catch (Exception ex)
 77            {
 478                failures++;
 479                var delay = AsyncResponseRetry.Backoff(failures, TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(5)
 480                _logger.LogWarning(ex, "PostgreSQL LISTEN loop failed; retrying in {Delay}.", delay);
 481                await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
 82            }
 83        }
 35984    }
 85}