Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to handle different exceptions differently with the same policy? #104

Closed
BertLamb opened this issue Apr 27, 2016 · 11 comments
Labels

Comments

@BertLamb
Copy link

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?

@reisenberger
Copy link
Member

reisenberger commented Apr 28, 2016

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!

@reisenberger
Copy link
Member

Hi @BertLamb Did this solve your problem? Can we close the issue? Or: Would you like any further assistance?
Thanks

@BertLamb
Copy link
Author

BertLamb commented May 9, 2016

Thanks! Yes and no, what if I wanted to have a CircuitBreaker for IOExceptions but just a Retry on CommandExceptions?

@reisenberger
Copy link
Member

reisenberger commented May 9, 2016

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);

@BertLamb
Copy link
Author

BertLamb commented May 9, 2016

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!

@johnknoop
Copy link

@reisenberger Any progress on this? Such a pipeline functionality would be sweet.

@reisenberger
Copy link
Member

@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.)

@johnknoop
Copy link

@reisenberger Oh, great! I'll have a look at that. Thanks!

@StefDotmailer
Copy link

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?

@reisenberger
Copy link
Member

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.

@StefDotmailer
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants