App-vNext/Polly · error · TimeoutRejectedException

The delegate executed through TimeoutPolicy did not complete

Error message

The delegate executed through TimeoutPolicy did not complete within the timeout.

What it means

Runtime TimeoutRejectedException thrown by the legacy TimeoutEngine when the executed delegate did not finish before the timeout elapsed. The engine observes an OperationCanceledException together with the timeout CancellationTokenSource being in a cancellation-requested state, then calls the onTimeout callback and throws TimeoutRejectedException carrying the applied timeout and the original exception. Unlike the 10 argument-validation errors above, this fires during policy execution, not construction.

Source

Thrown at src/Polly/Timeout/TimeoutEngine.cs:65

                 */
                actionTask.Wait(timeoutCancellationTokenSource.Token);
            }
            catch (AggregateException ex) when (ex.InnerExceptions.Count == 1)
            {
                // Issue #270. Unwrap extra AggregateException caused by the way pessimistic timeout policy for synchronous executions is necessarily constructed.
                ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
            }

            return actionTask.Result;
        }
        catch (Exception ex)
        {
            // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token)
            // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken.
            if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested)
            {
                onTimeout(context, timeout, actionTask, ex);
                throw new TimeoutRejectedException("The delegate executed through TimeoutPolicy did not complete within the timeout.", timeout, ex);
            }

            throw;
        }
    }
}

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Increase the timeout to match the realistic latency of the wrapped delegate, or fix the delegate to be faster.
  2. Combine Timeout with Retry (using a PolicyWrap) so transient timeouts are retried with backoff.
  3. Ensure the delegate honors the CancellationToken — Optimistic timeout relies on cooperative cancellation; pass ct to HttpClient/EF/etc.
  4. For non-cooperative code, use TimeoutStrategy.Pessimistic so the policy abandons the task instead of waiting on cancellation.
  5. Add a Fallback policy in the wrap to return a default or cached value when TimeoutRejectedException fires.

Example fix

// before
var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1));
await policy.ExecuteAsync(async ct => await httpClient.GetAsync(url, ct), CancellationToken.None); // throws on slow network

// after
var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(5));
var retry = Policy.Handle<TimeoutRejectedException>().WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(i));
var wrap = Policy.WrapAsync(retry, policy);
await wrap.ExecuteAsync(async ct => await httpClient.GetAsync(url, ct), CancellationToken.None);
Defensive patterns

Strategy: try-catch

Validate before calling

if (timeout <= TimeSpan.Zero) throw new InvalidOperationException("timeout must be positive");
// runtime: instrument the delegate's own latency budget so the timeout reflects p99

Type guard

static bool IsTimeoutRejected(Exception e) => e is TimeoutRejectedException;

Try / catch

try
{
    await policy.ExecuteAsync(async ct => await work(ct), cancellationToken);
}
catch (TimeoutRejectedException ex)
{
    logger.LogWarning(ex, "delegate exceeded timeout {Timeout}", ex.Timeout);
    // degrade gracefully, return cached/default, or rethrow to an outer fallback
}

Prevention

When it happens

Trigger: The wrapped delegate runs slower than the configured timeout under Optimistic strategy (cancellation cooperatively observed) — or, under Pessimistic strategy, the delegate did not observe cancellation and was abandoned. Slow downstream calls (HTTP, DB), deadlocks, or unbounded loops all trigger it.

Common situations: An HTTP call to a degraded upstream that takes longer than the policy's timeout; a database query without its own command timeout; optimistic cancellation not honored by a blocking synchronous call; timeout set lower than the real p99 latency of the dependency; cascading latency under load.

Understand the failure class

Related errors


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