App-vNext/Polly · error · ArgumentNullException

action

Error message

action

What it means

ArgumentNullException (ParamName "action") is thrown by the non-generic AsyncTimeoutPolicy.ImplementationAsync (AsyncTimeoutPolicy.cs:30-33) when the delegate passed for execution is null. This is the protected execution entry point; the null check protects the engine before it dereferences the action to start the task. It indicates the caller handed the policy a null lambda rather than a real operation.

Source

Thrown at src/Polly/Timeout/AsyncTimeoutPolicy.cs:32

        TimeoutStrategy timeoutStrategy,
        Func<Context, TimeSpan, Task, Exception, Task> onTimeoutAsync)
    {
        _timeoutProvider = timeoutProvider;
        _timeoutStrategy = timeoutStrategy;
        _onTimeoutAsync = onTimeoutAsync;
    }

    /// <inheritdoc/>
    [DebuggerStepThrough]
    protected override Task<TResult> ImplementationAsync<TResult>(
        Func<Context, CancellationToken, Task<TResult>> action,
        Context context,
        CancellationToken cancellationToken,
        bool continueOnCapturedContext)
    {
        if (action is null)
        {
            throw new ArgumentNullException(nameof(action));
        }

        return AsyncTimeoutEngine.ImplementationAsync(
            action,
            context,
            _timeoutProvider,
            _timeoutStrategy,
            _onTimeoutAsync,
            continueOnCapturedContext,
            cancellationToken);
    }
}

/// <summary>
/// A timeout policy which can be applied to async delegates.
/// </summary>
/// <typeparam name="TResult">The return type of delegates which may be executed through the policy.</typeparam>
public class AsyncTimeoutPolicy<TResult> : AsyncPolicy<TResult>, ITimeoutPolicy<TResult>

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Pass a non-null delegate: policy.ExecuteAsync(ct => DoWorkAsync(ct)).
  2. Guard the action variable before execution: if (action is null) throw new InvalidOperationException(...).
  3. Ensure DI registrations for the delegate factory return a real delegate, never null.

Example fix

// before
Func<CancellationToken, Task<string>> action = GetAction(); // returns null
await timeoutPolicy.ExecuteAsync(action);

// after
Func<CancellationToken, Task<string>> action = GetAction()
    ?? throw new InvalidOperationException("action factory returned null");
await timeoutPolicy.ExecuteAsync(action);
Defensive patterns

Strategy: validation

Validate before calling

if (action is null) throw new InvalidOperationException("Execution action must be provided.");
await timeoutPolicy.ExecuteAsync(action);

Type guard

static bool IsValidAction<TResult>(Func<Context, CancellationToken, Task<TResult>> action)
    => action is not null;

Prevention

When it happens

Trigger: Calling policy.ExecuteAsync(null) or ExecuteAsync((Func<...>)null) on an AsyncTimeoutPolicy; passing a null Func<Context,CancellationToken,Task<TResult>> directly through the lower-level ImplementationAsync surface.

Common situations: Refactor that extracted the operation into a variable that resolved to null; DI failing to resolve the delegate factory; tests stubbing the action parameter; conditional code paths that forget to assign the action.

Related errors


AI-assisted analysis of App-vNext/Polly@d0e46bdb1e (2026-08-13). Data as JSON: /api/errors/9fff3ca9eeaef3d5. Report an issue: GitHub.