Repository navigation
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
PolicyWrapcan be used with the generic.Execute/Asyncmethods, across multiple(...) TResulttypes.
Also:
- a
PolicyWrapcan be onward-wrapped into further wraps, to build more powerful combinations. - the same
Policyinstance can thread-safely be used in more than onePolicyWrap. (EachPolicyWrapcan 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 Policys together, the PolicyWrap remains non-generic, no matter how many policies in the wrap. Any .Execute can be executed through the non-generic wrap using the .Execute generic method.
When you include a generic-typed SomePolicy in a wrap, the PolicyWrap as a whole becomes generic-typed PolicyWrap. 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).
-
PolicyWrapexecutes 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 RetryPolicys; two FallbackPolicys) 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, as at v5.0.
.ExecuteAndCapture(...) is intended to tell you whether policy execution was successful or failed - defined in terms of whether a final exception was thrown, and whether any result returned or final exception thrown was handled by the policy or not.
With PolicyWrap, this raises conceptual questions:
- A
PolicyWrapinstance does not itself define any exceptions or results to handle - only its constituents do. - A
PolicyWrapinstance may comprise multiple constituent individual policies, each of which may (designedly) handle different exceptions or results.
In that context, we need to define (Polly does not yet define) what it means to say a given exception or result was or was not handled by the PolicyWrap.
It is proposed that a future release will resolve this by saying that a given exception or result was handled by the PolicyWrap if the outermost policy in the PolicyWrap handled it.
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