Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Retry Policy Not Triggering in PollyResiliencePipelineProvider #2240

Open
aftabkajal opened this issue Dec 14, 2024 · 0 comments
Open

Comments

@aftabkajal
Copy link

aftabkajal commented Dec 14, 2024

I've implemented a custom retry mechanism using Polly in the PollyResiliencePipelineProvider. Despite configuring the ShouldHandle property and providing retry strategies, retries are not triggered for retryable exceptions or HTTP status codes.

  1. Configure Ocelot with a downstream service returning 503 status.
  2. Use the provided custom PollyResiliencePipelineProvider.
  3. Observe that the retry policy does not trigger as expected.

Code Snippet:
`
public class PollyResiliencePipelineProvider : PollyQoSResiliencePipelineProvider
{
private readonly OcelotGlobalConfiguration _options;
private readonly IOcelotLogger _logger;
public PollyResiliencePipelineProvider(
IOptions options,
IOcelotLoggerFactory loggerFactory,
ResiliencePipelineRegistry registry)
: base(loggerFactory, registry)
{
_options = options.Value;
_logger = loggerFactory.CreateLogger();
}

protected override ResiliencePipelineBuilder<HttpResponseMessage> ConfigureCircuitBreaker(ResiliencePipelineBuilder<HttpResponseMessage> builder, DownstreamRoute route)
{
    ConfigureRetry(builder, route); // Add Retry strategy first

    return base.ConfigureCircuitBreaker(builder, route);
}

protected ResiliencePipelineBuilder<HttpResponseMessage> ConfigureRetry(ResiliencePipelineBuilder<HttpResponseMessage> builder, DownstreamRoute route)
{
    var isQosOptionsExist = route.MetadataOptions.Metadata.TryGetValue(nameof(FileQoSOptions), out var retryOptions);

    if (!isQosOptionsExist || string.IsNullOrWhiteSpace(retryOptions))
    {
        _logger.LogWarning($"QoSOptions not found or empty for route: {GetRouteName(route)}.");
        return builder;
    }

    var retryMetadata = JsonSerializer.Deserialize<OcelotQoSOptions>(retryOptions);

    if (retryMetadata == null)
    {
        _logger.LogWarning($"Failed to deserialize QoSOptions for route: {GetRouteName(route)}.");
        return builder;
    }

    // Add Retry strategy only if the retry count is defined in QoSOptions and greater than 0
    if (retryMetadata.RetryCount <= 0)
    {
        _logger.LogWarning($"RetryCount {retryMetadata.RetryCount} for route: {GetRouteName(route)}.");
        return builder;
    }

    _logger.LogInformation($"Retry options for route: {GetRouteName(route)}: RetryCount={retryMetadata.RetryCount}, RetryDelay={retryMetadata.RetryDelay}");

    var info = $"Retry for the route: {GetRouteName(route)}: ";
    var strategyOptions = new RetryStrategyOptions<HttpResponseMessage>
    {
        MaxRetryAttempts = retryMetadata.RetryCount,
        Delay = TimeSpan.FromMilliseconds(retryMetadata.RetryDelay),
        ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
                     .Handle<Exception>(ex =>
                     {
                         var shouldRetry = RetryableExceptions.Contains(ex.GetType());

                         if (shouldRetry)
                         {
                             _logger.LogDebug($"Retry triggered by exception: {ex.GetType().Name}");
                         }

                         return shouldRetry;
                     })
                    .HandleResult(response =>
                    {
                        var shouldRetry = ServerErrorCodes.Contains(response.StatusCode);

                        if (shouldRetry)
                        {
                            _logger.LogDebug($"Retry triggered by HTTP status code: {response.StatusCode}");
                        }

                        return shouldRetry;
                    }),
        BackoffType = DelayBackoffType.Constant,
        OnRetry = args =>
        {
            _logger.LogWarning(info +
                $"Retry attempt {args.AttemptNumber} after {args.RetryDelay.TotalMilliseconds} ms due to {args.Outcome.Exception?.Message ?? args.Outcome.Result?.StatusCode.ToString()}");
            return ValueTask.CompletedTask;
        },
    };

    return builder.AddRetry(strategyOptions);
}

private static readonly ImmutableArray<Type> RetryableExceptions =
     [
        typeof(SocketException),
        typeof(HttpRequestException),
        typeof(TimeoutRejectedException),
        typeof(BrokenCircuitException),
        typeof(RateLimitRejectedException),
        typeof(TimeoutException),
     ];

}

`

.AddPolly<PollyResiliencePipelineProvider>();

Log:
image

Specifications

  • Version: 23.4.2
  • .NET version: 8.0
  • Hosting: Docker and Local

Expected Behavior:
The retry logic should trigger on HTTP 503 status codes or HttpRequestException.

Actual Behavior:
The retry mechanism is not invoked, and no retry attempts are logged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant