Description
openedon Mar 12, 2024
Description
When registering the standard resilience handler using builder.Services.ConfigureHttpClientDefaults(x => x.AddStandardResilienceHandler)
, all HttpClient
s 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.e575128cRuntime 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