< Summary

Information
Class: AsiBackbone.Signing.ManagedKey.ManagedKeyRetryBackoff
Assembly: AsiBackbone.Signing.ManagedKey
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Signing.ManagedKey/ManagedKeyRetryBackoff.cs
Line coverage
89%
Covered lines: 26
Uncovered lines: 3
Coverable lines: 29
Total lines: 119
Line coverage: 89.6%
Branch coverage
69%
Covered branches: 18
Total branches: 26
Branch coverage: 69.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
CalculateDelay(...)60%212086.95%
CalculateUpperBoundTicks(...)100%66100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Signing.ManagedKey/ManagedKeyRetryBackoff.cs

#LineLine coverage
 1namespace AsiBackbone.Signing.ManagedKey;
 2
 3/// <summary>
 4/// Calculates bounded exponential retry delays with jitter.
 5/// </summary>
 6internal static class ManagedKeyRetryBackoff
 7{
 8    internal const string StrategyName = "exponential-jitter";
 9
 10    /// <summary>
 11    /// Calculates a monotonically increasing retry delay for the one-based retry attempt.
 12    /// </summary>
 13    internal static TimeSpan CalculateDelay(
 14        TimeSpan baseDelay,
 15        TimeSpan maxDelay,
 16        int retryAttempt,
 17        double jitterSample,
 18        TimeSpan previousDelay)
 19    {
 3020        ArgumentOutOfRangeException.ThrowIfLessThan(baseDelay, TimeSpan.Zero);
 21
 3022        if (maxDelay < TimeSpan.Zero || maxDelay < baseDelay)
 23        {
 024            throw new ArgumentOutOfRangeException(nameof(maxDelay));
 25        }
 26
 3027        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(retryAttempt);
 28
 3029        if (double.IsNaN(jitterSample) || jitterSample < 0d || jitterSample > 1d)
 30        {
 031            throw new ArgumentOutOfRangeException(nameof(jitterSample));
 32        }
 33
 3034        if (previousDelay < TimeSpan.Zero || previousDelay > maxDelay)
 35        {
 036            throw new ArgumentOutOfRangeException(nameof(previousDelay));
 37        }
 38
 3039        if (baseDelay == TimeSpan.Zero)
 40        {
 841            return TimeSpan.Zero;
 42        }
 43
 2244        long upperBoundTicks = CalculateUpperBoundTicks(baseDelay.Ticks, maxDelay.Ticks, retryAttempt);
 2245        long exponentialLowerBoundTicks = upperBoundTicks / 2;
 2246        long monotonicLowerBoundTicks = previousDelay.Ticks < upperBoundTicks
 2247            ? previousDelay.Ticks + 1
 2248            : upperBoundTicks;
 2249        long lowerBoundTicks = Math.Min(
 2250            upperBoundTicks,
 2251            Math.Max(exponentialLowerBoundTicks, monotonicLowerBoundTicks));
 2252        long jitterRangeTicks = upperBoundTicks - lowerBoundTicks;
 2253        long jitteredTicks = jitterSample >= 1d
 2254            ? upperBoundTicks
 2255            : lowerBoundTicks + (long)(jitterRangeTicks * jitterSample);
 56
 2257        return TimeSpan.FromTicks(Math.Min(jitteredTicks, maxDelay.Ticks));
 58    }
 59
 60    private static long CalculateUpperBoundTicks(long baseDelayTicks, long maxDelayTicks, int retryAttempt)
 61    {
 2262        long upperBoundTicks = Math.Min(baseDelayTicks, maxDelayTicks);
 63
 8864        for (int attempt = 1; attempt < retryAttempt && upperBoundTicks < maxDelayTicks; attempt++)
 65        {
 2266            upperBoundTicks = upperBoundTicks > maxDelayTicks / 2
 2267                ? maxDelayTicks
 2268                : Math.Min(upperBoundTicks * 2, maxDelayTicks);
 69        }
 70
 2271        return upperBoundTicks;
 72    }
 73}
 74
 75/// <summary>
 76/// Supplies a random sample used by the retry backoff calculation.
 77/// </summary>
 78internal interface IManagedKeyRetryJitterSource
 79{
 80    double NextDouble();
 81}
 82
 83/// <summary>
 84/// Delays a retry while preserving cancellation.
 85/// </summary>
 86internal interface IManagedKeyRetryDelay
 87{
 88    ValueTask DelayAsync(TimeSpan delay, CancellationToken cancellationToken);
 89}
 90
 91internal sealed class SharedRandomManagedKeyRetryJitterSource : IManagedKeyRetryJitterSource
 92{
 93    internal static SharedRandomManagedKeyRetryJitterSource Instance { get; } = new();
 94
 95    private SharedRandomManagedKeyRetryJitterSource()
 96    {
 97    }
 98
 99    public double NextDouble()
 100    {
 101        return Random.Shared.NextDouble();
 102    }
 103}
 104
 105internal sealed class SystemManagedKeyRetryDelay : IManagedKeyRetryDelay
 106{
 107    internal static SystemManagedKeyRetryDelay Instance { get; } = new();
 108
 109    private SystemManagedKeyRetryDelay()
 110    {
 111    }
 112
 113    public ValueTask DelayAsync(TimeSpan delay, CancellationToken cancellationToken)
 114    {
 115        return delay <= TimeSpan.Zero
 116            ? ValueTask.CompletedTask
 117            : new ValueTask(Task.Delay(delay, cancellationToken));
 118    }
 119}