Skip to content

Commit b60eea8

Browse files
authored
Expose Randomizer property and use it in retry strategy (#1346)
1 parent 6b38850 commit b60eea8

10 files changed

+133
-52
lines changed

src/Polly.Core/ResilienceStrategyBuilderBase.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ private protected ResilienceStrategyBuilderBase(ResilienceStrategyBuilderBase ot
2727
Properties = other.Properties;
2828
TimeProvider = other.TimeProvider;
2929
OnCreatingStrategy = other.OnCreatingStrategy;
30+
Randomizer = other.Randomizer;
31+
DiagnosticSource = other.DiagnosticSource;
3032
}
3133

3234
/// <summary>
@@ -70,6 +72,16 @@ private protected ResilienceStrategyBuilderBase(ResilienceStrategyBuilderBase ot
7072
[EditorBrowsable(EditorBrowsableState.Never)]
7173
public DiagnosticSource? DiagnosticSource { get; set; }
7274

75+
/// <summary>
76+
/// Gets or sets the randomizer that is used by strategies that need to generate random numbers.
77+
/// </summary>
78+
/// <remarks>
79+
/// The default randomizer is thread safe and returns values between 0.0 and 1.0.
80+
/// </remarks>
81+
[EditorBrowsable(EditorBrowsableState.Never)]
82+
[Required]
83+
public Func<double> Randomizer { get; set; } = RandomUtil.Instance.NextDouble;
84+
7385
internal abstract bool IsGenericBuilder { get; }
7486

7587
internal void AddStrategyCore(Func<ResilienceStrategyBuilderContext, ResilienceStrategy> factory, ResilienceStrategyOptions options)
@@ -118,7 +130,8 @@ private ResilienceStrategy CreateResilienceStrategy(Entry entry)
118130
strategyType: entry.Properties.StrategyType,
119131
timeProvider: TimeProvider,
120132
isGenericBuilder: IsGenericBuilder,
121-
diagnosticSource: DiagnosticSource);
133+
diagnosticSource: DiagnosticSource,
134+
randomizer: Randomizer);
122135

123136
return entry.Factory(context);
124137
}

src/Polly.Core/ResilienceStrategyBuilderContext.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Polly;
44

