Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use delegates in RetryStrategyOptions #1214

Merged
merged 3 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions src/Polly.Core.Benchmarks/Utils/Helper.Retry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Polly.Strategy;

namespace Polly.Core.Benchmarks.Utils;

Expand All @@ -18,17 +19,19 @@ public static object CreateRetry(PollyVersion technology)

PollyVersion.V8 => CreateStrategy(builder =>
{
var options = new RetryStrategyOptions<string>
builder.AddRetry(new RetryStrategyOptions<string>
{
RetryCount = 3,
BackoffType = RetryBackoffType.Constant,
BaseDelay = delay
};

options.ShouldRetry.HandleOutcome((outcome, _) => outcome.Result == Failure || outcome.Exception is InvalidOperationException);
options.OnRetry.Register(() => { });

builder.AddRetry(options);
BaseDelay = delay,
ShouldRetry = (outcome, _) => outcome switch
{
{ Exception: InvalidOperationException } => PredicateResult.True,
{ Result: string result } when result == Failure => PredicateResult.True,
_ => PredicateResult.False
},
OnRetry = (_, _) => default
});
}),
_ => throw new NotSupportedException()
};
Expand Down
28 changes: 15 additions & 13 deletions src/Polly.Core.Tests/Issues/IssuesTests.FlowingContext_849.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Polly.Retry;
using Polly.Strategy;

namespace Polly.Core.Tests.Issues;

Expand All @@ -8,19 +9,20 @@ public partial class IssuesTests
public void FlowingContext_849()
{
var contextChecked = false;
var retryOptions = new RetryStrategyOptions();

// configure the predicate and use the context
retryOptions.ShouldRetry.HandleResult<int>((_, args) =>
{
// access the context to evaluate the retry
ResilienceContext context = args.Context;
context.Should().NotBeNull();
contextChecked = true;
return false;
});

var strategy = new ResilienceStrategyBuilder().AddRetry(retryOptions).Build();
var strategy = new ResilienceStrategyBuilder<int>()
.AddRetry(new RetryStrategyOptions<int>
{
// configure the predicate and use the context
ShouldRetry = (_, args) =>
{
// access the context to evaluate the retry
ResilienceContext context = args.Context;
context.Should().NotBeNull();
contextChecked = true;
return PredicateResult.False;
}
})
.Build();

// execute the retry
strategy.Execute(_ => 0);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Polly.Retry;
using Polly.Strategy;

namespace Polly.Core.Tests.Issues;

Expand All @@ -13,17 +14,23 @@ public void HandleMultipleResults_898()
BackoffType = RetryBackoffType.Constant,
RetryCount = 1,
BaseDelay = TimeSpan.FromMilliseconds(1),
ShouldRetry = (outcome, _) => outcome switch
{
// handle string results
{ Result: string res } when res == "error" => PredicateResult.True,

// handle int results
{ Result: int res } when res == -1 => PredicateResult.True,
_ => PredicateResult.False
},
OnRetry = (_, args) =>
{
// add a callback updates the resilience context with the retry marker
args.Context.Properties.Set(isRetryKey, true);
return default;
}
};

// now add a callback updates the resilience context with the retry marker
options.OnRetry.Register((_, args) => args.Context.Properties.Set(isRetryKey, true));

// handle int results
options.ShouldRetry.HandleResult(-1);

// handle string results
options.ShouldRetry.HandleResult("error");

// create the strategy
var strategy = new ResilienceStrategyBuilder { TimeProvider = TimeProvider.Object }.AddRetry(options).Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public class RetryResilienceStrategyBuilderExtensionsTests
{
BackoffType = RetryBackoffType.Exponential,
RetryCount = 3,
BaseDelay = TimeSpan.FromSeconds(2)
BaseDelay = TimeSpan.FromSeconds(2),
ShouldRetry = (_, _) => PredicateResult.True,
});

