diff --git a/src/Polly/Retry/AsyncRetryPolicy.cs b/src/Polly/Retry/AsyncRetryPolicy.cs index e8ce73c90c8..1991941df1c 100644 --- a/src/Polly/Retry/AsyncRetryPolicy.cs +++ b/src/Polly/Retry/AsyncRetryPolicy.cs @@ -3,7 +3,6 @@ /// /// A retry policy that can be applied to asynchronous delegates. /// -#pragma warning disable CA1062 // Validate arguments of public methods public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy { private readonly Func _onRetryAsync; @@ -34,6 +33,11 @@ protected override Task ImplementationAsync( CancellationToken cancellationToken, bool continueOnCapturedContext) { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + var sleepDurationProvider = _sleepDurationProvider != null ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) : (Func, Context, TimeSpan>)null; @@ -79,9 +83,18 @@ internal AsyncRetryPolicy( /// [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) => - AsyncRetryEngine.ImplementationAsync( + protected override Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + return AsyncRetryEngine.ImplementationAsync( action, context, ExceptionPredicates, @@ -92,5 +105,6 @@ protected override Task ImplementationAsync(Func> action = null!; + var policyBuilder = new PolicyBuilder(exception => exception); + Func onRetryAsync = (_, _, _, _) => Task.CompletedTask; + int permittedRetryCount = int.MaxValue; + IEnumerable? sleepDurationsEnumerable = null; + Func sleepDurationProvider = null!; + + var instance = Activator.CreateInstance( + typeof(AsyncRetryPolicy), + flags, + null, + [policyBuilder, onRetryAsync, permittedRetryCount, sleepDurationsEnumerable, sleepDurationProvider], + null)!; + var instanceType = instance.GetType(); + var methods = instanceType.GetMethods(flags); + var methodInfo = methods.First(method => method is { Name: "ImplementationAsync", ReturnType.Name: "Task`1" }); + var generic = methodInfo.MakeGenericMethod(typeof(EmptyStruct)); + + var func = () => generic.Invoke(instance, [action, new Context(), CancellationToken.None, false]); + + var exceptionAssertions = func.Should().Throw(); + exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation."); + exceptionAssertions.And.InnerException.Should().BeOfType() + .Which.ParamName.Should().Be("action"); + } + [Fact] public void Should_throw_when_retry_count_is_less_than_zero_without_context() { diff --git a/test/Polly.Specs/Retry/RetryTResultSpecsAsync.cs b/test/Polly.Specs/Retry/RetryTResultSpecsAsync.cs index 9ebeb0948a2..cd3b9e5ef2a 100644 --- a/test/Polly.Specs/Retry/RetryTResultSpecsAsync.cs +++ b/test/Polly.Specs/Retry/RetryTResultSpecsAsync.cs @@ -4,6 +4,35 @@ namespace Polly.Specs.Retry; public class RetryTResultSpecsAsync { + [Fact] + public void Should_throw_when_action_is_null() + { + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + Func> action = null!; + var policyBuilder = new PolicyBuilder(exception => exception); + Func, TimeSpan, int, Context, Task> onRetryAsync = (_, _, _, _) => Task.CompletedTask; + int permittedRetryCount = int.MaxValue; + IEnumerable? sleepDurationsEnumerable = null; + Func, Context, TimeSpan> sleepDurationProvider = null!; + + var instance = Activator.CreateInstance( + typeof(AsyncRetryPolicy), + flags, + null, + [policyBuilder, onRetryAsync, permittedRetryCount, sleepDurationsEnumerable, sleepDurationProvider], + null)!; + var instanceType = instance.GetType(); + var methods = instanceType.GetMethods(flags); + var methodInfo = methods.First(method => method is { Name: "ImplementationAsync", ReturnType.Name: "Task`1" }); + + var func = () => methodInfo.Invoke(instance, [action, new Context(), CancellationToken.None, false]); + + var exceptionAssertions = func.Should().Throw(); + exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation."); + exceptionAssertions.And.InnerException.Should().BeOfType() + .Which.ParamName.Should().Be("action"); + } + [Fact] public void Should_throw_when_retry_count_is_less_than_zero_without_context() {