Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docs] Add circuit breaker to the migration guide #1764

170 changes: 170 additions & 0 deletions docs/migration-v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,176 @@ ResiliencePipeline<HttpResponseMessage> pipelineT = new ResiliencePipelineBuilde
>
> For further information please check out the [Timeout resilience strategy documentation](strategies/timeout.md).

## Migrating circuit breaker policies

This section describes how to migrate v7 circuit breaker policies to V8 circuit breaker strategies.

### Circuit breaker in v7

V7's "Standard" Circuit Breaker policy could be defined like bellow:
peter-csala marked this conversation as resolved.
Show resolved Hide resolved

<!-- snippet: migration-circuit-breaker-v7 -->
```cs
// Create sync circuit breaker
ISyncPolicy syncPolicy = Policy
.Handle<SomeExceptionType>()
.CircuitBreaker(
exceptionsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromSeconds(1));

// Create async circuit breaker
IAsyncPolicy asyncPolicy = Policy
.Handle<SomeExceptionType>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromSeconds(1));

// Create generic sync circuit breaker
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy<HttpResponseMessage>
.Handle<SomeExceptionType>()
.CircuitBreaker(
handledEventsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromSeconds(1));

// Create generic async circuit breaker
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy<HttpResponseMessage>
.Handle<SomeExceptionType>()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromSeconds(1));
```
<!-- endSnippet -->

V7's Advanced Circuit Breaker policy could be defined like bellow:
peter-csala marked this conversation as resolved.
Show resolved Hide resolved

<!-- snippet: migration-advanced-circuit-breaker-v7 -->
```cs
// Create sync advanced circuit breaker
ISyncPolicy syncPolicy = Policy
.Handle<SomeExceptionType>()
.AdvancedCircuitBreaker(
failureThreshold: 0.5d,
samplingDuration: TimeSpan.FromSeconds(5),
minimumThroughput: 2,
durationOfBreak: TimeSpan.FromSeconds(1));

// Create async advanced circuit breaker
IAsyncPolicy asyncPolicy = Policy
.Handle<SomeExceptionType>()
.AdvancedCircuitBreakerAsync(
failureThreshold: 0.5d,
samplingDuration: TimeSpan.FromSeconds(5),
minimumThroughput: 2,
durationOfBreak: TimeSpan.FromSeconds(1));

// Create generic sync advanced circuit breaker
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy<HttpResponseMessage>
.Handle<SomeExceptionType>()
.AdvancedCircuitBreaker(
failureThreshold: 0.5d,
samplingDuration: TimeSpan.FromSeconds(5),
minimumThroughput: 2,
durationOfBreak: TimeSpan.FromSeconds(1));

// Create generic async advanced circuit breaker
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy<HttpResponseMessage>
.Handle<SomeExceptionType>()
.AdvancedCircuitBreakerAsync(
failureThreshold: 0.5d,
samplingDuration: TimeSpan.FromSeconds(5),
minimumThroughput: 2,
durationOfBreak: TimeSpan.FromSeconds(1));

// Check circuit state
ICircuitBreakerPolicy cbPolicy = (ICircuitBreakerPolicy)asyncPolicy;
bool isOpen = cbPolicy.CircuitState == CircuitState.Open || cbPolicy.CircuitState == CircuitState.Isolated;

// Manually control state
cbPolicy.Isolate(); // transitions into Isolated state
cbPolicy.Reset(); // transitions into Closed state
```
<!-- endSnippet -->

### Circuit breaker in v8

> [!IMPORTANT]
>
> Polly V8 does not support the standard ("classic") circuit breaker with consecutive failure counting.
peter-csala marked this conversation as resolved.
Show resolved Hide resolved
>
> In case of V8 you can define a Circuit Breaker strategy which works like the advanced circuit breaker in V7.

