diff --git a/src/Polly/Bulkhead/BulkheadPolicy.cs b/src/Polly/Bulkhead/BulkheadPolicy.cs index b985c501dd0..68d257701b5 100644 --- a/src/Polly/Bulkhead/BulkheadPolicy.cs +++ b/src/Polly/Bulkhead/BulkheadPolicy.cs @@ -4,7 +4,6 @@ namespace Polly.Bulkhead; /// /// A bulkhead-isolation policy which can be applied to delegates. /// -#pragma warning disable CA1062 // Validate arguments of public methods public class BulkheadPolicy : Policy, IBulkheadPolicy { private readonly SemaphoreSlim _maxParallelizationSemaphore; @@ -25,8 +24,21 @@ internal BulkheadPolicy( /// [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) => - BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + return BulkheadEngine.Implementation( + action, + context, + _onBulkheadRejected, + _maxParallelizationSemaphore, + _maxQueuedActionsSemaphore, + cancellationToken); + } /// /// Gets the number of slots currently available for executing actions through the bulkhead. @@ -73,8 +85,20 @@ internal BulkheadPolicy( /// [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) => - BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + return BulkheadEngine.Implementation( + action, + context, + _onBulkheadRejected, + _maxParallelizationSemaphore, + _maxQueuedActionsSemaphore, cancellationToken); + } /// /// Gets the number of slots currently available for executing actions through the bulkhead. diff --git a/test/Polly.Specs/Bulkhead/BulkheadSpecs.cs b/test/Polly.Specs/Bulkhead/BulkheadSpecs.cs index cfbf37094bd..95bd0b6dbc7 100644 --- a/test/Polly.Specs/Bulkhead/BulkheadSpecs.cs +++ b/test/Polly.Specs/Bulkhead/BulkheadSpecs.cs @@ -10,6 +10,34 @@ public BulkheadSpecs(ITestOutputHelper testOutputHelper) #region Configuration + [Fact] + public void Should_throw_when_action_is_null() + { + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + Func action = null!; + var maxParallelization = 1; + var maxQueueingActions = 1; + Action onBulkheadRejected = _ => { }; + + var instance = Activator.CreateInstance( + typeof(BulkheadPolicy), + flags, + null, + [maxParallelization, maxQueueingActions, onBulkheadRejected], + null)!; + var instanceType = instance.GetType(); + var methods = instanceType.GetMethods(flags); + var methodInfo = methods.First(method => method is { Name: "Implementation", ReturnType.Name: "TResult" }); + var generic = methodInfo.MakeGenericMethod(typeof(EmptyStruct)); + + var func = () => generic.Invoke(instance, [action, new Context(), CancellationToken.None]); + + 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_maxParallelization_less_or_equal_to_zero_and_no_maxQueuingActions() { diff --git a/test/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs b/test/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs index e31972184d3..197a1e46feb 100644 --- a/test/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs +++ b/test/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs @@ -10,6 +10,33 @@ public BulkheadTResultSpecs(ITestOutputHelper testOutputHelper) #region Configuration + [Fact] + public void Should_throw_when_action_is_null() + { + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + Func action = null!; + var maxParallelization = 1; + var maxQueueingActions = 1; + Action onBulkheadRejected = _ => { }; + + var instance = Activator.CreateInstance( + typeof(BulkheadPolicy), + flags, + null, + [maxParallelization, maxQueueingActions, onBulkheadRejected], + null)!; + var instanceType = instance.GetType(); + var methods = instanceType.GetMethods(flags); + var methodInfo = methods.First(method => method is { Name: "Implementation", ReturnType.Name: "EmptyStruct" }); + + var func = () => methodInfo.Invoke(instance, [action, new Context(), CancellationToken.None]); + + 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_maxParallelization_less_or_equal_to_zero_and_no_maxQueuingActions() {