5+
#pragma warning disable S107
6+
57
/// <summary>
68
/// The context used for building an individual resilience strategy.
79
/// </summary>
@@ -14,7 +16,8 @@ internal ResilienceStrategyBuilderContext(
1416
string strategyType,
1517
TimeProvider timeProvider,
1618
bool isGenericBuilder,
17-
DiagnosticSource? diagnosticSource)
19+
DiagnosticSource? diagnosticSource,
20+
Func<double> randomizer)
1821
{
1922
BuilderName = builderName;
2023
BuilderProperties = builderProperties;
@@ -23,6 +26,7 @@ internal ResilienceStrategyBuilderContext(
2326
TimeProvider = timeProvider;
2427
IsGenericBuilder = isGenericBuilder;
2528
Telemetry = TelemetryUtil.CreateTelemetry(diagnosticSource, builderName, builderProperties, strategyName, strategyType);
29+
Randomizer = randomizer;
2630
}
2731

2832
/// <summary>
@@ -55,5 +59,7 @@ internal ResilienceStrategyBuilderContext(
5559
/// </summary>
5660
internal TimeProvider TimeProvider { get; }
5761

62+
internal Func<double> Randomizer { get; }
63+
5864
internal bool IsGenericBuilder { get; }
5965
}

src/Polly.Core/Retry/RetryHelper.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ internal static class RetryHelper
66

77
public static bool IsValidDelay(TimeSpan delay) => delay >= TimeSpan.Zero;
88

9-
public static TimeSpan GetRetryDelay(RetryBackoffType type, int attempt, TimeSpan baseDelay, ref double state, RandomUtil random)
9+
public static TimeSpan GetRetryDelay(RetryBackoffType type, int attempt, TimeSpan baseDelay, ref double state, Func<double> randomizer)
1010
{
1111
if (baseDelay == TimeSpan.Zero)
1212
{
@@ -23,7 +23,7 @@ public static TimeSpan GetRetryDelay(RetryBackoffType type, int attempt, TimeSpa
2323
RetryBackoffType.Linear => (attempt + 1) * baseDelay,
2424
RetryBackoffType.Exponential => Math.Pow(ExponentialFactor, attempt) * baseDelay,
2525
#endif
26-
RetryBackoffType.ExponentialWithJitter => DecorrelatedJitterBackoffV2(attempt, baseDelay, ref state, random),
26+
RetryBackoffType.ExponentialWithJitter => DecorrelatedJitterBackoffV2(attempt, baseDelay, ref state, randomizer),
2727
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "The retry backoff type is not supported.")
2828
};
2929
}
@@ -40,11 +40,11 @@ public static TimeSpan GetRetryDelay(RetryBackoffType type, int attempt, TimeSpa
4040
/// The actual amount of delay-before-retry for try t may be distributed between 0 and <c>f * (2^(t+1) - 2^(t-1)) for t >= 2;</c>
4141
/// or between 0 and <c>f * 2^(t+1)</c>, for t is 0 or 1.</param>
4242
/// <param name="prev">The previous state value used for calculations.</param>
43-
/// <param name="random">The random utility to use.</param>
43+
/// <param name="randomizer">The generator to use.</param>
4444
/// <remarks>
4545
/// This code was adopted from https://github.com/Polly-Contrib/Polly.Contrib.WaitAndRetry/blob/master/src/Polly.Contrib.WaitAndRetry/Backoff.DecorrelatedJitterV2.cs.
4646
/// </remarks>
47-
private static TimeSpan DecorrelatedJitterBackoffV2(int attempt, TimeSpan baseDelay, ref double prev, RandomUtil random)
47+
private static TimeSpan DecorrelatedJitterBackoffV2(int attempt, TimeSpan baseDelay, ref double prev, Func<double> randomizer)
4848
{
4949
// The original author/credit for this jitter formula is @george-polevoy .
5050
// Jitter formula used with permission as described at https://github.com/App-vNext/Polly/issues/530#issuecomment-526555979
@@ -64,7 +64,7 @@ private static TimeSpan DecorrelatedJitterBackoffV2(int attempt, TimeSpan baseDe
6464

6565
long targetTicksFirstDelay = baseDelay.Ticks;
6666

67-
double t = attempt + random.NextDouble();
67+
double t = attempt + randomizer();
6868
double next = Math.Pow(2, t) * Math.Tanh(Math.Sqrt(PFactor * t));
6969

7070
double formulaIntrinsicValue = next - prev;

src/Polly.Core/Retry/RetryResilienceStrategy.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using Polly.Telemetry;
23

34
namespace Polly.Retry;
@@ -6,14 +7,14 @@ internal sealed class RetryResilienceStrategy<T> : OutcomeResilienceStrategy<T>
67
{
78
private readonly TimeProvider _timeProvider;
89
private readonly ResilienceStrategyTelemetry _telemetry;
9-
private readonly RandomUtil _randomUtil;
10+
private readonly Func<double> _randomizer;
1011

1112
public RetryResilienceStrategy(
1213
RetryStrategyOptions<T> options,
1314
bool isGeneric,
1415
TimeProvider timeProvider,
1516
ResilienceStrategyTelemetry telemetry,
16-
RandomUtil randomUtil)
17+
Func<double> randomizer)
1718
: base(isGeneric)
1819
{
1920
ShouldHandle = options.ShouldHandle;
@@ -25,7 +26,7 @@ public RetryResilienceStrategy(
2526

2627
_timeProvider = timeProvider;
2728
_telemetry = telemetry;
28-
_randomUtil = randomUtil;
29+
_randomizer = randomizer;
2930
}
3031

3132
public TimeSpan BaseDelay { get; }
@@ -61,7 +62,7 @@ protected override async ValueTask<Outcome<T>> ExecuteCallbackAsync<TState>(Func
6162
return outcome;
6263
}
6364

64-
var delay = RetryHelper.GetRetryDelay(BackoffType, attempt, BaseDelay, ref retryState, _randomUtil);
65+
var delay = RetryHelper.GetRetryDelay(BackoffType, attempt, BaseDelay, ref retryState, _randomizer);
6566
if (DelayGenerator is not null)
6667
{
6768
var delayArgs = new OutcomeArguments<T, RetryDelayArguments>(context, outcome, new RetryDelayArguments(attempt, delay));

src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private static TBuilder AddRetryCore<TBuilder, TResult>(this TBuilder builder, R
5050
context.IsGenericBuilder,
5151
context.TimeProvider,
5252
context.Telemetry,
53-
RandomUtil.Instance),
53+
context.Randomizer),
5454
options);
5555
}
5656
}

test/Polly.Core.Tests/ResilienceStrategyBuilderContextTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public void Ctor_EnsureDefaults()
99
{
1010
var properties = new ResilienceProperties();
1111
var timeProvider = new FakeTimeProvider();
12-
var context = new ResilienceStrategyBuilderContext("builder-name", properties, "strategy-name", "strategy-type", timeProvider.Object, true, Mock.Of<DiagnosticSource>());
12+
var context = new ResilienceStrategyBuilderContext("builder-name", properties, "strategy-name", "strategy-type", timeProvider.Object, true, Mock.Of<DiagnosticSource>(), () => 1.0);
1313

1414
context.IsGenericBuilder.Should().BeTrue();
1515
context.BuilderName.Should().Be("builder-name");
@@ -18,6 +18,7 @@ public void Ctor_EnsureDefaults()
1818
context.StrategyType.Should().Be("strategy-type");
1919
context.TimeProvider.Should().Be(timeProvider.Object);
2020
context.Telemetry.Should().NotBeNull();
21+
context.Randomizer.Should().NotBeNull();
2122

2223
context.Telemetry.TelemetrySource.BuilderName.Should().Be("builder-name");
2324
context.Telemetry.TelemetrySource.BuilderProperties.Should().BeSameAs(properties);

test/Polly.Core.Tests/ResilienceStrategyBuilderTests.cs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel.DataAnnotations;
2+
using Moq;
23
using Polly.Utils;
34

45
namespace Polly.Core.Tests;
@@ -14,6 +15,30 @@ public void Ctor_EnsureDefaults()
1415
builder.Properties.Should().NotBeNull();
1516
builder.TimeProvider.Should().Be(TimeProvider.System);
1617
builder.IsGenericBuilder.Should().BeFalse();
18+
builder.Randomizer.Should().NotBeNull();
19+
}
20+
21+
[Fact]
22+
public void CopyCtor_Ok()
23+
{
24+
var builder = new ResilienceStrategyBuilder
25+
{
26+
TimeProvider = Mock.Of<TimeProvider>(),
27+
BuilderName = "dummy",
28+
Randomizer = () => 0.0,
29+
DiagnosticSource = Mock.Of<DiagnosticSource>(),
30+
OnCreatingStrategy = _ => { },
31+
};
32+
33+
builder.Properties.Set(new ResiliencePropertyKey<string>("dummy"), "dummy");
34+
35+
var other = new ResilienceStrategyBuilder<double>(builder);
36+
other.BuilderName.Should().Be(builder.BuilderName);
37+
other.TimeProvider.Should().Be(builder.TimeProvider);
38+
other.Randomizer.Should().BeSameAs(builder.Randomizer);
39+
other.DiagnosticSource.Should().BeSameAs(builder.DiagnosticSource);
40+
other.OnCreatingStrategy.Should().BeSameAs(builder.OnCreatingStrategy);
41+
other.Properties.GetValue(new ResiliencePropertyKey<string>("dummy"), "").Should().Be("dummy");
1742
}
1843

1944
[Fact]
@@ -133,10 +158,7 @@ public void AddStrategy_MultipleNonDelegating_Ok()
133158
}
134159

135160
[Fact]
136-
public void Build_Empty_ReturnsNullResilienceStrategy()
137-
{
138-
new ResilienceStrategyBuilder().Build().Should().BeSameAs(NullResilienceStrategy.Instance);
139-
}
161+
public void Build_Empty_ReturnsNullResilienceStrategy() => new ResilienceStrategyBuilder().Build().Should().BeSameAs(NullResilienceStrategy.Instance);
140162

141163
[Fact]
142164
public void AddStrategy_AfterUsed_Throws()
@@ -259,6 +281,7 @@ public void BuildStrategy_EnsureCorrectContext()
259281
context.BuilderProperties.Should().BeSameAs(builder.Properties);
260282
context.Telemetry.Should().NotBeNull();
261283
context.TimeProvider.Should().Be(builder.TimeProvider);
284+
context.Randomizer.Should().BeSameAs(builder.Randomizer);
262285
verified1 = true;
263286

264287
return new TestResilienceStrategy();
@@ -312,10 +335,7 @@ public void Build_OnCreatingStrategy_EnsureRespected()
312335
}
313336