<!-- snippet: migration-circuit-breaker-v8 -->
```cs
// Create pipeline with circuit breaker. Because ResiliencePipeline supports both sync and async
// callbacks, there is no need to define it twice.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
FailureRatio = 0.5d,
SamplingDuration = TimeSpan.FromSeconds(5),
MinimumThroughput = 2,
BreakDuration = TimeSpan.FromSeconds(1)
})
.Build();

// Create a generic pipeline with circuit breaker. Because ResiliencePipeline<T> supports both sync and async
// callbacks, there is no need to define it twice.
ResiliencePipeline<HttpResponseMessage> pipelineT = new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddCircuitBreaker(new CircuitBreakerStrategyOptions<HttpResponseMessage>
{
ShouldHandle = new PredicateBuilder<HttpResponseMessage>().Handle<SomeExceptionType>(),
FailureRatio = 0.5d,
SamplingDuration = TimeSpan.FromSeconds(5),
MinimumThroughput = 2,
BreakDuration = TimeSpan.FromSeconds(1)
})
.Build();

// Check circuit state
CircuitBreakerStateProvider stateProvider = new();
// Manually control state
CircuitBreakerManualControl manualControl = new();

ResiliencePipeline pipelineState = new ResiliencePipelineBuilder()
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
FailureRatio = 0.5d,
SamplingDuration = TimeSpan.FromSeconds(5),
MinimumThroughput = 2,
BreakDuration = TimeSpan.FromSeconds(1),
StateProvider = stateProvider,
ManualControl = manualControl
})
.Build();

// Check circuit state
bool isOpen = stateProvider.CircuitState == CircuitState.Open || stateProvider.CircuitState == CircuitState.Isolated;

// Manually control state
await manualControl.IsolateAsync(); // transitions into Isolated state
await manualControl.CloseAsync(); // transitions into Closed state
```
<!-- endSnippet -->

