Skip to content

Shared circuit breaker when registering a resilience handler with ConfigureHttpClientDefaults #5021

Open

Description

Description

When registering the standard resilience handler using builder.Services.ConfigureHttpClientDefaults(x => x.AddStandardResilienceHandler), all HttpClients seem to be using a shared circuit breaker. When one client is causing the circuit to open, the circuit opens for all clients.

Reproduction Steps

Here is my project file:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.2.0" />
  </ItemGroup>

</Project>

Here is my appsettings.json (modified to be able to more easily reproduce the issue):

{
  "RetryOptions": {
    "Retry": {
      "Delay": "00:00:00",
      "MaxRetryAttempts": 1
    }
  }
}

And here is the Program.cs:

var builder = WebApplication.CreateBuilder(args);

var section = builder.Configuration.GetSection("RetryOptions");

var succeedingClientBuilder = builder.Services.AddHttpClient<SucceedingClient>(x =>
    x.BaseAddress = new Uri("https://ipv4.icanhazip.com/"));

var failingClientBuilder = builder.Services.AddHttpClient<FailingClient>(x =>
    x.BaseAddress = new Uri("http://the-internet.herokuapp.com/status_codes/500"));

var useHttpClientDefaults = true;
if (useHttpClientDefaults)
{
    builder.Services.ConfigureHttpClientDefaults(x => x.AddStandardResilienceHandler(section));
}
else
{
    succeedingClientBuilder.AddStandardResilienceHandler(section);
    failingClientBuilder.AddStandardResilienceHandler(section);
}

var app = builder.Build();

app.MapGet("/test", async (SucceedingClient succeedingClient, FailingClient failingClient) =>
{
    var succeedingClientResult = await succeedingClient.Get();
    var failingClientResult = await failingClient.Get();

    return $"""
        SucceedingClient result: {succeedingClientResult}
        FailingClient result: {failingClientResult}
        """;
});

app.Run();

public class SucceedingClient(HttpClient client)
{
    public async Task<string> Get()
    {
        try
        {
            using var response = await client.GetAsync("");
            return $"Status code {response.StatusCode}";
        }
        catch (Exception e)
        {
            return $"Exception {e.Message}";
        }
    }
}

public class FailingClient(HttpClient client)
{
    public async Task<string> Get()
    {
        try
        {
            using var response = await client.GetAsync("");
            return $"Status code {response.StatusCode}";
        }
        catch (Exception e)
        {
            return $"Exception {e.Message}";
        }
    }
}

Run this sample code and continuously hit the /test endpoint until the circuit breaker kicks in.

Expected behavior

I would expect the circuit would only open for the client that is facing issues. From the reproduction steps, the circuit should not open for SucceedingClient when it opens for FailingClient.

Actual behavior

The circuit breaker opens for both the SucceedingClient and the FailingClient, even though only FailingClient is receiving status code 500.

Regression?

No response

Known Workarounds

Use AddStandardResilienceHandler on each HttpClient instead of using ConfigureHttpClientDefaults.

Configuration

dotnet --info .NET SDK: Version: 8.0.200 Commit: 438cab6a9d Workload version: 8.0.200-manifests.e575128c

Runtime Environment:
OS Name: Windows
OS Version: 10.0.22631
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\8.0.200\

.NET workloads installed:
There are no installed workloads to display.

Host:
Version: 8.0.2
Architecture: x64
Commit: 1381d5ebd2

.NET SDKs installed:
8.0.200 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Other information

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions