-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce RetryResilienceStrategy (#1101)
- Loading branch information
Showing
28 changed files
with
886 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Polly.Retry; | ||
|
||
namespace Polly.Core.Tests.Retry; | ||
|
||
public class RetryConstantsTests | ||
{ | ||
[Fact] | ||
public void EnsureDefaults() | ||
{ | ||
RetryConstants.DefaultBackoffType.Should().Be(RetryBackoffType.Exponential); | ||
RetryConstants.DefaultBaseDelay.Should().Be(TimeSpan.FromSeconds(2)); | ||
RetryConstants.DefaultRetryCount.Should().Be(3); | ||
RetryConstants.MaxRetryCount.Should().Be(100); | ||
RetryConstants.InfiniteRetryCount.Should().Be(-1); | ||
RetryConstants.StrategyType.Should().Be("Retry"); | ||
RetryConstants.OnRetryEvent.Should().Be("OnRetry"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using System; | ||
using Polly.Retry; | ||
|
||
namespace Polly.Core.Tests.Retry; | ||
|
||
public class RetryHelperTests | ||
{ | ||
[Fact] | ||
public void IsValidDelay_Ok() | ||
{ | ||
RetryHelper.IsValidDelay(TimeSpan.Zero).Should().BeTrue(); | ||
RetryHelper.IsValidDelay(TimeSpan.FromSeconds(1)).Should().BeTrue(); | ||
RetryHelper.IsValidDelay(TimeSpan.MaxValue).Should().BeTrue(); | ||
RetryHelper.IsValidDelay(TimeSpan.MinValue).Should().BeFalse(); | ||
RetryHelper.IsValidDelay(TimeSpan.FromMilliseconds(-1)).Should().BeFalse(); | ||
} | ||
|
||
[Fact] | ||
public void UnsupportedRetryBackoffType_Throws() | ||
{ | ||
RetryBackoffType type = (RetryBackoffType)99; | ||
|
||
Assert.Throws<ArgumentOutOfRangeException>(() => RetryHelper.GetRetryDelay(type, 0, TimeSpan.FromSeconds(1))); | ||
} | ||
|
||
[Fact] | ||
public void Constant_Ok() | ||
{ | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 0, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 1, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 2, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
|
||
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 0, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(1)); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 1, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(1)); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Constant, 2, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(1)); | ||
} | ||
|
||
[Fact] | ||
public void Linear_Ok() | ||
{ | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 0, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 1, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 2, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
|
||
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 0, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(1)); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 1, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(2)); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(3)); | ||
} | ||
|
||
[Fact] | ||
public void Exponential_Ok() | ||
{ | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 0, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 1, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 2, TimeSpan.Zero).Should().Be(TimeSpan.Zero); | ||
|
||
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 0, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(1)); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 1, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(2)); | ||
RetryHelper.GetRetryDelay(RetryBackoffType.Exponential, 2, TimeSpan.FromSeconds(1)).Should().Be(TimeSpan.FromSeconds(4)); | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
src/Polly.Core.Tests/Retry/RetryResilienceStrategyBuilderExtensionsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
using Polly.Builder; | ||
using Polly.Retry; | ||
using Xunit; | ||
|
||
namespace Polly.Core.Tests.Retry; | ||
|
||
public class RetryResilienceStrategyBuilderExtensionsTests | ||
{ | ||
public static readonly TheoryData<Action<ResilienceStrategyBuilder>> OverloadsData = new() | ||
{ | ||
builder => | ||
{ | ||
builder.AddRetry(retry=>retry.Result(10)); | ||
AssertStrategy(builder, RetryBackoffType.Exponential, 3, TimeSpan.FromSeconds(2)); | ||
}, | ||
builder => | ||
{ | ||
builder.AddRetry(retry=>retry.Result(10), RetryBackoffType.Linear); | ||
AssertStrategy(builder, RetryBackoffType.Linear, 3, TimeSpan.FromSeconds(2)); | ||
}, | ||
builder => | ||
{ | ||
builder.AddRetry(retry=>retry.Result(10), RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1)); | ||
AssertStrategy(builder, RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1)); | ||
}, | ||
}; | ||
|
||
[MemberData(nameof(OverloadsData))] | ||
[Theory] | ||
public void AddRetry_Overloads_Ok(Action<ResilienceStrategyBuilder> configure) | ||
{ | ||
var builder = new ResilienceStrategyBuilder(); | ||
var options = new RetryStrategyOptions(); | ||
|
||
builder.Invoking(b => configure(b)).Should().NotThrow(); | ||
} | ||
|
||
[Fact] | ||
public void AddRetry_DefaultOptions_Ok() | ||
{ | ||
var builder = new ResilienceStrategyBuilder(); | ||
var options = new RetryStrategyOptions(); | ||
|
||
builder.AddRetry(options); | ||
|
||
AssertStrategy(builder, options.BackoffType, options.RetryCount, options.BaseDelay); | ||
} | ||
|
||
private static void AssertStrategy(ResilienceStrategyBuilder builder, RetryBackoffType type, int retries, TimeSpan delay) | ||
{ | ||
var strategy = (RetryResilienceStrategy)builder.Build(); | ||
|
||
strategy.BackoffType.Should().Be(type); | ||
strategy.RetryCount.Should().Be(retries); | ||
strategy.BaseDelay.Should().Be(delay); | ||
} | ||
|
||
[Fact] | ||
public void AddRetry_InvalidOptions_Throws() | ||
{ | ||
var builder = new ResilienceStrategyBuilder(); | ||
|
||
builder | ||
.Invoking(b => b.AddRetry(new RetryStrategyOptions { ShouldRetry = null! })) | ||
.Should() | ||
.Throw<ValidationException>() | ||
.WithMessage("The retry strategy options are invalid.*"); | ||
} | ||
} |
176 changes: 176 additions & 0 deletions
176
src/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
using Moq; | ||
using Polly.Retry; | ||
using Polly.Telemetry; | ||
|
||
namespace Polly.Core.Tests.Retry; | ||
|
||
public class RetryResilienceStrategyTests | ||
{ | ||
private readonly RetryStrategyOptions _options = new(); | ||
private readonly FakeTimeProvider _timeProvider = new(); | ||
private readonly Mock<ResilienceTelemetry> _telemetry = new(); | ||
|
||
[Fact] | ||
public void ShouldRetryEmpty_Skipped() | ||
{ | ||
bool called = false; | ||
_options.OnRetry.Add<int>(() => called = true); | ||
SetupNoDelay(); | ||
var sut = CreateSut(); | ||
|
||
sut.Execute(_ => 0, default); | ||
|
||
called.Should().BeFalse(); | ||
} | ||
|
||
[Fact] | ||
public void Retry_RetryCount_Respected() | ||
{ | ||
int calls = 0; | ||
_options.OnRetry.Add<int>(() => calls++); | ||
_options.ShouldRetry.Result<int>(0); | ||
_options.RetryCount = 12; | ||
SetupNoDelay(); | ||
var sut = CreateSut(); | ||
|
||
sut.Execute(_ => 0, default); | ||
|
||
calls.Should().Be(12); | ||
} | ||
|
||
[Fact] | ||
public void RetryException_RetryCount_Respected() | ||
{ | ||
int calls = 0; | ||
_options.OnRetry.Add<int>((args, _) => | ||
{ | ||
args.Exception.Should().BeOfType<InvalidOperationException>(); | ||
calls++; | ||
}); | ||
_options.ShouldRetry.Exception<InvalidOperationException>(); | ||
_options.RetryCount = 3; | ||
SetupNoDelay(); | ||
var sut = CreateSut(); | ||
|
||
Assert.Throws<InvalidOperationException>(() => sut.Execute<int>(_ => throw new InvalidOperationException(), default)); | ||
|
||
calls.Should().Be(3); | ||
} | ||
|
||
[Fact] | ||
public void Retry_Infinite_Respected() | ||
{ | ||
int calls = 0; | ||
_options.BackoffType = RetryBackoffType.Constant; | ||
_options.OnRetry.Add<int>((_, args) => | ||
{ | ||
if (args.Attempt > RetryConstants.MaxRetryCount) | ||
{ | ||
throw new InvalidOperationException(); | ||
} | ||
calls++; | ||
}); | ||
_options.ShouldRetry.Result(0); | ||
_options.RetryCount = RetryStrategyOptions.InfiniteRetryCount; | ||
SetupNoDelay(); | ||
var sut = CreateSut(); | ||
|
||
Assert.Throws<InvalidOperationException>(() => sut.Execute(_ => 0, default)); | ||
|
||
calls.Should().Be(RetryConstants.MaxRetryCount + 1); | ||
} | ||
|
||
[Fact] | ||
public void RetryDelayGenerator_Respected() | ||
{ | ||
int calls = 0; | ||
_options.OnRetry.Add<int>(() => calls++); | ||
_options.ShouldRetry.Result<int>(0); | ||
_options.RetryCount = 3; | ||
_options.BackoffType = RetryBackoffType.Constant; | ||
_options.RetryDelayGenerator.SetGenerator<int>((_, _) => TimeSpan.FromMilliseconds(123)); | ||
_timeProvider.SetupDelay(TimeSpan.FromMilliseconds(123)); | ||
|
||
var sut = CreateSut(); | ||
|
||
sut.Execute(_ => 0, default); | ||
|
||
_timeProvider.Verify(v => v.Delay(TimeSpan.FromMilliseconds(123), default), Times.Exactly(3)); | ||
} | ||
|
||
[Fact] | ||
public void OnRetry_EnsureCorrectArguments() | ||
{ | ||
var attempts = new List<int>(); | ||
var delays = new List<TimeSpan>(); | ||
_options.OnRetry.Add<int>((outcome, args) => | ||
{ | ||
attempts.Add(args.Attempt); | ||
delays.Add(args.RetryDelay); | ||
outcome.Exception.Should().BeNull(); | ||
outcome.Result.Should().Be(0); | ||
}); | ||
|
||
_options.ShouldRetry.Result<int>(0); | ||
_options.RetryCount = 3; | ||
_options.BackoffType = RetryBackoffType.Linear; | ||
_timeProvider.SetupAnyDelay(); | ||
|
||
var sut = CreateSut(); | ||
|
||
sut.Execute(_ => 0, default); | ||
|
||
attempts.Should().HaveCount(3); | ||
attempts[0].Should().Be(0); | ||
attempts[1].Should().Be(1); | ||
attempts[2].Should().Be(2); | ||
|
||
delays[0].Should().Be(TimeSpan.FromSeconds(2)); | ||
delays[1].Should().Be(TimeSpan.FromSeconds(4)); | ||
delays[2].Should().Be(TimeSpan.FromSeconds(6)); | ||
} | ||
|
||
[Fact] | ||
public void RetryDelayGenerator_EnsureCorrectArguments() | ||
{ | ||
var attempts = new List<int>(); | ||
var hints = new List<TimeSpan>(); | ||
_options.RetryDelayGenerator.SetGenerator<int>((outcome, args) => | ||
{ | ||
attempts.Add(args.Attempt); | ||
hints.Add(args.DelayHint); | ||
outcome.Exception.Should().BeNull(); | ||
outcome.Result.Should().Be(0); | ||
return TimeSpan.Zero; | ||
}); | ||
|
||
_options.ShouldRetry.Result<int>(0); | ||
_options.RetryCount = 3; | ||
_options.BackoffType = RetryBackoffType.Linear; | ||
_timeProvider.SetupAnyDelay(); | ||
|
||
var sut = CreateSut(); | ||
|
||
sut.Execute(_ => 0, default); | ||
|
||
attempts.Should().HaveCount(3); | ||
attempts[0].Should().Be(0); | ||
attempts[1].Should().Be(1); | ||
attempts[2].Should().Be(2); | ||
|
||
hints[0].Should().Be(TimeSpan.FromSeconds(2)); | ||
hints[1].Should().Be(TimeSpan.FromSeconds(4)); | ||
hints[2].Should().Be(TimeSpan.FromSeconds(6)); | ||
} | ||
|
||
private void SetupNoDelay() => _options.RetryDelayGenerator.SetGenerator<int>((_, _) => TimeSpan.Zero); | ||
|
||
private RetryResilienceStrategy CreateSut() | ||
{ | ||
return new RetryResilienceStrategy(_options, _timeProvider.Object, _telemetry.Object); | ||
} | ||
} |
Oops, something went wrong.