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()
{