AssertStrategy(builder, RetryBackoffType.Exponential, 3, TimeSpan.FromSeconds(2));
Expand All @@ -32,12 +33,12 @@ public class RetryResilienceStrategyBuilderExtensionsTests
},
builder =>
{
builder.AddRetry(retry=>retry.HandleResult(10), RetryBackoffType.Linear);
builder.AddRetry(retry => retry.HandleResult(10), RetryBackoffType.Linear);
AssertStrategy(builder, RetryBackoffType.Linear, 3, TimeSpan.FromSeconds(2));
},
builder =>
{
builder.AddRetry(retry=>retry.HandleResult(10), RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1));
builder.AddRetry(retry => retry.HandleResult(10), RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1));
AssertStrategy(builder, RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1));
},
builder =>
Expand All @@ -46,9 +47,9 @@ public class RetryResilienceStrategyBuilderExtensionsTests

AssertStrategy(builder, RetryBackoffType.ExponentialWithJitter, 3, TimeSpan.FromSeconds(2), strategy =>
{
var args = new RetryDelayArguments(ResilienceContext.Get(), 8, TimeSpan.Zero);
var args = new RetryDelayArguments(ResilienceContext.Get().Initialize<int>(true), 8, TimeSpan.Zero);

strategy.DelayGenerator!.GenerateAsync(new Outcome<int>(new InvalidOperationException()), args).Result.Should().Be(TimeSpan.FromMilliseconds(8));
strategy.DelayGenerator!(new Outcome(new InvalidOperationException()), args).Result.Should().Be(TimeSpan.FromMilliseconds(8));
});
},
builder =>
Expand All @@ -57,7 +58,8 @@ public class RetryResilienceStrategyBuilderExtensionsTests
{
BackoffType = RetryBackoffType.Exponential,
RetryCount = 3,
BaseDelay = TimeSpan.FromSeconds(2)
BaseDelay = TimeSpan.FromSeconds(2),
ShouldRetry = (_, _) => PredicateResult.True
});

