Search code examples
c#pollyretry-logic

Pass custom context into OnRetry


  • I would like to use Polly v8, which introduces new ResiliencePipeline API.
  • I would like to use ResiliencePipeline.ExecuteAsync() to handle the execution of provided asynchronous action using the Retry strategy.
  • I would need to have custom context available in OnRetry callback, which is defined upon ResiliencePipeline building phase (via ResiliencePipelineBuilder).
  • It looks like there is only an OnRetryArguments available in the OnRetry callback.
  • If I haven't missed anything, only Context property (of ResilienceContext type) could be used for custom data passing, namely the Properties (of ResilienceProperties type).

I haven't found any way, how to "configure" the particular ResilienceContext instance used in given ExecuteAsync call, other than creating it by myself and passing it directly to the appropriate overload. This seems a bit complicated, as it needs to take instance from a pool and then return it back after the execution is finished (looks like ResilienceContextPool.Shared could serve it).

Is this the preferred way, or am I missing something obvious?

Is the ResilienceContextPool.Shared considered to be used for these purposes?

EDIT:

public static class ResiliencePipelineExtensions
{
    public static async ValueTask ExecuteAsync(this ResiliencePipeline resilience, Func<CancellationToken, ValueTask> action, string operation, IDictionary<string, object> context, CancellationToken cancellationToken)
    {
        var resilienceContext = ResilienceContextPool.Shared.Get(operation, cancellationToken);

        foreach (var property in context)
        {
            resilienceContext.Properties.Set(new ResiliencePropertyKey<object>(property.Key), property.Value);
        }

        try
        {
            await resilience.ExecuteAsync(ctx => action(ctx.CancellationToken), resilienceContext);
        }
        finally
        {
            ResilienceContextPool.Shared.Return(resilienceContext);
        }
    }
}

Looks like this works as expected...


Solution

  • At the time of writing this post Polly V8 is not yet finished. Currently the V8 beta 2 is available which means it is feature ready and the API considered (mostly) stable.

    That means there is still a chance to have an impact on how the V8 API should look like. There have already been a couple or public API review sessions and lots of suggestion have been accepted and incorporated into the code base.

    So, if you find the current API is hard to use then please feel free to file an issue on Polly's github.

    For example the current implementation of ResilienceProperties does not allow "bulk load" of key value pairs. But under the hood it is using a Dictionary<string, object?> collection. It might be a good feedback to extend this class with "bulk load" capability.


    is it intended to be so "complicated" to use brand new API?

    V8 is way different than what we had in the previous years. It is a complete rewrite and a couple of new design principles have been embraced.

    Async from ground-up

    Like for instance the API is from ground-up async. That means all user-defined delegates should be async as well. In previous pre-release versions you had to write some boiler-plate code to use the API in synchronous fashion

    ShouldRetry = outcome => new ValueTask<bool>(outcome.Exception is TimeoutRejectedException),
    

    but based on the feedbacks some helper classes have been introduced as well as a couple of implicit conversion operators

    ShouldRetry = new PredicateBuilder().Handle<TimeoutRejectedException>()
    

    Expose similar API

    Also V8 tries to follow couple of .NET conventions that have been battle hardened in the previous years. Like the ResilienceContextPool's API has a huge resemblance of ArrayPool (with some exceptions, like .Shared.Get needs to be used instead of .Shared.Rent).

    Reuse existing components

    Another example is that .NET 7 has introduced RateLimiter and rather than having two separate implementations Polly V8 uses that component. Of course some wrappers had to be introduced to nicely integrate with other strategies and with the pipeline builder.

    new ResiliencePipelineBuilder()
        .AddRateLimiter(new RateLimiterStrategyOptions());
    
    new ResiliencePipelineBuilder()
        .AddConcurrencyLimiter(100, 50);
    

    Minimize input parameters

    Yet another design principle was to minimize the number of input parameters. In case of V7 there were several overloads for onRetry for example. Each overload provided access to different parameters and it was quite hard sometimes to find the appropriate overload.

    In case of V8 you have a single XYZOptions parameter for the builder methods and XYZArguments for user-defined delegates.


    The above list is not exhaustive but I hope this little background information provided you a bit of context why does the V8 API look as it is.