1

I am testing a Polly TimeOut policy

public static IAsyncPolicy<HttpResponseMessage> GetTimeoutPolicy(int timeoutSeconds)
{
    return Policy.TimeoutAsync<HttpResponseMessage>(
       timeout: TimeSpan.FromSeconds(timeoutSeconds),
       timeoutStrategy: TimeoutStrategy.Optimistic);
}

with the following test:

[Fact]
public async Task GetTimeoutPolicy_ThrowsTimeoutRejectedException2()
{
    int timeoutSeconds = 1;
    var policy = HttpPolicies.HttpPolicies.GetTimeoutPolicy(timeoutSeconds);

    await policy.Awaiting(p => p.ExecuteAsync(async () =>
    {
      await Task.Delay(TimeSpan.FromSeconds(2));
      return new HttpResponseMessage(System.Net.HttpStatusCode.OK);
    })).Should().ThrowAsync<TimeoutRejectedException>();
}

The test fails if I don't use the TimeoutStrategy.Pessimistic strategy, it is the Task.Delay that cause the issue? How can I solve the issue?

1 Answer 1

1

There are 4 different scenarios based on

  • your decorated code is synchronous or asynchronous
  • your timeout is using optimistic or pessimistic strategy

Sync Policy + Pessimistic strategy

var syncTimeoutPessimistic = Policy.Timeout(
    TimeSpan.FromSeconds(3), 
    TimeoutStrategy.Pessimistic, 
    (_, __, ___) =>
{
    "onTimeout is fired".Dump();
});

try
{
    syncTimeoutPessimistic.Execute(() =>
    {
        Thread.Sleep(5_000);
        "Decorated operation is finished".Dump();
    });
} 
catch(TimeoutRejectedException)
{
    "Timeout has kicked in".Dump();
}

Sync Policy + Optimistic strategy

  • CancellationToken should be respected otherwise it won't work
var syncTimeoutOptimistic = Policy.Timeout(
    TimeSpan.FromSeconds(3), 
    TimeoutStrategy.Optimistic, 
    (_, __, ___) =>
{
    "onTimeout is fired".Dump();
});

try
{
    syncTimeoutOptimistic.Execute((ct) =>
    {
        var result = Task.Run(() => { Thread.Sleep(5_000); return Task.CompletedTask; });
        result.Wait(ct);
        "Decorated operation is finished".Dump();
    }, CancellationToken.None);
} 
catch(TimeoutRejectedException)
{
    "Timeout has kicked in".Dump();
}

It is more like a hack than a suggested way.

Async Policy + Pessimistic strategy

var asyncTimeoutPessimistic = Policy.TimeoutAsync(
    TimeSpan.FromSeconds(3), 
    TimeoutStrategy.Pessimistic, 
    (_, __, ___) =>
{
    "onTimeout is fired".Dump();
    return Task.CompletedTask;
});

try
{
    await asyncTimeoutPessimistic.ExecuteAsync(async () =>
    {
        await Task.Delay(5_000);
        "Decorated operation is finished".Dump();
    });
} 
catch(TimeoutRejectedException)
{
    "Timeout has kicked in".Dump();
}

Async + Optimistic strategy

  • CancellationToken should be respected otherwise it won't work
var asyncTimeoutOptimistic = Policy.TimeoutAsync(
    TimeSpan.FromSeconds(3), 
    TimeoutStrategy.Optimistic, 
    (_, __, ___) =>
{
    "onTimeout is fired".Dump();
    return Task.CompletedTask;
});

try
{
    await asyncTimeoutOptimistic.ExecuteAsync(async (ct) =>
    {
        await Task.Delay(5_000, ct);
        "Decorated operation is finished".Dump();
    }, CancellationToken.None);
} 
catch(TimeoutRejectedException)
{
    "Timeout has kicked in".Dump();
}

In all cases you should see the following output:

Dumping object(String)
 onTimeout is fired
Dumping object(String)
 Timeout has kicked in

So, for your code (async Policy + Pessimistic strategy) you should use a different overload of ExecuteAsync which utilizes the CancellationToken. Don't forget to pass the ct to the Task.Delay!!!

p => p.ExecuteAsync(async ct =>
{
    await Task.Delay(TimeSpan.FromSeconds(2) ct);
    return new HttpResponseMessage(System.Net.HttpStatusCode.OK);
}, CancellationToken.None)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.