Polly v8 API feedback
See original GitHub issueI’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.
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 likearguments.Properties["foo"]
.- 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 aCancellationToken
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. - 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.
- 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:
- Created 3 months ago
- Comments:23 (23 by maintainers)
Top GitHub Comments
I think we’ve tackled everything I raised here as of alpha.8.
Can you add a comment to the API review? We can discuss this addition there.