I just started digging into Polly and I really like it. One thing I can't quite seem to figure out how to do is to have a Policy that reacts differently to different exception types.
I'd like to do something like this:
// don't need to reconnect on a CommandException
myPolicy = Policy
.Handle<CommandException>()
.Retry(10);
// but need to reconnect before a retry when an IOException occurs
myPolicy.Handle<IOException>()
.Retry(10, (exception, retryCount, context) =>
{
Reconnect();
});
myPolicy.Execute(Command);
Is this possible?
Hi @BertLamb
Using .Or<TException> lets you handle more than one type of exception in the same policy. And, the exception just thrown is passed the to onRetry delegate before the next try commences, so you can vary onRetry actions depending on the exception causing the retry. So:
// don't need to reconnect on a CommandException
// but need to reconnect before a retry when an IOException occurs
myPolicy = Policy
.Handle<CommandException>()
.Or<IOException>()
.Retry(10, (exception, retryCount, context) =>
{
if (exception is IOException) { Reconnect(); }
});
myPolicy.Execute(Command);
Does this cover it? Let us know if you have any other questions!
Hi @BertLamb Did this solve your problem? Can we close the issue? Or: Would you like any further assistance?
Thanks
Thanks! Yes and no, what if I wanted to have a CircuitBreaker for IOExceptions but just a Retry on CommandExceptions?
Hi @BertLamb . To do that with Polly, you can define separate policies and nest them, as described in the wiki here or as shown below:
var retryPolicy = Policy
.Handle<CommandException>()
.Retry(10, (exception, retryCount, context) => Reconnect());
var circuitBreaker = Policy
.Handle<IOException>
.CircuitBreaker(2, TimeSpan.FromMinutes(1));
retryPolicy.Execute(() => circuitBreaker.Execute(command));
There isn't currently a way to define a Policy that handles a variety of different exceptions in a variety of different ways, _all in one single fluent statement_. However, the Polly Roadmap envisages the Polly Pipeline, which would allow any number of functionally-composed policies to be reduced to one Policy, thus:
var combinedPolicy = Policy.Pipeline(retryPolicy, circuitBreaker);
combinedPolicy.Execute(command);
or (an alternative syntax under consideration):
var combinedPolicy = retryPolicy.Wrap(circuitBreaker);
combinedPolicy.Execute(command);
I guess once the functionality for collapsing functionally-composed (wrapped) policies into one (as in the Polly Pipeline) was in place, it might be possible to create an on-going fluent syntax as follows - is this the kind of thing you had in mind?
var myPolicy = Policy
.Handle<CommandException>()
.Retry(10, (exception, retryCount, context) => Reconnect());
.Handle<IOException>
.CircuitBreaker(2, TimeSpan.FromMinutes(1));
myPolicy.Execute(command);
Ah, nice, I like that Policy.Pipeline concept. Seems a bit clearer (to me) than the fluent style I was originally thinking for chaining policies and that you captured at the end there. Thanks for your time and help!
@reisenberger Any progress on this? Such a pipeline functionality would be sweet.
@johnknoop Yes, this was delivered at Polly v5.0.0 and its eventual name was PolicyWrap. (We moved away from the Pipeline name as that suggested a one-way flow, but as you'll see from the diags in the PolicyWrap wiki, the execution flow through the PolicyWrap is very much two-way.)
@reisenberger Oh, great! I'll have a look at that. Thanks!
Hi, is this solved by PolicyWrap?
I would like to get the same behaviour as:
var retryBackOffOrNormal =
Policy.HandleResult<string>(job => job == "error")
.OrResult(job => job.StartsWith("error"))
.WaitAndRetry(2,
(attempt, ctx, x) =>
{
if (ctx.Result == "error")
{
Console.WriteLine("backoff retry");
return TimeSpan.FromSeconds(Math.Pow(2, attempt)) +
TimeSpan.FromMilliseconds(jitterer.Next(0, 100));
}
Console.WriteLine("regular retry");
return TimeSpan.FromSeconds(2) +
TimeSpan.FromMilliseconds(jitterer.Next(0, 100));
});
so if the error is exactly "error", it will do exponential backoff; if the error is "error, something unexpected happened" it will do a regular retry.
if I try and use Wrap, I get 9 retries with a combination of both the wait strategies:
var retryBackOff =
Policy.HandleResult<string>(job => job == "error")
.WaitAndRetry(3,
attempt =>
{
Console.WriteLine("backoff retry");
return TimeSpan.FromSeconds(Math.Pow(2, attempt)) +
TimeSpan.FromMilliseconds(jitterer.Next(0, 100));
});
var retryAllErrors =
Policy.HandleResult<string>(job => job.StartsWith("error"))
.WaitAndRetry(3,
attempt =>
{
Console.WriteLine("regular retry");
return TimeSpan.FromSeconds(2) +
TimeSpan.FromMilliseconds(jitterer.Next(0, 100));
});
var i = 0;
retryBackOffRateLimit.Wrap(retryAllErrors)
.Execute(() =>
{
Console.WriteLine($"Attempt {++i}");
return "error";
});
am I not using it right or Wrap is not suitable for this scenario?
Hi @StefDotmailer . This:
if the error is exactly "error", it will do exponential backoff; if the error is "error, something unexpected happened" it will do a regular retry
suggests the intention is two mutually exclusive cases. To get that effect, define the policy predicates to be mutually exclusive. Then, only one or the other policy (not both) will handle any return result:
var retryBackOff = Policy.HandleResult<string>(job => job == "error") /* etc */
var retryAllErrorsExceptQuoteErrorQuote = Policy.HandleResult<string>(job => job.StartsWith("error") && job != "error") /* etc */
To explain why your posted code generated 9 retries: both the predicates job => job.StartsWith("error") and job => job == "error" match "error". So both policies (correctly) handled the error. PolicyWrap does not apply mutual-exclusivity; PolicyWrap acts in a nested fashion by functional composition, so Execute places calls _through_ the outer policy, _through_ the next inner policy ... until eventually the next-inner thing to execute is your delegate. Since both policies handled the execution result, you were (correctly) getting 3 x 3 = 9 retries.
Hi @reisenberger, thank you for the explanation.
would be nice if there was also a pipeline style handling, I may have a look at implementing it and send a PR at some point in the future if it is not planned already.
Most helpful comment
Hi @BertLamb . To do that with Polly, you can define separate policies and nest them, as described in the wiki here or as shown below:
There isn't currently a way to define a Policy that handles a variety of different exceptions in a variety of different ways, _all in one single fluent statement_. However, the Polly Roadmap envisages the Polly Pipeline, which would allow any number of functionally-composed policies to be reduced to one Policy, thus:
or (an alternative syntax under consideration):
I guess once the functionality for collapsing functionally-composed (wrapped) policies into one (as in the Polly Pipeline) was in place, it might be possible to create an on-going fluent syntax as follows - is this the kind of thing you had in mind?