314337
[Fact]
315-
public void EmptyOptions_Ok()
316-
{
317-
ResilienceStrategyBuilderExtensions.EmptyOptions.Instance.StrategyType.Should().Be("Empty");
318-
}
338+
public void EmptyOptions_Ok() => ResilienceStrategyBuilderExtensions.EmptyOptions.Instance.StrategyType.Should().Be("Empty");
319339

320340
[Fact]
321341
public void ExecuteAsync_EnsureReceivedCallbackExecutesNextStrategy()

test/Polly.Core.Tests/Retry/RetryHelperTests.cs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Polly.Core.Tests.Retry;
66

77
public class RetryHelperTests
88
{
9-
private readonly RandomUtil _randomUtil = new(0);
9+
private readonly Func<double> _randomizer = new RandomUtil(0).NextDouble;
1010

1111
[Fact]
1212
public void IsValidDelay_Ok()
@@ -26,7 +26,7 @@ public void UnsupportedRetryBackoffType_Throws()
2626
Assert.Throws<ArgumentOutOfRangeException>(() =>
2727
{
2828
double state = 0;
29-
return RetryHelper.GetRetryDelay(type, 0, TimeSpan.FromSeconds(1), ref state, _randomUtil);
29+
return RetryHelper.GetRetryDelay(type, 0, TimeSpan.FromSeconds(1), ref state, _randomizer);
3030
});
3131
}
3232

