> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/App-vNext/Polly/llms.txt
> Use this file to discover all available pages before exploring further.

# Retry Strategy

> Automatically retry failed operations with configurable delays and backoff algorithms

# Retry Strategy

The retry **reactive** resilience strategy re-executes the same callback method if its execution fails. Failure can be either an `Exception` or a result object indicating unsuccessful processing.

<Note>
  Between retry attempts, the retry strategy waits a specified amount of time. You have fine-grained control over how to calculate the next delay.
</Note>

## When to Use Retry

Use the retry strategy when:

* Dealing with transient failures that are likely to self-correct
* Calling remote services that may experience temporary network issues
* Accessing resources that may be temporarily unavailable
* Working with rate-limited APIs that return 429 (Too Many Requests) responses

<Warning>
  Don't use retry for operations that are not idempotent (operations that can be safely repeated without side effects).
</Warning>

## Installation

```bash theme={null}
dotnet add package Polly.Core
```

## Usage

### Basic Retry

```csharp theme={null}
// Retry using default options (3 retries with 2 second delay)
var pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions())
    .Build();

await pipeline.ExecuteAsync(async token => 
{
    // Your operation that may fail
    await CallExternalServiceAsync(token);
}, cancellationToken);
```

### Instant Retries

```csharp theme={null}
var options = new RetryStrategyOptions
{
    Delay = TimeSpan.Zero
};
```

### Advanced Configuration

```csharp theme={null}
var options = new RetryStrategyOptions
{
    ShouldHandle = new PredicateBuilder()
        .Handle<HttpRequestException>(),
    BackoffType = DelayBackoffType.Exponential,
    UseJitter = true,  // Adds randomness to avoid thundering herd
    MaxRetryAttempts = 4,
    Delay = TimeSpan.FromSeconds(3),
};
```

### Custom Delay Generator

```csharp theme={null}
var options = new RetryStrategyOptions
{
    MaxRetryAttempts = 2,
    DelayGenerator = static args =>
    {
        var delay = args.AttemptNumber switch
        {
            0 => TimeSpan.Zero,
            1 => TimeSpan.FromSeconds(1),
            _ => TimeSpan.FromSeconds(5)
        };
        
        return new ValueTask<TimeSpan?>(delay);
    }
};
```

### Handling HTTP Responses

```csharp theme={null}
var options = new RetryStrategyOptions<HttpResponseMessage>
{
    ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
        .Handle<HttpRequestException>()
        .HandleResult(r => r.StatusCode == HttpStatusCode.TooManyRequests),
    DelayGenerator = static args =>
    {
        // Extract retry-after header from response
        if (args.Outcome.Result is HttpResponseMessage response &&
            response.Headers.RetryAfter?.Delta is TimeSpan delay)
        {
            return new ValueTask<TimeSpan?>(delay);
        }
        
        return new ValueTask<TimeSpan?>((TimeSpan?)null);
    }
};
```

### Retry Events

```csharp theme={null}
var options = new RetryStrategyOptions
{
    MaxRetryAttempts = 2,
    OnRetry = static args =>
    {
        Console.WriteLine($"Retry attempt {args.AttemptNumber}");
        // Log the error, send metrics, etc.
        return default;
    }
};
```

## Configuration Options

<ParamField path="ShouldHandle" type="Predicate" default="Any exceptions except OperationCanceledException">
  Defines which results and/or exceptions should trigger a retry.
</ParamField>

<ParamField path="MaxRetryAttempts" type="int" default="3">
  The maximum number of retry attempts to use, in addition to the original call.
</ParamField>

<ParamField path="BackoffType" type="DelayBackoffType" default="Constant">
  The back-off algorithm type:

  * `Constant`: Same delay between retries
  * `Linear`: Delay increases linearly
  * `Exponential`: Delay increases exponentially
</ParamField>

<ParamField path="Delay" type="TimeSpan" default="2 seconds">
  The base delay between retry attempts. The actual delay depends on the `BackoffType`.
</ParamField>

<ParamField path="MaxDelay" type="TimeSpan?" default="null">
  If provided, caps the calculated retry delay to this value.
</ParamField>

<ParamField path="UseJitter" type="bool" default="false">
  If `true`, adds randomness to retry delays:

  * For `Constant` and `Linear`: adds ±25% random variation
  * For `Exponential`: uses Decorrelated Jitter Backoff V2 algorithm
</ParamField>

<ParamField path="DelayGenerator" type="Func<DelayGeneratorArguments, ValueTask<TimeSpan?>>" default="null">
  Dynamically calculates the retry delay. If this returns `null`, the strategy uses the calculated delay from other properties.
</ParamField>

<ParamField path="OnRetry" type="Func<OnRetryArguments, ValueTask>" default="null">
  Invoked before the strategy delays the next attempt. Useful for logging and metrics.
</ParamField>

## Backoff Types Explained

