-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathRetryResilienceStrategy.cs
More file actions
148 lines (118 loc) · 5.07 KB
/
RetryResilienceStrategy.cs
File metadata and controls
148 lines (118 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
using Polly.Telemetry;
namespace Polly.Retry;
internal sealed class RetryResilienceStrategy<T> : ResilienceStrategy<T>
{
private readonly TimeProvider _timeProvider;
private readonly ResilienceStrategyTelemetry _telemetry;
private readonly Func<double> _randomizer;
public RetryResilienceStrategy(
RetryStrategyOptions<T> options,
TimeProvider timeProvider,
ResilienceStrategyTelemetry telemetry)
{
ShouldHandle = options.ShouldHandle;
BaseDelay = options.Delay;
MaxDelay = options.MaxDelay;
BackoffType = options.BackoffType;
RetryCount = options.MaxRetryAttempts;
OnRetry = options.OnRetry;
DelayGenerator = options.DelayGenerator;
UseJitter = options.UseJitter;
_timeProvider = timeProvider;
_telemetry = telemetry;
_randomizer = options.Randomizer;
}
public TimeSpan BaseDelay { get; }
public TimeSpan? MaxDelay { get; }
public DelayBackoffType BackoffType { get; }
public int RetryCount { get; }
public Func<RetryPredicateArguments<T>, ValueTask<bool>> ShouldHandle { get; }
public Func<RetryDelayGeneratorArguments<T>, ValueTask<TimeSpan?>>? DelayGenerator { get; }
public bool UseJitter { get; }
public Func<OnRetryArguments<T>, ValueTask>? OnRetry { get; }
protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func<ResilienceContext, TState, ValueTask<Outcome<T>>> callback, ResilienceContext context, TState state)
{
double retryState = 0;
int attempt = 0;
while (true)
{
var startTimestamp = _timeProvider.GetTimestamp();
Outcome<T> outcome;
try
{
outcome = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext);
}
#pragma warning disable CA1031
catch (Exception ex)
{
outcome = new(ex);
}
#pragma warning restore CA1031
var shouldRetryArgs = new RetryPredicateArguments<T>(context, outcome, attempt);
var handle = await ShouldHandle(shouldRetryArgs).ConfigureAwait(context.ContinueOnCapturedContext);
var executionTime = _timeProvider.GetElapsedTime(startTimestamp);
var isLastAttempt = IsLastAttempt(attempt, out bool incrementAttempts);
if (isLastAttempt)
{
TelemetryUtil.ReportFinalExecutionAttempt(_telemetry, context, outcome, attempt, executionTime, handle);
}
else
{
TelemetryUtil.ReportExecutionAttempt(_telemetry, context, outcome, attempt, executionTime, handle);
}
if (isLastAttempt || !handle)
{
return outcome;
}
var delay = RetryHelper.GetRetryDelay(BackoffType, UseJitter, attempt, BaseDelay, MaxDelay, ref retryState, _randomizer);
if (DelayGenerator is not null)
{
var delayArgs = new RetryDelayGeneratorArguments<T>(context, outcome, attempt);
if (await DelayGenerator(delayArgs).ConfigureAwait(false) is TimeSpan newDelay && RetryHelper.IsValidDelay(newDelay))
{
delay = newDelay;
}
}
#pragma warning disable S3236 // Remove this argument from the method call; it hides the caller information.
Debug.Assert(delay >= TimeSpan.Zero, "The delay cannot be negative.");
#pragma warning restore S3236 // Remove this argument from the method call; it hides the caller information.
var onRetryArgs = new OnRetryArguments<T>(context, outcome, attempt, delay, executionTime);
_telemetry.Report<OnRetryArguments<T>, T>(new(ResilienceEventSeverity.Warning, RetryConstants.OnRetryEvent), onRetryArgs);
if (OnRetry is not null)
{
await OnRetry(onRetryArgs).ConfigureAwait(context.ContinueOnCapturedContext);
}
if (outcome.TryGetResult(out var resultValue))
{
await DisposeHelper.TryDisposeSafeAsync(resultValue, context.IsSynchronous).ConfigureAwait(context.ContinueOnCapturedContext);
}
try
{
context.CancellationToken.ThrowIfCancellationRequested();
// stryker disable once all : no means to test this
if (delay > TimeSpan.Zero)
{
await _timeProvider.DelayAsync(delay, context).ConfigureAwait(context.ContinueOnCapturedContext);
}
}
catch (OperationCanceledException e)
{
return Outcome.FromException<T>(e);
}
if (incrementAttempts)
{
attempt++;
}
}
}
internal bool IsLastAttempt(int attempt, out bool incrementAttempts)
{
if (attempt == int.MaxValue)
{
incrementAttempts = false;
return false;
}
incrementAttempts = true;
return attempt >= RetryCount;
}
}