Search code examples
c#.net-coredotnet-httpclientpollycircuit-breaker

Do Named HTTP clients use the same policy handlers instances?


Do HTTP policies instances are shared when I create named HTTP client in each request using IHttpClientFactory?

I am using the IHttpClientFactory to create a named-http-client per request:

var workloadHttpClient = _httpClientFactory.CreateClient(product.ToString());
var response = await workloadHttpClient.SendAsync(message);

I am defining named-clients, and Polly's Circuit Breaker per such client:

services
    .AddSingleton<IProductProvider, XProvider>()
    .AddHttpClient(Products.X.ToString())
    .AddPolicyHandler(BuildCircuitBreakerPolicy());

services
    .AddSingleton<IProductProvider, YProvider>()
    .AddHttpClient(Products.Y.ToString())
    .AddPolicyHandler(BuildCircuitBreakerPolicy());

With this definition, will the circuit breaker work?

Will all the created clients named "X" use the same instance of the circuit breaker policy, thus really counting all the failures from all executions of XProvider?


Solution

  • The AddPolicyHandler calls the AddHttpClientHandler to register a PolicyHttpMessageHandler under the hood. This handler is a DelegatingHandler.

    Whenever you use HttpClientFactory then the underlying HttpClientHandlers (and the associated DelegatingHandlers) are cached for 2 minutes by default.

    That means whenever you create a new HttpClient with the same name then a cached HttpClientHandler will be used. After 2 minutes elapses then the handler is marked as to-be-cleaned. Whenever a new HttpClient with the same name is requested then a new HttpClientHandler will be created.

    Here you can find a simplified demo to show you that the CB indeed breaks on the second HttpClient instance:

    using System;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Microsoft.Extensions.DependencyInjection;
    using Polly;
                        
    public class Program
    {
        public static async Task Main()
        {
            var collection = new ServiceCollection();
            collection.AddHttpClient();
            collection.AddHttpClient("A")
                .AddPolicyHandler(GetCircuitBreakerPolicy());
            var sp = collection.BuildServiceProvider();
            var factory = sp.GetRequiredService<IHttpClientFactory>();
            for(int i=0; i < 10; i++)
            {
                var client = factory.CreateClient("A");
                var res = await client.GetAsync("http://httpstat.us/500");
                Console.WriteLine(res.StatusCode);
            }
        }
        
        static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
            => Policy<HttpResponseMessage>
                .HandleResult(res => res.StatusCode == HttpStatusCode.InternalServerError)
                .CircuitBreakerAsync(1, TimeSpan.FromSeconds(2));
    }
    

    If you want to change this 2 minutes caching period then please check the HandlerLifetime of the HttpClientFactoryOptions.