-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
PolicyWrap
To provide a simple way to combine resilience strategies.
PolicyWrap
provides a flexible way to encapsulate applying multiple resilience policies to delegates in a nested fashion (sometimes known as the 'Russian-Doll' or 'onion-skin-layers' model).
Rather than the longhand:
fallback.Execute(() => breaker.Execute(() => retry.Execute(action)));
A PolicyWrap
can express this:
Policy.Wrap(fallback, breaker, retry).Execute(action);
or equivalently:
fallback.Wrap(breaker.Wrap(retry)).Execute(action);
or equivalently:
fallback.Wrap(breaker).Wrap(retry).Execute(action);
In these examples, retry
is the innermost policy, immediately wrapping the delegate; it will pass its results (or failure) back to breaker
; and in turn back to the outermost, fallback
.
A PolicyWrap
is just another Policy
, and has the same qualities:
- it is thread-safe
- it can be reused across multiple call sites
- a non-generic
PolicyWrap
can be used with the generic.Execute/Async<TResult>(...)
methods, across multipleTResult
types.
Also:
- a
PolicyWrap
can be onward-wrapped into further wraps, to build more powerful combinations. - the same
Policy
instance can thread-safely be used in more than onePolicyWrap
. (EachPolicyWrap
can weave a different thread through available policy instances; policy instances do not have only one possible antecedent or subsequent, in wraps.)
From a functional-programming or mathematical perspective, this is functional composition of a higher-order function, as in Linq or Rx.
If applying a policy to a delegate is f(x)
(where f
is the Polly policy and x
the delegate), a PolicyWrap
allows you to express a(b(c(d(e(f(x))))))
(or: a(b(c(d(e(f)))))(x)
), (where a
to f
are policies, and x
the delegate).
Syntax examples given are sync; comparable async overloads exist for asynchronous operation. See readme and wiki for more details.
PolicyWrap policyWrap = Policy.Wrap(fallback, cache, retry, breaker, timeout, bulkhead);
PolicyWrap policyWrap = fallback.Wrap(cache).Wrap(retry).Wrap(breaker).Wrap(timeout).Wrap(bulkhead);
// or (functionally equivalent)
PolicyWrap policyWrap = fallback.Wrap(cache.Wrap(retry.Wrap(breaker.Wrap(timeout.Wrap(bulkhead)))));
The instance syntax allows you to build variations on a theme, mixing common and site-specific resilience needs:
PolicyWrap commonResilience = Policy.Wrap(retry, breaker, timeout);
// ... then wrap in extra policies specific to a call site:
Avatar avatar = Policy
.Handle<Whatever>()
.Fallback<Avatar>(Avatar.Blank)
.Wrap(commonResilience)
.Execute(() => { /* get avatar */ });
// Share the same commonResilience, but wrap with a different fallback at another call site:
Reputation reps = Policy
.Handle<Whatever>()
.Fallback<Reputation>(Reputation.NotAvailable)
.Wrap(commonResilience)
.Execute(() => { /* get reputation */ });
When you wrap non-generic Policy
s together, the PolicyWrap
remains non-generic, no matter how many policies in the wrap. Any .Execute<TResult>
can be executed through the non-generic wrap using the .Execute<TResult>
generic method.
When you include a generic-typed SomePolicy<TResult>
in a wrap, the PolicyWrap
as a whole becomes generic-typed PolicyWrap<TResult>
. This provides type-safety: it would be non-sensical to have (paraphrasing syntax)
fallback<int>(...).Execute(() => breaker<string>(...).Execute(() => retry<Foo>(...).Execute<Bar>(func)));
(just as Linq and Rx do not let you do this).
-
PolicyWrap
executes the supplied delegate through the layers or wrap: the outermost (leftmost in reading order) policy executes the next inner, which executes the next inner, etc, until the innermost policy executes the user delegate. - Exceptions bubble back outwards (until handled) through the layers.
Policies can be combined flexibly in any order. It is worth however considering the following points
Policy type | Common positions in a PolicyWrap
|
Explanation |
---|---|---|
FallbackPolicy |
Usually outermost | Provides a substitute value after all other resilience strategies have failed. |
FallbackPolicy |
Can also be used mid-wrap ... | ... eg as a failover strategy calling multiple possible endpoints (try first; if not, try next). |
CachePolicy |
As outer as possible but not outside stub fallbacks | As outer as possible: if you hold a cached value, you don't want to bother trying the bulkhead or circuit-breaker etc. But cache should not wrap any FallbackPolicy providing a placeholder-on-failure (you likely don't want to cache and serve the placeholder to all subsequent callers) |
TimeoutPolicy |
Outside any RetryPolicy , CircuitBreaker or BulkheadPolicy
|
... to apply an overall timeout to executions, including any delays-before-retry / wait for bulkhead |
RetryPolicy and CircuitBreaker
|
Either retry wraps breaker, or vice versa. | Judgment call. With longer delays between retries (eg async background jobs), we have Retry wrap CircuitBreaker (the circuit-state might reasonably change in the delay between tries). With no/short delays between retries, we have CircuitBreaker wrap Retry (don't take hammering the underlying system with three closely-spaced tries as cause to break the circuit). |
BulkheadPolicy |
Usually innermost unless wraps a final TimeoutPolicy ; certainly inside any WaitAndRetry
|
Bulkhead intentionally limits parallelization. You want that parallelization devoted to running the delegate, not eg occupied by waits for a retry. |
TimeoutPolicy |
Inside any RetryPolicy , CircuitBreaker or BulkheadPolicy , closest to the delegate. |
... to apply a timeout to an individual try. |
You may use the same policy-type multiple times (eg two RetryPolicy
s; two FallbackPolicy
s) in the same wrap, to set different handling strategies for different exceptions/faults as part of one overall resilience strategy. For example:
- You might retry more times or with shorter delay for one kind of exception, than another.
- You may want one kind of exception to immediately break the circuit; others to break more cautiously.
- You can provide different fallback values/messages for different handled faults.
- You might nest multiple cache policies with different kinds of cache: eg memory-cache, backed up by disk or cloud cache.
We do not recommend the use of .ExecuteAndCapture(...)
with PolicyWrap
. A PolicyWrap
instance may contain multiple policies each specifying .Handle<>()
, .HandleResult<>()
, .Or<>()
and .OrResult<>()
clauses; and these clauses may differ for different policies in the wrap. .ExecuteAndCapture(...)
on a PolicyWrap
instance cannot therefore (at v5.0), in a simple and clear way, identify whether a given result should be considered FaultType.ResultHandledByThisPolicy
or FaultType.ExceptionHandledByThisPolicy
.
A PolicyKey
can be attached to a PolicyWrap
, as with any other policy:
PolicyWrap commonResilience = Policy
.Wrap(retry, breaker, timeout)
.WithPolicyKey("CommonServiceResilience");
The wrap's PolicyKey
is exposed on the execution Context
as the property:
context.PolicyWrapKey
In a multiply-nested wrap, the PolicyKey
attached to the outermost wrap carries all through the execution as the context.PolicyWrapKey
.
The future Polly roadmap envisages adding metrics to Polly, with metrics for the overall execution time (latency) across a PolicyWrap
or elements of a wrap.
PolicyWrap
is thread-safe: multiple calls may safely be placed concurrently through a policy instance.
PolicyWrap
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