8

Is there a way in Polly to retry all exceptions apart from those which are specified.. for example:

var p = Policy
    .Handle<HttpListenerException>(e => !(e.NativeErrorCode == 1))
    .Or<Exception>()
    .RetryAsync();

Here i have picked a slightly contrived situation where i would want to NOT retry when the NativeErrorCode == 1?

I initially hoped that this will retry if any value other than 1, and any other Exception dealt with by the .Or<Exception>() ..

What is actually happening, is that the .Or<Exception> will also catch the NativeErrorCode == 1, even though it was excluded from above? I think..

One option i considered, but not tested... (no error checking ;p)

var p = Policy
    .Handle<Exception>(e => SomethingMoreComplex(e) == true)
    .RetryAsync();

private bool SomethingMoreComplex(Exception e)
{
    if (e is HttpListenerException t)
    {
        if (t.NativeErrorCode == 1) return false;
    }

    return true;
}

is that threadsafe? :|

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
m1nkeh
  • 1,337
  • 23
  • 45
  • 3
    There's lots of ways to write this. I think the cleanest way is `.Handle(e => e.NativeErrorCode != 1).Or(e => !(e is HttpListenerException))` (that is, simply exclude `HttpListenerException` from the most inclusive check). The *shortest* way is probably `.Handle(e => (e as HttpListenerException)?.NativeErrorCode != 1)`, but that's unnecessarily cryptic. – Jeroen Mostert Jun 20 '18 at 13:25
  • ah yes, i like that. My equivalent to e.NativeErrorCode != 1 is in reality a bit complex, but this will probably work fine... i will check how (un)readable it get! :) – m1nkeh Jun 20 '18 at 14:27
  • 1
    +1 to @JeroenMostert 's. Re thread safety: Polly policies themselves are fully thread-safe. And your `SomethingMoreComplex(...)` predicate isn't sharing any state outside the scope that could make it non-thread-safe. So, LGTM. – mountain traveller Jun 20 '18 at 16:48
  • Possible duplicate of [Check string content of response before retrying with Polly](https://stackoverflow.com/questions/50835992/check-string-content-of-response-before-retrying-with-polly) – Michael Freidgeim Sep 13 '19 at 10:00

1 Answers1

4

If you look at the Policy.HandleSyntax.cs file then you can see how the Handle<T> methods have been defined:

public partial class Policy
{
    public static PolicyBuilder Handle<TException>() where TException : Exception
        => new PolicyBuilder(exception => exception is TException ? exception : null);

    public static PolicyBuilder Handle<TException>(Func<TException, bool> exceptionPredicate) where TException : Exception
        => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null);
        
    ...
}

public partial class Policy<TResult>
{
    public static PolicyBuilder<TResult> Handle<TException>() where TException : Exception
        => new PolicyBuilder<TResult>(exception => exception is TException ? exception : null);

    public static PolicyBuilder<TResult> Handle<TException>(Func<TException, bool> exceptionPredicate) where TException : Exception
        => new PolicyBuilder<TResult>(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null);
            
    ...
}
  • All of the methods rely on the PolicyBuilder class, which has an internal ctor.
    • So, we can't use it in our custom code.
  • Also notice that the Policy class does not have a public ctor.
    • So, we can't create a Policy instance, that's why it does not make sense to create extension methods for the Policy class.

Here is one way to overcome on these limitations:

public static class PolicyExt
{
    public static PolicyBuilder HandleExcept<TException>() where TException : Exception
        => Policy.Handle((Exception exception) => exception is TException is false);

    public static PolicyBuilder HandleExcept<TException>(Func<TException, bool> exceptionPredicate) where TException : Exception
        => Policy.Handle((Exception exception) => exception is TException is false || exception is TException texception && !exceptionPredicate(texception));
}

public static class PolicyExt<TResult>
{
    public static PolicyBuilder<TResult> Handle<TException>() where TException : Exception
        => Policy<TResult>.Handle((Exception exception) => exception is TException is false);

    public static PolicyBuilder<TResult> Handle<TException>(Func<TException, bool> exceptionPredicate) where TException : Exception
        => Policy<TResult>.Handle((Exception exception) => exception is TException is false || exception is TException texception && !exceptionPredicate(texception));
}

And finally here is a quick test:

var policy = PolicyExt.HandleExcept<Exception>(ex => ex is NotSupportedException)
    .WaitAndRetry(2, _ => TimeSpan.FromSeconds(2));

//Or just:
//var policy = PolicyExt.HandleExcept<NotSupportedException>()
//    .WaitAndRetry(2, _ => TimeSpan.FromSeconds(2));

policy.Execute(() =>
{
    Console.WriteLine("Have been called");
    throw new NotSupportedException();
});

Output:

Have been called
Unhandled exception. System.NotSupportedException: Specified method is not supported.
  • So, in case NotSupportedException the retry logic is not triggered.
  • But if we execute the policy against the following delegate:
policy.Execute(() =>
{
    Console.WriteLine("Have been called");
    throw new Exception("Custom");
});

Then the output will be the following:

Have been called
Have been called
Have been called
Unhandled exception. System.Exception: Custom

So, the retry is triggered.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75