@@ -35,41 +35,41 @@ public void Constant_Ok()
3535
{
3636
double state = 0;
3737

38-
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 0, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
39-
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 1, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
40-
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 2, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
38+
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 0, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
39+
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 1, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
40+
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 2, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
4141

42-
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 0, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(1));
43-
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 1, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(1));
44-
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 2, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(1));
42+
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 0, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(1));
43+
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 1, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(1));
44+
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 2, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(1));
4545
}
4646

4747
[Fact]
4848
public void Linear_Ok()
4949
{
5050
double state = 0;
5151

52-
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 0, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
53-
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 1, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
54-
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 2, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
52+
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 0, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
53+
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 1, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
54+
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 2, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
5555

56-
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 0, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(1));
57-
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 1, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(2));
58-
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(3));
56+
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 0, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(1));
57+
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 1, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(2));
58+
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(3));
5959
}
6060

6161
[Fact]
6262
public void Exponential_Ok()
6363
{
6464
double state = 0;
6565

66-
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 0, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
67-
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 1, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
68-
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 2, TimeSpan.Zero, ref state, _randomUtil).Should().Be(TimeSpan.Zero);
66+
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 0, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
67+
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 1, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
68+
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 2, TimeSpan.Zero, ref state, _randomizer).Should().Be(TimeSpan.Zero);
6969

