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

Information
Class: AsyncResponse.AsyncResponseRetry<T>
Assembly: AsyncResponse.Core
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/AsyncResponse.Core/AsyncResponseRetry.cs
Line coverage
100%
Covered lines: 15
Uncovered lines: 0
Coverable lines: 15
Total lines: 47
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ExecuteAsync()100%22100%
Backoff(...)100%11100%

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/src/AsyncResponse.Core/AsyncResponseRetry.cs

#LineLine coverage
 1namespace AsyncResponse;
 2
 3internal static class AsyncResponseRetry
 4{
 5    /// <summary>Runs this background operation until cancellation is requested.</summary>
 6    public static async Task<T> ExecuteAsync<T>(
 7        Func<CancellationToken, Task<T>> action,
 8        Func<Exception, bool> isTransient,
 9        int maxAttempts,
 10        TimeSpan baseDelay,
 11        TimeSpan maxDelay,
 12        CancellationToken cancellationToken)
 13    {
 314        ArgumentNullException.ThrowIfNull(action);
 315        ArgumentNullException.ThrowIfNull(isTransient);
 316        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxAttempts);
 17
 318        var attempt = 0;
 19        while (true)
 20        {
 321            cancellationToken.ThrowIfCancellationRequested();
 322            attempt++;
 23
 24            try
 25            {
 326                return await action(cancellationToken).ConfigureAwait(false);
 27            }
 328            catch (Exception ex) when (attempt < maxAttempts && isTransient(ex))
 29            {
 330                await Task.Delay(Backoff(attempt, baseDelay, maxDelay), cancellationToken).ConfigureAwait(false);
 331            }
 32        }
 333    }
 34
 35    /// <summary>Computes the retry backoff delay: exponential with half-jitter.</summary>
 36    public static TimeSpan Backoff(int completedAttempts, TimeSpan baseDelay, TimeSpan maxDelay)
 37    {
 338        var multiplier = 1 << Math.Min(Math.Max(completedAttempts, 1) - 1, 10);
 339        var milliseconds = Math.Min(maxDelay.TotalMilliseconds, baseDelay.TotalMilliseconds * multiplier);
 40
 41        // Half-jitter: keep at least half the exponential step so backoff still backs off, and
 42        // randomize the rest — a broker blip fails many waiters across many replicas at once, and
 43        // un-jittered exponential delays would send them all reconnecting in lockstep waves.
 344        milliseconds = milliseconds / 2 + Random.Shared.NextDouble() * (milliseconds / 2);
 345        return TimeSpan.FromMilliseconds(Math.Max(1, milliseconds));
 46    }
 47}