-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Timeout
To ensure the caller never has to wait beyond the configured timeout.
To enforce a timeout on actions having no in-built timeout.
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.
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 aTimeSpan
- 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. The action is run before the
TimeoutRejectedException
(see below) is thrown.
Throws:
-
TimeoutRejectedException
, when an execution is abandoned due to timeout.
TimeoutPolicy
supports optimistic and pessimistic timeout.
TimeoutStrategy.Optimistic
assumes delegates you execute support co-operative cancellation (ie honor CancellationToken
s).
The policy combines a timing-out CancellationToken
into any passed-in CancellationToken
, and uses the fact that the executed delegate honors cancellation to achieve the timeout. You must use Execute/Async(...)
(or similar) overloads taking a CancellationToken
, and the executed delegate must honor that token:
Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Optimistic);
HttpResponseMessage httpResponse = await _timeoutPolicy
.ExecuteAsync(
async ct => await httpClient.GetAsync(requestEndpoint, ct), // Execute a delegate which responds to a CancellationToken input parameter.
CancellationToken.None // CancellationToken.None here indicates have no independent cancellation control you wish to add to the cancellation provided by TimeoutPolicy.
);
You can also combine your own CancellationToken
(perhaps to carry independent cancellation signalled by the user). For example:
CancellationTokenSource userCancellationSource = new CancellationTokenSource();
// userCancellationSource perhaps hooked up to the user clicking a 'cancel' button, or other independent cancellation
Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Optimistic);
HttpResponseMessage httpResponse = await _timeoutPolicy
.ExecuteAsync(
async ct => await httpClient.GetAsync(requestEndpoint, ct),
userCancellationSource.Token
);
// GetAsync(...) will be cancelled when either the timeout occurs, or userCancellationSource is signalled.
We recommend using optimistic timeout wherever possible, as it consumes less resource. Optimistic timeout is the default.
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.
What is meant by timeout in this case is that the caller 'walks away': stops waiting for the underlying delegate to complete. The underlying delegate is not magically cancelled - see What happens to the timed-out delegate? below.
For synchronous executions, the ability of the calling thread to 'walk away' 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 Task
s involved.
Note that pessimistic timeout for async executions will not timeout purely synchronous delegates. It expects that the executed async
code conforms to the standard async
pattern, returning a Task
representing the continuing execution of that async work (for example when the executed delegate hits an internal await
statement).
A key question with any timeout policy is what to do with the abandoned (timed-out) task.
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 expect to 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 dispose resources, carry out other clean-up, and capture any exception the timed-out task may eventually raise (important, to prevent these manifesting as UnobservedTaskException
s):
Policy.Timeout(30, TimeoutStrategy.Pessimistic, (context, timespan, task) =>
{
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.IsCanceled)
{
// (If the executed delegates do not honour cancellation, this IsCanceled branch may never be hit. It can be good practice however to include, in case a Policy configured with TimeoutStrategy.Pessimistic is used to execute a delegate honouring cancellation.)
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.
}
// Additionally, clean up any resources ...
});
});
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
)
For a good discussion on walking away from executions you cannot cancel (pessimistic timeout), see Stephen Toub on How do I cancel non-cancelable async operations?.
Every action which could block a thread, or block waiting for a resource or response, should have a timeout. [Michael Nygard: Release It!].
The operation of TimeoutPolicy
is thread-safe: multiple calls may safely be placed concurrently through a policy instance.
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.
- Home
- Polly RoadMap
- Contributing
- Transient fault handling and proactive resilience engineering
- Supported targets
- Retry
- Circuit Breaker
- Advanced Circuit Breaker
- Timeout
- Bulkhead
- Cache
- Rate-Limit
- Fallback
- PolicyWrap
- NoOp
- PolicyRegistry
- Polly and HttpClientFactory
- Asynchronous action execution
- Handling InnerExceptions and AggregateExceptions
- Statefulness of policies
- Keys and Context Data
- Non generic and generic policies
- Polly and interfaces
- Some policy patterns
- Debugging with Polly in Visual Studio
- Unit-testing with Polly
- Polly concept and architecture
- Polly v6 breaking changes
- Polly v7 breaking changes
- DISCUSSION PROPOSAL- Polly eventing and metrics architecture