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
CancellationToken
to stop the decorated method if the predefined duration is elapsed.So, all you need is to pass use a different overload of the ExecuteAsync
ExecuteAsync
is 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.CancellationToken
which either a chained or a single token depending on the second parameter of theExecuteAsync
UPDATE #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 theNoOp
as the inner policy has the same effect as using theNoOp
directly, it will not trigger escalation:Output
So, you have to use only the
timeoutPolicy
if you receive"T"
. To do that, you have to change thepolicy
's type fromAsyncPolicyWrap<HttpReponseMessage>
to simplyIAsyncPolicy<HttpResponseMessage>
Or