App-vNext/Polly · error · ArgumentNullException

onRetry

Error message

onRetry

What it means

ArgumentNullException is thrown when onRetry is null in WaitAndRetryAsync(IEnumerable<TimeSpan> sleepDurations, Action<Exception,TimeSpan,int,Context> onRetry). This is the full-arity synchronous convenience overload that wraps into the terminal async overload.

Source

Thrown at src/Polly/Retry/AsyncRetrySyntax.cs:807

            onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx));
    }

    /// <summary>
    ///     Builds an <see cref="AsyncRetryPolicy" /> that will wait and retry as many times as there are provided
    ///     <paramref name="sleepDurations" />
    ///     calling <paramref name="onRetry" /> on each retry with the raised exception, the current sleep duration, retry count, and context data.
    ///     On each retry, the duration to wait is the current <paramref name="sleepDurations" /> item.
    /// </summary>
    /// <param name="policyBuilder">The policy builder.</param>
    /// <param name="sleepDurations">The sleep durations to wait for on each retry.</param>
    /// <param name="onRetry">The action to call on each retry.</param>
    /// <returns>The policy instance.</returns>
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="sleepDurations"/> or <paramref name="onRetry"/> is <see langword="null"/>.</exception>
    public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations, Action<Exception, TimeSpan, int, Context> onRetry)
    {
        if (onRetry == null)
        {
            throw new ArgumentNullException(nameof(onRetry));
        }

#pragma warning disable 1998 // async method has no awaits, will run synchronously
        return policyBuilder.WaitAndRetryAsync(
            sleepDurations,
            onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx));
#pragma warning restore 1998
    }

    /// <summary>
    ///     Builds an <see cref="AsyncRetryPolicy" /> that will wait and retry as many times as there are provided
    ///     <paramref name="sleepDurations" />
    ///     calling <paramref name="onRetryAsync" /> on each retry with the raised exception, the current sleep duration, retry count, and context data.
    ///     On each retry, the duration to wait is the current <paramref name="sleepDurations" /> item.
    /// </summary>
    /// <param name="policyBuilder">The policy builder.</param>
    /// <param name="sleepDurations">The sleep durations to wait for on each retry.</param>
    /// <param name="onRetryAsync">The action to call asynchronously on each retry.</param>

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Supply a non-null Action<Exception, TimeSpan, int, Context>.
  2. Use the overload without callback: WaitAndRetryAsync(sleepDurations).
  3. Default to a no-op: (ex, delay, retry, ctx) => { }.

Example fix

// before
.WaitAndRetryAsync(durations, null)

// after
.WaitAndRetryAsync(durations, (ex, delay, retry, ctx) =>
    logger.LogWarning("Attempt {Retry} after {Delay}", retry, delay))
Defensive patterns

Strategy: validation

Validate before calling

if (onRetry is null)
    onRetry = (_, _, _, _) => { };

Type guard

static bool IsValidCallback(Action<Exception, TimeSpan, int, Context> cb) => cb is not null;

Prevention

When it happens

Trigger: Calling this overload with a null onRetry action that receives exception, delay, retry count, and context.

Common situations: A full-arity callback left null because the developer copied the call from documentation but removed the callback body.

Related errors


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