Circuit breaker pattern for preventing cascading failures. Health degradation detection, half-open state, and recovery. Trigger: circuit breaker, cascading failure, half-open, break duration.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Closed (Normal)
↓ failure ratio exceeds threshold
Open (Blocking) — all requests fail immediately with BrokenCircuitException
↓ break duration expires
Half-Open (Testing) — allows one probe request through
↓ probe succeeds → Closed
↓ probe fails → Open (reset break timer)
builder.Services.AddHttpClient("PaymentService", client =>
{
client.BaseAddress = new Uri("https://payments.{Company}.com");
})
.AddResilienceHandler("payment-resilience", pipeline =>
{
// Retry first (innermost)
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 2,
Delay = TimeSpan.FromMilliseconds(500),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true
});
// Circuit breaker wraps retry (opens when retries keep failing)
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
// Open when 50% of requests fail
FailureRatio = 0.5,
// Over this time window
SamplingDuration = TimeSpan.FromSeconds(30),
// Minimum requests before evaluating
MinimumThroughput = 10,
// Stay open for this duration before half-open
BreakDuration = TimeSpan.FromSeconds(30)
});
// Timeout per request
pipeline.AddTimeout(TimeSpan.FromSeconds(5));
});
builder.Services.AddResiliencePipeline("external-service", pipeline =>
{
pipeline.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.3,
SamplingDuration = TimeSpan.FromSeconds(60),
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30),
ShouldHandle = new PredicateBuilder()
.Handle<HttpRequestException>()
.Handle<TimeoutRejectedException>(),
OnOpened = args =>
{
// Log when circuit opens
var logger = args.Context
.Properties.GetValue(
new ResiliencePropertyKey<ILogger>("logger"),
null!);
logger?.LogWarning(
"Circuit opened for {Duration}. " +
"Reason: {Outcome}",
args.BreakDuration,
args.Outcome?.Exception?.Message);
return ValueTask.CompletedTask;
},
OnClosed = args =>
{
var logger = args.Context
.Properties.GetValue(
new ResiliencePropertyKey<ILogger>("logger"),
null!);
logger?.LogInformation("Circuit closed — service recovered");
return ValueTask.CompletedTask;
},
OnHalfOpened = args =>
{
var logger = args.Context
.Properties.GetValue(
new ResiliencePropertyKey<ILogger>("logger"),
null!);
logger?.LogInformation(
"Circuit half-open — testing recovery");
return ValueTask.CompletedTask;
}
});
});
public sealed class PaymentService(
IHttpClientFactory httpClientFactory,
ILogger<PaymentService> logger)
{
public async Task<Result<PaymentResponse>> ChargeAsync(
Guid orderId, decimal amount, CancellationToken ct)
{
try
{
var client = httpClientFactory.CreateClient("PaymentService");
var response = await client.PostAsJsonAsync(
"/charges",
new { orderId, amount },
ct);
response.EnsureSuccessStatusCode();
var result = await response
.Content.ReadFromJsonAsync<PaymentResponse>(ct);
return Result<PaymentResponse>.Success(result!);
}
catch (BrokenCircuitException)
{
logger.LogWarning(
"Payment service circuit is open. " +
"Order {OrderId} payment deferred", orderId);
return Result<PaymentResponse>.Failure(
Error.ServiceUnavailable("Payment.Unavailable",
"Payment service is temporarily unavailable"));
}
}
}
// Custom health check that reflects circuit state
public sealed class PaymentServiceHealthCheck(
ResiliencePipelineProvider<string> pipelineProvider)
: IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken ct = default)
{
// The circuit breaker state indicates downstream health
// If the circuit is open, report degraded
try
{
var pipeline = pipelineProvider
.GetPipeline("external-service");
// If we can get the pipeline, the circuit state is managed
return Task.FromResult(
HealthCheckResult.Healthy("Payment service reachable"));
}
catch
{
return Task.FromResult(
HealthCheckResult.Degraded("Payment service circuit open"));
}
}
}
// appsettings.json
{
"Resilience": {
"PaymentService": {
"FailureRatio": 0.5,
"SamplingDurationSeconds": 30,
"MinimumThroughput": 10,
"BreakDurationSeconds": 30
}
}
}
// Options class
public sealed class CircuitBreakerOptions
{
public double FailureRatio { get; init; } = 0.5;
public int SamplingDurationSeconds { get; init; } = 30;
public int MinimumThroughput { get; init; } = 10;
public int BreakDurationSeconds { get; init; } = 30;
}
// Usage
var cbOptions = builder.Configuration
.GetSection("Resilience:PaymentService")
.Get<CircuitBreakerOptions>()!;
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = cbOptions.FailureRatio,
SamplingDuration = TimeSpan.FromSeconds(
cbOptions.SamplingDurationSeconds),
MinimumThroughput = cbOptions.MinimumThroughput,
BreakDuration = TimeSpan.FromSeconds(
cbOptions.BreakDurationSeconds)
});
BrokenCircuitException in calling codeAddCircuitBreaker or CircuitBreakerStrategyOptionsBrokenCircuitException handlingCircuitBreakerPolicy (migration candidate)BrokenCircuitException with fallback or graceful degradation| Parameter | Conservative | Balanced | Aggressive |
|---|---|---|---|
| Failure ratio | 0.7 | 0.5 | 0.3 |
| Sampling duration | 60s | 30s | 15s |
| Min throughput | 20 | 10 | 5 |
| Break duration | 60s | 30s | 15s |