diff --git a/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs b/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs
index a055a420c75..b0f787ea1df 100644
--- a/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs
+++ b/src/Polly/Bulkhead/AsyncBulkheadPolicy.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 AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy
{
private readonly SemaphoreSlim _maxParallelizationSemaphore;
@@ -35,9 +34,26 @@ internal AsyncBulkheadPolicy(
///
[DebuggerStepThrough]
- protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken,
- bool continueOnCapturedContext) =>
- AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, continueOnCapturedContext, cancellationToken);
+ protected override Task ImplementationAsync(
+ Func> action,
+ Context context,
+ CancellationToken cancellationToken,
+ bool continueOnCapturedContext)
+ {
+ if (action is null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ return AsyncBulkheadEngine.ImplementationAsync(
+ action,
+ context,
+ _onBulkheadRejectedAsync,
+ _maxParallelizationSemaphore,
+ _maxQueuedActionsSemaphore,
+ continueOnCapturedContext,
+ cancellationToken);
+ }
///
public void Dispose()
@@ -71,8 +87,26 @@ internal AsyncBulkheadPolicy(
///
[DebuggerStepThrough]
- protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) =>
- AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, continueOnCapturedContext, cancellationToken);
+ protected override Task ImplementationAsync(
+ Func> action,
+ Context context,
+ CancellationToken cancellationToken,
+ bool continueOnCapturedContext)
+ {
+ if (action is null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ return AsyncBulkheadEngine.ImplementationAsync(
+ action,
+ context,
+ _onBulkheadRejectedAsync,
+ _maxParallelizationSemaphore,
+ _maxQueuedActionsSemaphore,
+ continueOnCapturedContext,
+ cancellationToken);
+ }
///
/// Gets the number of slots currently available for executing actions through the bulkhead.
diff --git a/test/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs b/test/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs
index 12bbd58788c..80a84ae62db 100644
--- a/test/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs
+++ b/test/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs
@@ -5,6 +5,34 @@ public class BulkheadAsyncSpecs(ITestOutputHelper testOutputHelper) : BulkheadSp
{
#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;
+ Func onBulkheadRejectedAsync = (_) => Task.CompletedTask;
+
+ var instance = Activator.CreateInstance(
+ typeof(AsyncBulkheadPolicy),
+ flags,
+ null,
+ [maxParallelization, maxQueueingActions, onBulkheadRejectedAsync],
+ 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_maxParallelization_less_or_equal_to_zero_and_no_maxQueuingActions()
{
diff --git a/test/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs b/test/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs
index fe1fc1aa5f4..8c155ecccc8 100644
--- a/test/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs
+++ b/test/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs
@@ -10,6 +10,33 @@ public BulkheadTResultAsyncSpecs(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;
+ Func onBulkheadRejectedAsync = (_) => Task.CompletedTask;
+
+ var instance = Activator.CreateInstance(
+ typeof(AsyncBulkheadPolicy),
+ flags,
+ null,
+ [maxParallelization, maxQueueingActions, onBulkheadRejectedAsync],
+ 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_maxParallelization_less_or_equal_to_zero_and_no_maxQueuingActions()
{