Skip to content
bryancrosby edited this page Mar 31, 2017 · 40 revisions

Timeout policy (v5.0 onwards)

Purpose

To ensure the caller never has to wait beyond the configured timeout.

To enforce a timeout on actions having no in-built timeout.

Premise: 'Don't wait forever'

Waiting forever (having no timeout) is a bad design strategy: it specifically leads to the blocking up of threads or connections (itself often a cause of further failure), during a faulting scenario.

Beyond a certain wait, success is unlikely.

Syntax

TimeoutPolicy timeoutPolicy = Policy
  .Timeout([int|TimeSpan|Func<TimeSpan> timeout]
           [, TimeoutStrategy.Optimistic|Pessimistic]
           [, Action<Context, TimeSpan, Task> onTimeout])

TimeoutPolicy timeoutPolicy = Policy
  .TimeoutAsync([int|TimeSpan|Func<TimeSpan> timeout]
                [, TimeoutStrategy.Optimistic|Pessimistic]
                [, Func<Context, TimeSpan, Task, Task> onTimeoutAsync])

Parameters:

  • timeout: the time after which the execute delegate or func should be abandoned. Can be specified as an int (number of seconds), TimeSpan, or func returning a TimeSpan
  • timeoutStrategy (optional): whether to time out optimistically or pessimistically (see below)
  • onTimeout/Async (optional): an action to run when the policy times-out an executed delegate or func.

Throws:

  • TimeoutRejectedException, when an execution is abandoned due to timeout.

Operation

TimeoutPolicy supports optimistic and pessimistic timeout.

Optimistic timeout

TimeoutStrategy.Optimistic assumes delegates you execute support co-operative cancellation (ie honor CancellationTokens).

The policy combines a timing-out CancellationToken into any passed-in CancellationToken, and uses its trust in the executed delegate to honor cancellation, to achieve the timeout.

You must use Execute/Async(...) (or similar) overloads taking a CancellationToken, for optimistic timeout to work.

We recommend using optimistic timeout wherever possible, as it consumes less resource. Optimistic timeout is the default.

Pessimistic timeout

TimeoutStrategy.Pessimistic recognises that there are cases where you need to execute delegates which have no in-built timeout, and do not honor cancellation.

TimeoutStrategy.Pessimistic is designed to allow you nonetheless to enforce a timeout in these cases, guaranteeing still returning to the caller on timeout.

For synchronous executions this comes at a cost: the policy will execute the user delegate as a Task on a ThreadPool thread. (For asynchronous executions, the extra resource cost is marginal: no extra threads or executing Tasks involved.)

What happens to the timed-out delegate?

A key question with any timeout policy is what to do with the abandoned (timed-out) task.

Pessimistic timeout

Polly will not risk the state of your application by unilaterally terminating threads. Instead, for pessimistic executions, TimeoutPolicy captures and passes the abandoned execution to you as the Task parameter of the onTimeout/onTimeoutAsync delegate.

This prevents these tasks disappearing into the ether (remember, with pessimistic executions, we are talking by definition about delegates over which we have no control by cancellation token: they will continue their happy way until they either belatedly complete or fault).

The task property of onTimeout/Async allows you to clean up gracefully even after these otherwise ungovernable calls. You can capture any exception the timed-out task may eventually raise (and prevent these manifesting as UnobservedTaskExceptions):

Policy.Timeout(30, TimeoutStrategy.Pessimistic, (context, timespan, task) => 
    {
        return task.ContinueWith(t => { // ContinueWith important!: the abandoned task may very well still be executing, when the caller times out on waiting for it! 
            if (t.IsFaulted) 
            {
                logger.Error($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds, eventually terminated with: {t.Exception}.");
            }
            else if (t.IsCancelled)
            {
               // extra logic (if desired) for cancellation after timeout, eg
               logger.Error($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds, task cancelled.");
            }
            else
            {
               // extra logic (if desired) for tasks which complete, despite the caller having 'walked away' earlier due to timeout.
            }
        });
    });

Optimistic timeout

For optimistic executions, it is assumed the CancellationToken will cause clean-up of the abandoned task (so the Task parameter passed to onTimeout/onTimeoutAsync is always null)

Configuration recommendations

Every action which could block a thread, or block waiting for a resource or response, should have a timeout. [Michael Nygard: Release It!].

Thread safety and policy reuse

Thread safety

The operation of TimeoutPolicy is thread-safe: multiple calls may safely be placed concurrently through a policy instance.

Policy reuse

TimeoutPolicy instances may be re-used across multiple call sites.

When reusing policies, use an ExecutionKey to distinguish different call-site usages within logging and metrics.

Clone this wiki locally