> [!NOTE]
>
> On the [V7's wiki page](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker#reducing-thrown-exceptions-when-the-circuit-is-broken) you can find a tip how to reduce thrown exceptions. The described technique does **not** work with V8.
peter-csala marked this conversation as resolved.
Show resolved Hide resolved
>
> Under the [circuit breaker's anti-patterns](strategies/circuit-breaker.md#4---reducing-thrown-exceptions) you can find the suggested way for V8.

____

> [!IMPORTANT]
>
> Things to remember:
>
> - Use `AddCircuitBreaker` to add a circuit breaker strategy to your resiliency pipeline
> - Use the `CircuitBreakerStrategyOptions{<TResult>}` to customize your circuit breaker behavior to meet your requirements
>
> For further information please check out the [Circuit Breaker resilience strategy documentation](strategies/circuit-breaker.md).

## Migrating other policies

Migrating is a process similar to the ones described in the previous sections. Keep in mind that:
Expand Down
81 changes: 81 additions & 0 deletions docs/strategies/circuit-breaker.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,3 +535,84 @@ public Downstream1Client(
> The above sample code used the `AsAsyncPolicy<HttpResponseMessage>()` method to convert the `ResiliencePipeline<HttpResponseMessage>` to `IAsyncPolicy<HttpResponseMessage>`.
> It is required because the `AddPolicyHandler()` method anticipates an `IAsyncPolicy<HttpResponse>` parameter.
> Please be aware that, later an `AddResilienceHandler()` will be introduced in the `Microsoft.Extensions.Http.Resilience` package which is the successor of the `Microsoft.Extensions.Http.Polly`.

### 4 - Reducing thrown exceptions

In case of Circuit Breaker when it is either in `Open` or in `Isolated` state new requests are rejected immediately.
peter-csala marked this conversation as resolved.
Show resolved Hide resolved

That means the strategy will throw either a `BrokenCircuitException` or an `IsolatedCircuitException` respectively.

❌ DON'T

Use guard expression to call `Execute{Async}` only if the circuit is not broken:

<!-- snippet: circuit-breaker-anti-pattern-4 -->
```cs
var stateProvider = new CircuitBreakerStateProvider();
var circuitBreaker = new ResiliencePipelineBuilder()
.AddCircuitBreaker(new()
{
ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
BreakDuration = TimeSpan.FromSeconds(0.5),
StateProvider = stateProvider
})
.Build();

if (stateProvider.CircuitState
is not CircuitState.Open
and not CircuitState.Isolated)
{
var response = await circuitBreaker.ExecuteAsync(static async ct =>
{
return await IssueRequest();
}, CancellationToken.None);

// Your code goes here to process response
}
```
<!-- endSnippet -->

**Reasoning**:

- The problem with this approach is that the circuit breaker will never transition into `HalfOpen` state.
peter-csala marked this conversation as resolved.
Show resolved Hide resolved
- The circuit breaker does not act as an active object. In other words the state transition does not happen automatically in the background.
- The circuit transition into `HalfOpen` state when the `Execute{Async}` is called and the `BreakDuration` elapsed.
peter-csala marked this conversation as resolved.
Show resolved Hide resolved

✅ DO

Use `ExecuteOutcomeAsync` to avoid throwing exception:

<!-- snippet: circuit-breaker-pattern-4 -->
```cs
var context = ResilienceContextPool.Shared.Get();
var circuitBreaker = new ResiliencePipelineBuilder()
.AddCircuitBreaker(new()
{
ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
BreakDuration = TimeSpan.FromSeconds(0.5),
})
.Build();

Outcome<HttpResponseMessage> outcome = await circuitBreaker.ExecuteOutcomeAsync(static async (ctx, state) =>
{
var response = await IssueRequest();
return Outcome.FromResult(response);
}, context, "state");

ResilienceContextPool.Shared.Return(context);

if (outcome.Exception is BrokenCircuitException)
{
// the execution was stopped by CB
}
else
{
// Your code goes here to process response (outcome.Result)
}
```
<!-- endSnippet -->

**Reasoning**:

- The `ExecuteOutcomeAsync` is a low-allocation API which does not throw exception rather captures it inside the `Exception` property of the `Outcome` data structure.
peter-csala marked this conversation as resolved.
Show resolved Hide resolved
- Since your are calling one of the `Execute` methods that's why the circuit breaker can transition into `HalfOpen` state.
peter-csala marked this conversation as resolved.
Show resolved Hide resolved
64 changes: 64 additions & 0 deletions src/Snippets/Docs/CircuitBreaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,68 @@
await uriToCbMappings[downstream1Uri].ExecuteAsync(CallXYZOnDownstream1, CancellationToken.None);
#endregion
}

public static async ValueTask AntiPattern_4()
{
#region circuit-breaker-anti-pattern-4

var stateProvider = new CircuitBreakerStateProvider();
var circuitBreaker = new ResiliencePipelineBuilder()
.AddCircuitBreaker(new()
{
ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
BreakDuration = TimeSpan.FromSeconds(0.5),
StateProvider = stateProvider
})
.Build();

if (stateProvider.CircuitState
is not CircuitState.Open
and not CircuitState.Isolated)
{
var response = await circuitBreaker.ExecuteAsync(static async ct =>
{
return await IssueRequest();
}, CancellationToken.None);

// Your code goes here to process response
}

#endregion
}

private static ValueTask<HttpResponseMessage> IssueRequest() => ValueTask.FromResult(new HttpResponseMessage());

public static async ValueTask Pattern_4()
{
#region circuit-breaker-pattern-4

var context = ResilienceContextPool.Shared.Get();
var circuitBreaker = new ResiliencePipelineBuilder()
.AddCircuitBreaker(new()
{
ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
BreakDuration = TimeSpan.FromSeconds(0.5),
})
.Build();

Outcome<HttpResponseMessage> outcome = await circuitBreaker.ExecuteOutcomeAsync(static async (ctx, state) =>
{
var response = await IssueRequest();
return Outcome.FromResult(response);
}, context, "state");

ResilienceContextPool.Shared.Return(context);

if (outcome.Exception is BrokenCircuitException)

Check failure on line 270 in src/Snippets/Docs/CircuitBreaker.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Remove this conditional structure or edit its code blocks so that they're not all the same. (https://rules.sonarsource.com/csharp/RSPEC-3923)

Check failure on line 270 in src/Snippets/Docs/CircuitBreaker.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Remove this conditional structure or edit its code blocks so that they're not all the same. (https://rules.sonarsource.com/csharp/RSPEC-3923)
{
// the execution was stopped by CB
peter-csala marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
// Your code goes here to process response (outcome.Result)
peter-csala marked this conversation as resolved.
Show resolved Hide resolved
}

#endregion
}
}
Loading
Loading