AssertStrategy(builder, RetryBackoffType.Exponential, 3, TimeSpan.FromSeconds(2));
Expand Down Expand Up @@ -86,7 +88,7 @@ public void AddRetry_GenericOverloads_Ok(Action<ResilienceStrategyBuilder<int>>
public void AddRetry_DefaultOptions_Ok()
{
var builder = new ResilienceStrategyBuilder();
var options = new RetryStrategyOptions();
var options = new RetryStrategyOptions { ShouldRetry = (_, _) => PredicateResult.True };

builder.AddRetry(options);

Expand Down
55 changes: 28 additions & 27 deletions src/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class RetryResilienceStrategyTests
public void ShouldRetryEmpty_Skipped()
{
bool called = false;
_options.OnRetry.Register<int>(() => called = true);
_options.OnRetry = (_, _) => { called = true; return default; };
SetupNoDelay();
var sut = CreateSut();

Expand All @@ -44,7 +44,7 @@ public void ExecuteAsync_MultipleRetries_EnsureDiscardedResultsDisposed()
_options.RetryCount = 5;
SetupNoDelay();
_timeProvider.SetupAnyDelay();
_options.ShouldRetry.HandleResult<DisposableResult>(_ => true);
_options.ShouldRetry = (_, _) => PredicateResult.True;
var results = new List<DisposableResult>();
var sut = CreateSut();

Expand All @@ -69,8 +69,8 @@ public void ExecuteAsync_MultipleRetries_EnsureDiscardedResultsDisposed()
public void Retry_RetryCount_Respected()
{
int calls = 0;
_options.OnRetry.Register<int>(() => calls++);
_options.ShouldRetry.HandleResult<int>(0);
_options.OnRetry = (_, _) => { calls++; return default; };
_options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0);
_options.RetryCount = 12;
SetupNoDelay();
var sut = CreateSut();
Expand All @@ -84,12 +84,14 @@ public void Retry_RetryCount_Respected()
public void RetryException_RetryCount_Respected()
{
int calls = 0;
_options.OnRetry.Register<int>((args, _) =>
_options.OnRetry = (outcome, _) =>
{
args.Exception.Should().BeOfType<InvalidOperationException>();
outcome.Exception.Should().BeOfType<InvalidOperationException>();
calls++;
});
_options.ShouldRetry.HandleException<InvalidOperationException>();
return default;
};

_options.ShouldRetry = (outcome, _) => outcome.ExceptionPredicateAsync<InvalidOperationException>();
_options.RetryCount = 3;
SetupNoDelay();
var sut = CreateSut();
Expand All @@ -104,16 +106,17 @@ public void Retry_Infinite_Respected()
{
int calls = 0;
_options.BackoffType = RetryBackoffType.Constant;
_options.OnRetry.Register<int>((_, args) =>
_options.OnRetry = (_, args) =>
{
if (args.Attempt > RetryConstants.MaxRetryCount)
{
throw new InvalidOperationException();
}

calls++;
});
_options.ShouldRetry.HandleResult(0);
return default;
};
_options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0);
_options.RetryCount = RetryStrategyOptions.InfiniteRetryCount;
SetupNoDelay();
var sut = CreateSut();
Expand All @@ -127,11 +130,11 @@ public void Retry_Infinite_Respected()
public void RetryDelayGenerator_Respected()
{
int calls = 0;
_options.OnRetry.Register<int>(() => calls++);
_options.ShouldRetry.HandleResult<int>(0);
_options.OnRetry = (_, _) => { calls++; return default; };
_options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0);
_options.RetryCount = 3;
_options.BackoffType = RetryBackoffType.Constant;
_options.RetryDelayGenerator.SetGenerator<int>((_, _) => TimeSpan.FromMilliseconds(123));
_options.RetryDelayGenerator = (_, _) => new ValueTask<TimeSpan>(TimeSpan.FromMilliseconds(123));
_timeProvider.SetupDelay(TimeSpan.FromMilliseconds(123));

var sut = CreateSut();
Expand All @@ -146,16 +149,17 @@ public void OnRetry_EnsureCorrectArguments()
{
var attempts = new List<int>();
var delays = new List<TimeSpan>();
_options.OnRetry.Register<int>((outcome, args) =>
_options.OnRetry = (outcome, args) =>
{
attempts.Add(args.Attempt);
delays.Add(args.RetryDelay);

outcome.Exception.Should().BeNull();
outcome.Result.Should().Be(0);
});
return default;
};

_options.ShouldRetry.HandleResult<int>(0);
_options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0);
_options.RetryCount = 3;
_options.BackoffType = RetryBackoffType.Linear;
_timeProvider.SetupAnyDelay();
Expand All @@ -182,7 +186,7 @@ public void OnRetry_EnsureTelemetry()

_diagnosticSource.Setup(v => v.IsEnabled("OnRetry")).Returns(true);

_options.ShouldRetry.HandleResult(0);
_options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0);
_options.RetryCount = 3;
_options.BackoffType = RetryBackoffType.Linear;
_timeProvider.SetupAnyDelay();
Expand All @@ -199,18 +203,18 @@ public void RetryDelayGenerator_EnsureCorrectArguments()
{
var attempts = new List<int>();
var hints = new List<TimeSpan>();
_options.RetryDelayGenerator.SetGenerator<int>((outcome, args) =>
_options.RetryDelayGenerator = (outcome, args) =>
{
attempts.Add(args.Attempt);
hints.Add(args.DelayHint);

outcome.Exception.Should().BeNull();
outcome.Result.Should().Be(0);

return TimeSpan.Zero;
});
return new ValueTask<TimeSpan>(TimeSpan.Zero);
};

_options.ShouldRetry.HandleResult<int>(0);
_options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0);
_options.RetryCount = 3;
_options.BackoffType = RetryBackoffType.Linear;
_timeProvider.SetupAnyDelay();
Expand All @@ -229,10 +233,7 @@ public void RetryDelayGenerator_EnsureCorrectArguments()
hints[2].Should().Be(TimeSpan.FromSeconds(6));
}

private void SetupNoDelay() => _options.RetryDelayGenerator.SetGenerator<int>((_, _) => TimeSpan.Zero);
private void SetupNoDelay() => _options.RetryDelayGenerator = (_, _) => new ValueTask<TimeSpan>(TimeSpan.Zero);

private RetryResilienceStrategy CreateSut()
{
return new RetryResilienceStrategy(_options, _timeProvider.Object, _telemetry, new RandomUtil(0));
}
private RetryResilienceStrategy CreateSut() => new(_options, _timeProvider.Object, _telemetry, new RandomUtil(0));
}
37 changes: 20 additions & 17 deletions src/Polly.Core.Tests/Retry/RetryStrategyOptionsTResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ public void Ctor_Ok()
var options = new RetryStrategyOptions<int>();