### Constant Backoff

Same delay between all retry attempts.

```csharp theme={null}
// Settings: Delay = 1 second
// Result: [1000ms, 1000ms, 1000ms, 1000ms, 1000ms]
```

### Linear Backoff

Delay increases by the base delay amount with each attempt.

```csharp theme={null}
// Settings: Delay = 1 second
// Result: [1000ms, 2000ms, 3000ms, 4000ms, 5000ms]
```

### Exponential Backoff

Delay doubles with each attempt.

```csharp theme={null}
// Settings: Delay = 1 second
// Result: [1000ms, 2000ms, 4000ms, 8000ms, 16000ms]
```

<Tip>
  Use exponential backoff with jitter for best results when dealing with external services. This prevents the "thundering herd" problem where many clients retry simultaneously.
</Tip>

## Best Practices

<AccordionGroup>
  <Accordion title="Use appropriate retry counts">
    Don't retry too many times. 3-5 retries is usually sufficient for most scenarios. More retries increase latency and may overwhelm failing systems.
  </Accordion>

  <Accordion title="Enable jitter for external services">
    Always use `UseJitter = true` when calling external services to prevent synchronized retry storms from multiple clients.
  </Accordion>

  <Accordion title="Set maximum delays">
    Use `MaxDelay` to prevent exponential backoff from creating excessive wait times on later retry attempts.
  </Accordion>

  <Accordion title="Only retry transient failures">
    Configure `ShouldHandle` to only retry temporary failures (network issues, timeouts) and not permanent failures (400 Bad Request, 401 Unauthorized).
  </Accordion>

  <Accordion title="Combine with other strategies">
    Combine retry with circuit breaker to prevent retrying when a system is known to be down. Add timeout to prevent individual retry attempts from taking too long.
  </Accordion>
</AccordionGroup>

## Common Patterns

### Quick Retries Then Slow Retries

```csharp theme={null}
var quickRetries = new RetryStrategyOptions
{
    MaxRetryAttempts = 3,
    Delay = TimeSpan.FromMilliseconds(100),
    BackoffType = DelayBackoffType.Exponential,
    UseJitter = true
};

var slowRetries = new RetryStrategyOptions
{
    MaxRetryAttempts = 2,
    Delay = TimeSpan.FromSeconds(5),
    BackoffType = DelayBackoffType.Constant
};

var pipeline = new ResiliencePipelineBuilder()
    .AddRetry(slowRetries)   // Outer layer
    .AddRetry(quickRetries)  // Inner layer
    .Build();
```

### Retry with Maximum Duration

```csharp theme={null}
var options = new RetryStrategyOptions
{
    MaxRetryAttempts = int.MaxValue,
    Delay = TimeSpan.FromSeconds(2),
    MaxDelay = TimeSpan.FromMinutes(1)
};

var timeout = new ResiliencePipelineBuilder()
    .AddRetry(options)
    .AddTimeout(TimeSpan.FromMinutes(5)) // Overall timeout
    .Build();
```

## Examples

### HTTP Client with Retry

```csharp theme={null}
var httpClient = new HttpClient();

var pipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRetry(new RetryStrategyOptions<HttpResponseMessage>
    {
        ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
            .Handle<HttpRequestException>()
            .HandleResult(r => !r.IsSuccessStatusCode && r.StatusCode != HttpStatusCode.BadRequest),
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(1),
        BackoffType = DelayBackoffType.Exponential,
        UseJitter = true
    })
    .Build();

var response = await pipeline.ExecuteAsync(async ct =>
{
    return await httpClient.GetAsync("https://api.example.com/data", ct);
}, cancellationToken);
```

### Database Connection with Retry

```csharp theme={null}
var pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions
    {
        ShouldHandle = new PredicateBuilder()
            .Handle<SqlException>(ex => ex.IsTransient)
            .Handle<TimeoutException>(),
        MaxRetryAttempts = 5,
        Delay = TimeSpan.FromSeconds(2),
        BackoffType = DelayBackoffType.Exponential,
        OnRetry = args =>
        {
            Console.WriteLine($"Database connection failed. Retry {args.AttemptNumber}");
            return default;
        }
    })
    .Build();

await pipeline.ExecuteAsync(async ct =>
{
    await using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync(ct);
    // Execute query
}, cancellationToken);
```

<Accordion title="What happens when all retries are exhausted?">
  The retry strategy rethrows the final exception back to the calling code. You should handle this exception appropriately.
</Accordion>

<Accordion title="Can I retry indefinitely?">
  Yes, set `MaxRetryAttempts = int.MaxValue`. However, this is generally not recommended. Consider using a circuit breaker or implementing a maximum time limit instead.
</Accordion>

<Accordion title="How do I see retry attempts in logs?">
  Use the `OnRetry` callback to log retry attempts. You can also enable Polly telemetry for comprehensive observability.
</Accordion>
