question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Polly v8 API feedback

See original GitHub issue

I’ve spent a few hours today upgrading an internal application which uses Polly from the v7 policies to v8 resilience strategies and encountered a number of rough edges and/or difficulties. This issue catalogs those at a high-level for discussion as to whether or not these are issues or missing functionality that we would wish to fix/add as part of the v8 release. I can add more concrete details/repro if needed for the specific items.

  1. ResilienceProperties has a dictionary indexer for reading and writing properties, but it is an explicit interface definition so requires casting to a dictionary to use. It would be much more convenient to make it implicit so you can do things like arguments.Properties["foo"].
  2. It isn’t possible to share a CircuitBreakerManualControl across multiple strategies - we have a use case where we manually isolate circuit breakers and it isn’t possible to have a single instance available to any circuit breaker strategy to observe (thing similarly to sharing a CancellationToken on multiple operations). I’ve tried pooling them and iterating through the set when I want to open/close them, but this doesn’t seem to work with what I’ve tried so far.
  3. In the above case, if I try and manually isolate the manual control but there doesn’t happen to have been a strategy created that uses the builder yet, it throws an exception saying it hasn’t been initialized.
  4. Creating a strategy of policies composed of other potentially existing policies seems to be a bit cumbersome. A rough example of what I’m trying to do is below, and needing to create a new builder to get a policy (because I can’t make them directly because of internal constructors) is a bit weird.
// This is just illustrative, it doesn't compile as-is

private readonly ConcurrentDictionary<string, ResilienceStrategy> _registry = new();
private readonly ConcurrentDictionary<string, CircuitBreakerManualControl> _isolators = new();

public ResilienceStrategy GetStrategy(string token, ApiEndpointOption endpoint, string resource)
{
    var builder = new ResilienceStrategyBuilder();

    ApplyRetries(builder, endpoint);
    ApplyCircuitBreaker(builder, endpoint, resource);
    ApplyTimeout(builder, endpoint);

    return builder.Build();
}

private void ApplyRetries(ResilienceStrategyBuilder builder, ApiEndpointOption endpoint)
{
    if (endpoint.Retries > 0)
    {
        var strategy = _registry.GetOrAdd($"{endpoint.Name}-Retry", (key) => new ResilienceStrategyBuilder().AddRetry(new()
        {
            BackoffType = RetryBackoffType.ExponentialWithJitter,
            BaseDelay = endpoint.RetryDelaySeed,
            OnRetry = OnRetry,
            RetryCount = endpoint.Retries,
            ShouldHandle = new PredicateBuilder().Handle<ApiException>(CanRetry).Handle<TaskCanceledException>(),
            StrategyName = key,
        }).Build());

        builder.AddStrategy(strategy);
    }
}

private void ApplyCircuitBreaker(
    ResilienceStrategyBuilder builder,
    ApiEndpointOption endpoint,
    string resource)
{
    var manualControl = _isolators.GetOrAdd(endpoint.Name, (_) => new());

    var strategy = _registry.GetOrAdd($"{endpoint.Name}-{resource}-CircuitBreaker", (key) => new ResilienceStrategyBuilder().AddAdvancedCircuitBreaker(new()
    {
        BreakDuration = endpoint.FailureBreakDuration,
        FailureThreshold = endpoint.FailureThreshold,
        MinimumThroughput = endpoint.FailureMinimumThroughput,
        SamplingDuration = endpoint.FailureSamplingDuration,
        OnOpened = OnCircuitOpened,
        OnClosed = OnCircuitClosed,
        StrategyName = key,
        ManualControl = manualControl,
        ShouldHandle = new PredicateBuilder()
            .Handle<ApiException>(CanCircuitBreak)
            .HandleHttpRequestFault()
            .Handle<OperationCanceledException>()
            .Handle<TimeoutRejectedException>(),
    }).Build());

    if (endpoint.Isolate)
    {
        // TODO sync over async
        manualControl.IsolateAsync().GetAwaiter().GetResult();
    }

    builder.AddStrategy(strategy);
}

private void ApplyTimeout(ResilienceStrategyBuilder builder, ApiEndpointOption endpoint)
{
    var strategy = _registry.GetOrAdd($"{endpoint.Name}-Timeout", (key) => new ResilienceStrategyBuilder().AddTimeout(new TimeoutStrategyOptions()
    {
        OnTimeout = OnTimeout,
        StrategyName = key,
        Timeout = endpoint.Timeout.Add(TimeSpan.FromSeconds(1)),
    }).Build());

    builder.AddStrategy(strategy);
}

It might be that in some cases I’m just using the new API wrong, rather than their being issues. There might also be cases where I need to re-approach how Polly is used in a more fundamental way - for example Policies are currently created on-demand, which makes it non-trivial to put them in DI. Similarly, the pay configuration changes are handled to recreate the cached policies in a policy registry would need to be reworked to use the new reloading mechanism if using the DI integration as well.

I’ll do some more work on this tomorrow application with v8 and try and get the application’s tests passing again - at the moment I’ve got issues with circuit breakers as well as retries (not shown above) not working properly.

/cc @martintmk

Issue Analytics

  • State:closed
  • Created 3 months ago
  • Comments:23 (23 by maintainers)

github_iconTop GitHub Comments

1reaction
martincostellocommented, Aug 9, 2023

I think we’ve tackled everything I raised here as of alpha.8.

1reaction
martintmkcommented, Jul 10, 2023

When I do manually isolate a circuit ahead of time (because config says to) I need to do this call because configuring the service collection isn’t async: manualControl.IsolateAsync().GetAwaiter().GetResult(). This made me wonder if we could simplify this scenario by adding a constructor that allows you to set whether it’s open when you create it? Then I could just do something like var manualControl = new CircuitBreakerManualControl(open: true) instead which is much neater.

Can you add a comment to the API review? We can discuss this addition there.

Read more comments on GitHub >

github_iconTop Results From Across the Web

We want your feedback! Introducing Polly v8
We have issued a new pull request (Polly V8: Public API Review #1233) to introduce the v8 API to the public. We would...
Read more >
Joel Hulen - We want your feedback! Introducing Polly v8
Polly v8 update! As a follow-up to my post back in March, we are proud to announce Polly v8 for public API review....
Read more >
Polly
After completing review of the API to address unresolved issues, we will move on to a Beta release. The v8 docs are not...
Read more >
SynthesizeSpeech - Amazon Polly
Specifies the engine ( standard or neural ) for Amazon Polly to use when processing input text for speech synthesis. For information on...
Read more >
Introducing Workflows + REST API to automatically collect ...
Polly workflows enable users to collect mass feedback on mission-critical processes and tasks — they're meant to be user-friendly and simple to set...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found