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

Information
Class: AsyncResponse.Channels.PostgreSQL.PostgreSqlAsyncResponseChannel
Assembly: AsyncResponse.Channels.PostgreSQL
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/Channels/AsyncResponse.Channels.PostgreSQL/PostgreSqlAsyncResponseChannel.cs
Line coverage
100%
Covered lines: 34
Uncovered lines: 0
Coverable lines: 34
Total lines: 83
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
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%11100%
CreateWaiter<T>(...)100%11100%
ListenLoopAsync()100%22100%

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/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)
 327        : base(
 328            scopeFactory,
 329            sql,
 330            recoveryStateStore,
 331            options.Value,
 332            propagation,
 333            logger,
 334            channelTypeName: nameof(PostgreSqlAsyncResponseChannel),
 335            providerName: "PostgreSQL",
 336            activityTag: "postgresql",
 337            subscriberRecordNoun: "row",
 338            localDispatchRetryHint: "listener retry will pick it up")
 39    {
 340    }
 41
 42    /// <inheritdoc />
 343    protected override string ChannelName(string correlationId) => $"{_options.NotificationChannel}:{correlationId}";
 44
 45    /// <inheritdoc />
 346    protected override TimeSpan CurrentPollInterval() => _options.ListenerPollInterval;
 47
 48    /// <inheritdoc />
 49    protected override Task? StartWakeListener(CancellationToken cancellationToken)
 350        => Task.Run(() => ListenLoopAsync(cancellationToken));
 51
 52    /// <inheritdoc />
 53    protected override IAsyncResponseWaiter<T> CreateWaiter<T>(Task<T> responseTask, Func<ValueTask> cleanupAsync)
 154        => new PostgreSqlAsyncResponseWaiter<T>(responseTask, cleanupAsync);
 55
 56    private async Task ListenLoopAsync(CancellationToken cancellationToken)
 57    {
 358        var failures = 0;
 359        while (!cancellationToken.IsCancellationRequested)
 60        {
 61            try
 62            {
 363                await _store.ExecuteListenAsync(payload =>
 364                {
 165                    SignalDispatcher(string.IsNullOrEmpty(payload) ? null : payload);
 166                    return Task.CompletedTask;
 367                }, cancellationToken).ConfigureAwait(false);
 168                failures = 0;
 169            }
 370            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 71            {
 172                return;
 73            }
 374            catch (Exception ex)
 75            {
 376                failures++;
 377                var delay = AsyncResponseRetry.Backoff(failures, TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(5)
 378                _logger.LogWarning(ex, "PostgreSQL LISTEN loop failed; retrying in {Delay}.", delay);
 379                await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
 80            }
 81        }
 182    }
 83}