I'm trying to get a Polly timeout policy to execute and it's not working, but when I combine it with a retry policy it works fine. The policies are defined as follows: (Disclaimer: this is pure test code, ignore code smells)
var httpRetryPolicy = Policy.Handle<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(
5,
retryAttempt => TimeSpan.FromSeconds(1),
(_, _, retryAttempt, _) => Debug.WriteLine($"Retrying ({retryAttempt})")
);
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
4,
TimeoutStrategy.Optimistic,
(_, _, _, _) => { Debug.WriteLine("Timeout"); return Task.CompletedTask; }
);
The endpoint for the policy/delay test in PollyTestController is:
[HttpGet("{policyKey}/{delay}")]
public async Task<IActionResult> RunPolicyTest(string policyKey, int delay)
{
AsyncPolicyWrap<HttpResponseMessage> policy = null;
if(policyKey == "T")
policy = Policy.WrapAsync(timeoutPolicy, Policy.NoOpAsync<HttpResponseMessage>()));
else if(policyKey == "TR")
policy = Policy.WrapAsync(timeoutPolicy, httpRetryPolicy));
HttpClient httpClient = new HttpClient();
var url = $"http://localhost:5000/test/{delay}";
try
{
var response = await policy.ExecuteAsync(() => httpClient.GetAsync(url));
return Ok(await response.Content.ReadAsStringAsync());
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
And the test endpoint in TestController is:
[HttpGet("{delay}")]
public IActionResult GetWithDelay(int delay)
{
if (delay == 10) return NotFound("Not found");
Thread.Sleep(delay*1000);
return Ok("Success");
}
Scenario 1 - call http://localhost:5000/pollytest/T/6
This will select the "T" policy (timeout) to execute the call to http://localhost:5000/test/6. From the code above it's clear that the response will be delayed for 6 seconds, and since the timeout in the policy is 4 seconds, a TimeoutRejectedException should be thrown. But that's not happening: The request hangs for exactly 6 seconds before returning with a "Success" response. Nothing is printed to the Output window.
Scenario 2 - call http://localhost:5000/pollytest/TR/10
This will select the "TR" policy (timeout wrapped around retry) to execute the call to http://localhost:5000/test/10. Because of the value 10 for the delay, the endpoint will return a 404, so the retry policy will try 5 times with a delay of 1 second between each try. But this time the timeout policy kicks in after the fourth try, the fifth try never happens, the Output window shows:
Retrying (1)
Retrying (2)
Retrying (3)
Retrying (4)
Timeout
and the response is "The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout."
What am I missing here? Why is Scenario 1 not triggering the timeout policy?
In case of optimistic timeout the policy uses a
CancellationTokento stop the decorated method if the predefined duration is elapsed.So, all you need is to pass use a different overload of the ExecuteAsync
ExecuteAsyncis a user-defined cancellation token. If you have your own you can provide it here and Polly will chain that to the Timeout'sCancellationToken. If you don't have then you can provide None.CancellationTokenwhich either a chained or a single token depending on the second parameter of theExecuteAsyncUPDATE #1
Let's focus on this bit:
In case of
"T"you have used theNoOp. That policy was designed for unit testing only: test your method like there is no policy. Having theNoOpas the inner policy has the same effect as using theNoOpdirectly, it will not trigger escalation:Output
So, you have to use only the
timeoutPolicyif you receive"T". To do that, you have to change thepolicy's type fromAsyncPolicyWrap<HttpReponseMessage>to simplyIAsyncPolicy<HttpResponseMessage>Or