Polly timeout policy clarification

Lorenzo picture Lorenzo · Apr 14, 2017 · Viewed 8.5k times · Source

I am trying to get the timout Policy to work correctly. I have the following requirement while integrating an api.

  1. Create an http request to invoke endpoint1 and pass the transactionID and capture the result
  2. if the http request does not answer in 20 seconds then send a cancel request with the same transactionID and capture the result

For this task I would like to use Polly which seems to me a fantastic component to help handling transient failures. However as I am very new to this technology I just want to be sure if I am implementing correctly.

First of all I have created a timeout policy with Polly like this

var timeoutPolicy =
    Policy.TimeoutAsync(
        TimeSpan.FromSeconds( 20 ),
        TimeoutStrategy.Optimistic,
        async ( context, timespan, task ) => {
            //write here the cancel request 
        } );

then after that I am ready to execute the policy

var policyResult = await timeoutPolicy.ExecuteAndCaptureAsync( async () => {
    //make here the request 1
} );

What I got from the documentation is that if a timout occurs inside the timeoutPolicy.ExecuteAndCaptureAsync delegate Polly automagically invoke the onTimeout delegate. Right?

However my questions are:

  • What happens if inside the execute delegate an exception occurs? Should I wrap that polly construct in a try catch?
  • When I analyze the policy result how do I understand if the timeout has happened or not?

Answer

mountain traveller picture mountain traveller · Apr 14, 2017

What I got from the documentation is that if a timeout occurs inside the ExecuteAndCaptureAsync delegate Polly automagically invoke the onTimeout delegate. Right?

Correct.

What happens if inside the execute delegate an exception occurs?

Because you are using ExecuteAndCaptureAsync(...), the exception is placed in policyResult.FinalException.

Should I wrap that polly construct in a try catch?

Because you are using ExecuteAndCaptureAsync(..), the exception is placed in policyResult.FinalException, so you don't need a try-catch.

When I analyze the policy result how do I understand if the timeout has happened or not?

TimeoutPolicy throws TimeoutRejectedException on a timeout. Because you are using ExecuteAndCaptureAsync(...), you should find that exception placed in policyResult.FinalException.


A couple of further comments. With TimeoutStrategy.Optimisitic, which is based on co-operative cancellation by CancellationToken, you should execute a delegate taking a cancellation token:

var policyResult = await timeoutPolicy.ExecuteAndCaptureAsync(async (ct) => {
    //make request 1, in a form which responds to the cancellation token ct
}, userCancellationToken /* CancellationToken.None is acceptable. Polly will merge its timing-out CancellationToken into ct, during policy execution. */
);

Second, as an alternative to invoking the cancel request inside the onRetryAsync: async ( context, timespan, task ) => { ... }, you have the option to make the code more sequential / less nested with a pattern like below:

var policyResult = await timeoutPolicy.ExecuteAndCaptureAsync(async (ct) => {
    //make request 1, in a form which responds to the cancellation token ct
}, CancellationToken.None);

if (policyResult.Outcome == OutcomeType.Failure && policyResult.FinalException is TimeoutRejectedException)
{
    //write here the cancel request 
}

UPDATE: Invoking the cancel request will work either way - from inside the onRetryAsync, or sequentially, as just above. An advantage of the sequential version is that it may make it easier to reason about what happens if the cancel request fails with an exception. With the nested approach (cancel request invoked inside onRetryAsync), an exception finally captured into policyResult.FinalException could come from either the initial request or the cancel request - and it may be hard to tell which.