Polly fallback action did not throw the specified exception. What happened?

1.1k views Asked by At

Given this code that tries to call AuthenticationManager.Authenticate() for 10 times, and then throws a CustomException after 10 failed attempts:

string res = Policy
    .Handle<Exception>() // Handles any exception
    .OrResult<string>(result => string.IsNullOrWhiteSpace(result)) // or if result is null
    .Fallback(() => throw new CustomException("Exception!")) // Not being thrown after 10 unsuccessful attempts
    .Wrap(
        Policy
            .Handle<Exception>()
            .OrResult<string>(result => string.IsNullOrWhiteSpace(result))
            .WaitAndRetry(
                10,
                retryAttempt => TimeSpan.FromSeconds(60),
                onRetry: (response, delay, retryCount, context) => Trace.WriteLine($"[{DateTime.UtcNow}] Authentication failed. Retrying after 60 seconds...(Attempt {retryCount} of 10)")))
    .ExecuteAndCapture(() => AuthenticationManager.Authenticate())
    .Result;

Why is the CustomException not being thrown from the fallback action? What should be the correct way to do it?

2

There are 2 answers

0
terrible-coder On BEST ANSWER

Found that just using WaitAndRetry and just checking for the Outcome worked for me (in case anyone comes across this question):

var policyResult = Policy
    .Handle<Exception>()
    .OrResult<AuthenticationResult>(result => result is null)
        .WaitAndRetry(
            10,
            retryAttempt => TimeSpan.FromSeconds(60),
            onRetry: (response, delay, retryCount, context) => Trace.WriteLine($"[{DateTime.UtcNow}] Call failed. Retrying after 60 seconds...(Attempt {retryCount} of 10)"))
    .ExecuteAndCapture(() => AuthenticationManager.Authenticate());

if (policyResult.Outcome == OutcomeType.Failure)
{
    throw new CustomException("FAILED", policyResult.FinalException);
}

else
{
    string value = policyResult.FinalHandledResult;
}
0
Fildor On

These UnitTests pass: (xUnit)

[Fact]
public void Test1()
{
    var policy = Policy<string>
        .Handle<Exception>()
        .OrResult(result => string.IsNullOrWhiteSpace(result))
        .Fallback(() => throw new CustomException())
        .Wrap(
              Policy<string>
                 .Handle<Exception>()
                 .OrResult(result => string.IsNullOrWhiteSpace(result))
                 .WaitAndRetry(
                  10,
                  _ => TimeSpan.FromSeconds(1),
                  onRetry: (response, delay, retryCount, context) => Trace.WriteLine($"[{DateTime.UtcNow}] Authentication failed. Retrying after 1 second...(Attempt {retryCount} of 10)")
                  )
        );

    var result = policy.ExecuteAndCapture(() => throw new Exception("Muh"));
    Assert.IsType<CustomException>(result.FinalException);
    Assert.Null(result.Result);
    Assert.Equal(OutcomeType.Failure, result.Outcome);
}

[Fact]
public void Test2()
{
    var policy = Policy<string>
        .Handle<Exception>()
        .OrResult(result => string.IsNullOrWhiteSpace(result))
        .Fallback(() => throw new CustomException())
        .Wrap(
              Policy<string>
                 .Handle<Exception>()
                 .OrResult(result => string.IsNullOrWhiteSpace(result))
                 .WaitAndRetry(
                  10,
                  _ => TimeSpan.FromSeconds(1),
                  onRetry: (response, delay, retryCount, context) => Trace.WriteLine($"[{DateTime.UtcNow}] Authentication failed. Retrying after 1 second...(Attempt {retryCount} of 10)")
                  )
        );

    Assert.Throws<CustomException>(() => policy.Execute(() => throw new Exception("Muh")));
}

So, I figure, you could just use Execute, or check the Outcome and rethrow the Exception like so:

var result = policy.ExecuteAndCapture(() => AuthenticationManager.Authenticate());
if( result.Outcome == OutcomeType.Failure && result.FinalException is not null)
{
    throw result.FinalException;
}
return result.Result;