70-
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 0, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(1));
71-
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 1, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(2));
72-
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 2, TimeSpan.FromSeconds(1), ref state, _randomUtil).Should().Be(TimeSpan.FromSeconds(4));
70+
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 0, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(1));
71+
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 1, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(2));
72+
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 2, TimeSpan.FromSeconds(1), ref state, _randomizer).Should().Be(TimeSpan.FromSeconds(4));
7373
}
7474

7575
[InlineData(1)]
@@ -94,20 +94,20 @@ public void ExponentialWithJitter_Ok(int count)
9494
public void ExponentialWithJitter_EnsureRandomness()
9595
{
9696
var delay = TimeSpan.FromSeconds(7.8);
97-
var delays1 = GetExponentialWithJitterBackoff(false, delay, 100, RandomUtil.Instance);
98-
var delays2 = GetExponentialWithJitterBackoff(false, delay, 100, RandomUtil.Instance);
97+
var delays1 = GetExponentialWithJitterBackoff(false, delay, 100, RandomUtil.Instance.NextDouble);
98+
var delays2 = GetExponentialWithJitterBackoff(false, delay, 100, RandomUtil.Instance.NextDouble);
9999

100100
delays1.SequenceEqual(delays2).Should().BeFalse();
101101
}
102102

103-
private static IReadOnlyList<TimeSpan> GetExponentialWithJitterBackoff(bool contrib, TimeSpan baseDelay, int retryCount, RandomUtil? util = null)
103+
private static IReadOnlyList<TimeSpan> GetExponentialWithJitterBackoff(bool contrib, TimeSpan baseDelay, int retryCount, Func<double>? randomizer = null)
104104
{
105105
if (contrib)
106106
{
107107
return Backoff.DecorrelatedJitterBackoffV2(baseDelay, retryCount, 0, false).Take(retryCount).ToArray();
108108
}
109109

110-
var random = util ?? new RandomUtil(0);
110+
var random = randomizer ?? new RandomUtil(0).NextDouble;
111111
double state = 0;
112112
var result = new List<TimeSpan>();
113113

test/Polly.Core.Tests/Retry/RetryResilienceStrategyBuilderExtensionsTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,48 @@ public void AddRetry_InvalidOptions_Throws()
103103
.Should()
104104
.Throw<ValidationException>();
105105
}
106+
107+
[Fact]
108+
public void GetAggregatedDelay_ShouldReturnTheSameValue()
109+
{
110+
var options = new RetryStrategyOptions { BackoffType = RetryBackoffType.ExponentialWithJitter };
111+
112+
var delay = GetAggregatedDelay(options);
113+
GetAggregatedDelay(options).Should().Be(delay);
114+
}
115+
116+
[Fact]
117+
public void GetAggregatedDelay_EnsureCorrectValue()
118+
{
119+
var options = new RetryStrategyOptions { BackoffType = RetryBackoffType.Constant, BaseDelay = TimeSpan.FromSeconds(1), RetryCount = 5 };
120+
121+
GetAggregatedDelay(options).Should().Be(TimeSpan.FromSeconds(5));
122+
}
123+
124+
private static TimeSpan GetAggregatedDelay<T>(RetryStrategyOptions<T> options)
125+
{
126+
var aggregatedDelay = TimeSpan.Zero;
127+
128+
var strategy = new ResilienceStrategyBuilder { Randomizer = () => 1.0 }.AddRetry(new()
129+
{
130+
RetryCount = options.RetryCount,
131+
BaseDelay = options.BaseDelay,
132+
BackoffType = options.BackoffType,
133+
ShouldHandle = _ => PredicateResult.True, // always retry until all retries are exhausted
134+
RetryDelayGenerator = args =>
135+
{
136+
// the delay hint is calculated for this attempt by the retry strategy
137+
aggregatedDelay += args.Arguments.DelayHint;
138+
139+
// return zero delay, so no waiting
140+
return new ValueTask<TimeSpan>(TimeSpan.Zero);
141+
}
142+
})
143+
.Build();
144+
145+
// this executes all retries and we aggregate the delays immediately
146+
strategy.Execute(() => { });
147+
148+
return aggregatedDelay;
149+
}
106150
}

0 commit comments

Comments
 (0)