options.StrategyType.Should().Be("Retry");
options.ShouldRetry.Should().NotBeNull();
options.ShouldRetry.IsEmpty.Should().BeTrue();
options.ShouldRetry.Should().BeNull();

options.RetryDelayGenerator.Should().NotBeNull();
options.RetryDelayGenerator.IsEmpty.Should().BeTrue();
options.RetryDelayGenerator.Should().BeNull();

options.OnRetry.Should().NotBeNull();
options.OnRetry.IsEmpty.Should().BeTrue();
options.OnRetry.Should().BeNull();

options.RetryCount.Should().Be(3);
options.BackoffType.Should().Be(RetryBackoffType.ExponentialWithJitter);
Expand Down Expand Up @@ -49,8 +46,6 @@ Invalid Options
The field RetryCount must be between -1 and 100.
The field BaseDelay must be >= to 00:00:00.
The ShouldRetry field is required.
The RetryDelayGenerator field is required.
The OnRetry field is required.
""");
}

Expand All @@ -64,11 +59,11 @@ public async Task AsNonGenericOptions_Ok()
BaseDelay = TimeSpan.FromMilliseconds(555),
RetryCount = 7,
StrategyName = "my-name",
ShouldRetry = (outcome, _) => new ValueTask<bool>(outcome.HasResult && outcome.Result == 999),
OnRetry = (_, _) => { called = true; return default; },
RetryDelayGenerator = (_, _) => new ValueTask<TimeSpan>(TimeSpan.FromSeconds(123))
};

options.ShouldRetry.HandleResult(999);
options.OnRetry.Register(() => called = true);
options.RetryDelayGenerator.SetGenerator((_, _) => TimeSpan.FromSeconds(123));
var nonGenericOptions = options.AsNonGenericOptions();

nonGenericOptions.BackoffType.Should().Be(RetryBackoffType.Constant);
Expand All @@ -77,13 +72,21 @@ public async Task AsNonGenericOptions_Ok()
nonGenericOptions.StrategyName.Should().Be("my-name");
nonGenericOptions.StrategyType.Should().Be("Retry");

var args = new ShouldRetryArguments(ResilienceContext.Get(), 0);
var delayArgs = new RetryDelayArguments(ResilienceContext.Get(), 2, TimeSpan.FromMinutes(1));
var retryArgs = new OnRetryArguments(ResilienceContext.Get(), 0, TimeSpan.Zero);
// wrong result type
var context = ResilienceContext.Get().Initialize<string>(true);
var args = new ShouldRetryArguments(context, 0);
var delayArgs = new RetryDelayArguments(context, 2, TimeSpan.FromMinutes(1));
var retryArgs = new OnRetryArguments(context, 0, TimeSpan.Zero);
(await nonGenericOptions.ShouldRetry!(new Outcome(999), args)).Should().BeFalse();
await nonGenericOptions.OnRetry!(new Outcome(999), retryArgs);
(await nonGenericOptions.RetryDelayGenerator!(new Outcome(999), delayArgs)).Should().Be(TimeSpan.MinValue);
called.Should().BeFalse();

(await nonGenericOptions.ShouldRetry.CreateHandler()!.ShouldHandleAsync(new Outcome<int>(999), args)).Should().BeTrue();
await nonGenericOptions.OnRetry.CreateHandler()!.HandleAsync(new Outcome<int>(999), retryArgs);
// correct result type
context.Initialize<int>(true);
(await nonGenericOptions.ShouldRetry!(new Outcome(999), args)).Should().BeTrue();
await nonGenericOptions.OnRetry!(new Outcome(999), retryArgs);
(await nonGenericOptions.RetryDelayGenerator!(new Outcome(999), delayArgs)).Should().Be(TimeSpan.FromSeconds(123));
called.Should().BeTrue();
(await nonGenericOptions.RetryDelayGenerator.CreateHandler(default, _ => true)!.GenerateAsync(new Outcome<int>(999), delayArgs)).Should().Be(TimeSpan.FromSeconds(123));
}
}
Loading