Authoring a proactive Polly policy (Custom policies Part II)
In this article we'll build our first custom Polly policy: a proactive Policy to capture execution timings.
Polly polices fall into two categories: reactive (which react to faults) and non-reactive or proactive (which act on all executions). To author a policy which reacts to faults, see Part III: Authoring a reactive custom policy.
For background on custom policies and the Polly.Contrib, see Part I: Introducing custom Polly policies.
A final article in the series, Part IV, covers Custom policies for all execution types: sync and async, generic and non-generic.
Authoring a non-reactive custom policy: Capture execution timing
For simplicity, in this article we'll assume we're developing a custom policy for use with Polly and HttpClientFactory. This means that all executions are async and return
For this, we only need to implement the
IAsyncPolicy<TResult> form of the policy. For other forms, see Part IV of the series.
Step 1: Name your policy and extend the base class
The base class for async generic policies is, as you might expect,
AsyncPolicy<TResult>. So start by declaring:
By extending the base class, your policy is already plugged in to all the benefits of the existing Polly architecture: the execution-dispatch, the integration into
The compiler or IDE will report:
AsyncTimingPolicy<TResult> does not implement inherited abstract member AsyncPolicy<TResult> .ImplementationAsync(Func<Context, CancellationToken, Task<TResult>>, Context, CancellationToken, bool)
Step 2: Add a 'no-op' implementation
Let's fulfil the abstract method with a stub (your IDE may have a tooltip or shortcut to help):
This method is where your policy defines its implementation!
Let's look at the parameters - these are the information passed when the user executes a call through the policy:
Func<Context, CancellationToken, Task<TResult>> actionrepresents the delegate executed through the policy
Context contextis the context passed to execution
CancellationToken cancellationTokenis (you guessed it) the cancellation token passed to execution
bool continueOnCapturedContext, whether the user asked to continue after
awaits on a captured context.
The parameters map directly to the calling code:
So, let's flesh out our policy to provide a basic 'no-op' implementation. This just executes the code passed to the policy 'as is', using the parameters the user passed to execute the
action. The parameter
continueOnCapturedContext controls continuation context after the
That's it for a no-op implementation!
Aside: To make it easier for you to get started, Polly provides all this as a template starting point for a custom policy. To author your own custom policy, you can grab the template repo and copy/paste/rename the classes.
Step 3: Add your custom functionality
To implement custom functionality, you just code ... whatever you want ... around the
await action(...) statement.
We're going to calculate execution duration, so we'll use
Hmm, we still need to let the user do something with that
elapsed value. To keep this blog post simple and illustrate some points about async policy implementation, we'll intentionally allow users to provide an async delegate as
timingPublisher. (For a production implementation, you might want instead to adopt the event pattern or
We also make the
timingPublisher delegate take an input parameter
Polly.Context: this execution-scoped context travels with every Polly execution. Users can (optionally) use it to pass extra information in to the execution, to exchange information between different parts of the execution, or to capture information after execution (more info here and here). It also carries important metadata about the execution which can be useful for filtering. It's good practice to make
Context an input parameter to any delegate you let users configure on a policy.
Note [*] the use of
.ConfigureAwait(continueOnCapturedContext) on the extra
await we added. Whenever you code a new async policy, you should decorate all
await statements in this way, if you want your policy to play nicely with what Polly users expect. Best practice for libraries is to
.ConfigureAwait(false). However, some specialist uses of Polly (such as Actors) require continuations to continue on the original context; so Polly provides this control bool to be used on all
Step 4: Add configuration syntax
Finally, add some configuration syntax so that users can create instances of your policy!
You'll see the code above had a public constructor. There's nothing wrong with this, but by convention, Polly configuration syntax uses static factory methods (eg
Policy.Timeout(30)). So we would suggest amending the construction like this:
Let's take it for a spin!
Let's take the new
AsyncTimingPolicy<TResult> for a spin!
This is also an example of a quick way to test HttpClientFactory with Polly policies in a Console App.
Here's some example output:
Took 0.331 seconds retrieving https://www.google.com/ Took 0.146 seconds retrieving https://www.google.co.uk/ Took 0.121 seconds retrieving https://www.bbc.co.uk/
Of course, writing to the Console is the least imaginative use. The intention of this policy is to enable you to capture execution timing metrics to your favourite time-series db (Prometheus, InfluxDB, Azure Time Series, App-Metrics).
Where can I get the code?
The full source code for
AsyncTimingPolicy<TResult> and the test console app is available in this Github repo.
Grab the template
If you want to dive straight in to experimenting with a custom policy, just grab the template repo and start copy/paste/rename/extend-ing the classes.
Authoring a reactive custom policy
In the next part of this series, Part III, we'll look at authoring a reactive custom policy, to log any exception or fault and then rethrow or bubble it outwards. The new policy will allow you to inject custom logging into any position in a
Finally, in Part IV, we'll look at how to author other forms of policy - synchronous policies and non-generic policies - in the most efficient manner.