Reputation: 1009
I am trying to create a combined resilience strategy for my Rest clients and for this I have written the following :
private static Policy circuitBreakerPolicy = Policy
.Handle<TimeoutException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
And I am getting the error
Cannot implicitly convert type
Polly.CircuitBreaker.AsyncCircuitBreaker
toPolly.Policy
What am I missing ? I have checked a number of references online but I cannot come across a concise enough explanation of what causes this issue.
Usage :
Eventually I want to combine the CircuitBreaker policy above with a WaitandRetry
policy but I cannot figure out how to extract the combined policy :
(This is working)
public static IAsyncPolicy<HttpResponseMessage> CreateResiliencePolicy()
{
var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(180));
var waitAndRetryPolicy = Polly.Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)),
(result, timeSpan, context) =>
{
});
return timeoutPolicy.WrapAsync(waitAndRetryPolicy);
}
This is what I want (Not working) :
public static IAsyncPolicy<HttpResponseMessage> CreateResiliencePolicy()
{
var circuitBreakerPolicy = Policy
.Handle<TimeoutException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
var waitAndRetryPolicy = Polly.Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)),
(result, timeSpan, context) =>
{
});
return circuitBreakerPolicy.WrapAsync(waitAndRetryPolicy);
//I want to do this instead
//public static Policy resilientAsyncStrategy =circuitBreakerPolicy.WrapAsync(waitAndRetryPolicy);
//Then return resilientAsyncStrategy
//return resilientAsyncStrategy;
}
And then use the returned policy instance as :
public async Task<IEnumerable<PaymentDetailDto>> GetAsync()
{
var items = await resilientAsyncStrategy.ExecuteAsync(async () => await client.GetAsync());
return items;
}
Upvotes: 4
Views: 1886
Reputation: 22829
Polly defines the following four abstract Policy types:
Method | Function | |
---|---|---|
Sync | Policy |
Policy<TResult> |
Async | AsyncPolicy |
AsyncPolicy<TResult> |
So, in your case the circuitBreakerPolicy
should be defined like this:
private static AsyncPolicy circuitBreakerPolicy = Policy
.Handle<TimeoutException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
Whenever you want to combine / chain two (or more) policies then you should consider to use PolicyWrap
(reference). Please bear in mind that policy chain works in an escalation way which means if the inner policy can't handle the problem then it will propagate that to the next outer policy.
Please also bear in mind that policies should be compatible with each other. So, if one of them is async then other one should be as well. If the inner returns something then the outer should do the same.
So, your CreateResiliencePolicy
could look like this:
public static AsyncPolicy<HttpResponseMessage> CreateResilienceStrategy()
{
var circuitBreakerPolicy = Policy<HttpResponseMessage>
.Handle<TimeoutException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
var waitAndRetryPolicy = Polly.Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)),
(result, timeSpan, context) =>
{
});
return Policy.WrapAsync(circuitBreakerPolicy, waitAndRetryPolicy);
}
Please also bear in mind that ordering matters:
Policy.WrapAsync(circuitBreakerPolicy, waitAndRetryPolicy)
you have an inner Retry and an outer CBPolicy.WrapAsync(waitAndRetryPolicy, circuitBreakerPolicy)
you have an inner CB and an outer RetryThe following two lines are equivalent:
circuitBreakerPolicy.WrapAsync(waitAndRetryPolicy);
Policy.WrapAsync(circuitBreakerPolicy, waitAndRetryPolicy)
If you want to change the ordering of the policies then your strategy will work in a different way. If you want to use the CB as inner policy and the retry as outer then you should amend the waitAndRetryPolicy
to handle BrokenCircuitException
as well.
Couple of months ago I have put together a sample application which demonstrates how can you incrementally design your resilience strategy.
UPDATE: Add sample code
I've used the following console app to test your retry policy:
private static HttpClient client = new HttpClient();
public static async Task Main(string[] args)
{
var strategy = CreateResilienceStrategy();
await strategy.ExecuteAsync(async (ct) =>
await client.GetAsync("https://httpstat.us/500", ct)
, CancellationToken.None);
Console.WriteLine("Finished");
}
public static AsyncPolicy<HttpResponseMessage> CreateResilienceStrategy()
{
var circuitBreakerPolicy = Policy<HttpResponseMessage>
.Handle<TimeoutException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2),
onBreak: (_, __) => Console.WriteLine("Break"),
onReset: () => Console.WriteLine("Reset"),
onHalfOpen: () => Console.WriteLine("HalfOpen"));
var waitAndRetryPolicy = Polly.Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)),
onRetryAsync: (_, ts, ___) =>
{
Console.WriteLine($"Retry, penalty: {ts.Seconds} secs");
return Task.CompletedTask;
});
return Policy.WrapAsync(circuitBreakerPolicy, waitAndRetryPolicy);
}
The output:
Retry, penalty: 3 secs
Retry, penalty: 9 secs
Retry, penalty: 27 secs
Finished
As you can see,
strategy.ExecuteAsync
returned a response where the status code is 500Because the CB policy was setup to trigger for TimeoutException
that's why I set the client.Timeout
to 1 millisecond in the Main
. With that the application crashes with TaskCanceledException
.
It does that because HttpClient throws TaskCanceledException
in case of Timeout instead of TimeoutException
. If you would use polly's Timeout policy then you would receive TimeoutRejectedException
.
So, here is my amended to code to be able to test CB
private static HttpClient client = new HttpClient();
public static async Task Main(string[] args)
{
client.Timeout = TimeSpan.FromMilliseconds(1);
var strategy = CreateResilienceStrategy();
try
{
await strategy.ExecuteAsync(async (ct) =>
await client.GetAsync("https://httpstat.us/500", ct)
, CancellationToken.None);
Console.WriteLine("Finished");
}
catch (Exception ex)
{
Console.WriteLine("Failed with " + ex.GetType().Name);
}
}
public static AsyncPolicy<HttpResponseMessage> CreateResilienceStrategy()
{
var circuitBreakerPolicy = Policy<HttpResponseMessage>
.Handle<OperationCanceledException>()
.Or<TimeoutRejectedException>()
.CircuitBreakerAsync(1, TimeSpan.FromSeconds(2),
onBreak: (_, __) => Console.WriteLine("Break"),
onReset: () => Console.WriteLine("Reset"),
onHalfOpen: () => Console.WriteLine("HalfOpen"));
var waitAndRetryPolicy = Polly.Policy
.Handle<HttpRequestException>()
.Or<OperationCanceledException>()
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)),
onRetryAsync: (_, ts, ___) =>
{
Console.WriteLine($"Retry, penalty: {ts.Seconds} secs");
return Task.CompletedTask;
});
return Policy.WrapAsync(waitAndRetryPolicy, circuitBreakerPolicy);
}
Modifications:
OperationCanceledException
conditions for both policiesThe output
Break
Retry, penalty: 3 secs
HalfOpen
Break
Retry, penalty: 9 secs
HalfOpen
Break
Retry, penalty: 27 secs
HalfOpen
Break
Failed with OperationCanceledException
Upvotes: 3