| | 1 | | using NostrSure.Infrastructure.Client.Abstractions; |
| | 2 | |
|
| | 3 | | namespace NostrSure.Infrastructure.Client.Implementation; |
| | 4 | |
|
| | 5 | | /// <summary> |
| | 6 | | /// Retry policy with exponential backoff |
| | 7 | | /// </summary> |
| | 8 | | public class RetryBackoffPolicy : IHealthPolicy |
| | 9 | | { |
| | 10 | | private readonly TimeSpan _baseDelay; |
| | 11 | | private readonly TimeSpan _maxDelay; |
| | 12 | | private readonly int _maxRetries; |
| | 13 | | private readonly Random _jitterRandom; |
| | 14 | |
|
| 40 | 15 | | public RetryBackoffPolicy( |
| 40 | 16 | | TimeSpan? baseDelay = null, |
| 40 | 17 | | TimeSpan? maxDelay = null, |
| 40 | 18 | | int maxRetries = 5) |
| 40 | 19 | | { |
| 40 | 20 | | _baseDelay = baseDelay ?? TimeSpan.FromSeconds(1); |
| 40 | 21 | | _maxDelay = maxDelay ?? TimeSpan.FromMinutes(1); |
| 40 | 22 | | _maxRetries = maxRetries; |
| 40 | 23 | | _jitterRandom = new Random(); |
| 40 | 24 | | } |
| | 25 | |
|
| | 26 | | public async Task DelayAsync(int attempt, CancellationToken cancellationToken = default) |
| 3 | 27 | | { |
| 4 | 28 | | if (attempt <= 0) return; |
| | 29 | |
|
| 2 | 30 | | var delay = GetDelay(attempt); |
| 2 | 31 | | await Task.Delay(delay, cancellationToken); |
| 2 | 32 | | } |
| | 33 | |
|
| | 34 | | public bool ShouldRetry(int attempt) |
| 13 | 35 | | { |
| 13 | 36 | | return attempt <= _maxRetries; |
| 13 | 37 | | } |
| | 38 | |
|
| | 39 | | public TimeSpan GetDelay(int attempt) |
| 69 | 40 | | { |
| 72 | 41 | | if (attempt <= 0) return TimeSpan.Zero; |
| | 42 | |
|
| | 43 | | // Exponential backoff: baseDelay * 2^(attempt-1) |
| 66 | 44 | | var exponentialDelay = TimeSpan.FromTicks( |
| 66 | 45 | | _baseDelay.Ticks * (1L << Math.Min(attempt - 1, 10))); // Cap at 2^10 to prevent overflow |
| | 46 | |
|
| | 47 | | // Cap at max delay |
| 66 | 48 | | var cappedDelay = exponentialDelay > _maxDelay ? _maxDelay : exponentialDelay; |
| | 49 | |
|
| | 50 | | // Add jitter (±25% of the delay) |
| 66 | 51 | | var jitterRange = (int)(cappedDelay.TotalMilliseconds * 0.25); |
| 66 | 52 | | var jitter = _jitterRandom.Next(-jitterRange, jitterRange + 1); |
| 66 | 53 | | var finalDelay = cappedDelay.Add(TimeSpan.FromMilliseconds(jitter)); |
| | 54 | |
|
| | 55 | | // Ensure non-negative |
| 66 | 56 | | return finalDelay > TimeSpan.Zero ? finalDelay : TimeSpan.Zero; |
| 69 | 57 | | } |
| | 58 | | } |