Skip to content

Commit

Permalink
Merge branch 'App-vNext:main' into DurationGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
atawLee authored Oct 30, 2023
2 parents 39c4721 + 41d4e8d commit 5f7095d
Show file tree
Hide file tree
Showing 34 changed files with 608 additions and 484 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
]
},
"docfx": {
"version": "2.72.0",
"version": "2.72.1",
"commands": [
"docfx"
]
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ jobs:
publish-docs:
runs-on: ubuntu-latest

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

permissions:
contents: write
pages: write
Expand Down
324 changes: 174 additions & 150 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
```
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 11 (10.0.22621.2428/22H2/2022Update/SunValley2)
12th Gen Intel Core i7-1270P, 1 CPU, 16 logical and 12 physical cores
.NET SDK 7.0.403
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
Job=MediumRun Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=2 WarmupCount=10
```
| Method | Mean | Error | StdDev | Allocated |
|------------------------------- |---------:|---------:|---------:|----------:|
| CompositeComponent_ExecuteCore | 44.37 ns | 1.994 ns | 2.923 ns | - |
27 changes: 27 additions & 0 deletions bench/Polly.Core.Benchmarks/CompositeComponentBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Polly.Telemetry;
using Polly.Utils.Pipeline;

namespace Polly.Core.Benchmarks;

public class CompositeComponentBenchmark
{
private ResilienceContext? _context;
private PipelineComponent? _component;

[GlobalSetup]
public void Setup()
{
var first = PipelineComponent.Empty;
var second = PipelineComponent.Empty;
var source = new ResilienceTelemetrySource("pipeline", "instance", "strategy");
var telemetry = new ResilienceStrategyTelemetry(source, null);
var components = new[] { first, second };

_component = CompositeComponent.Create(components, telemetry, TimeProvider.System);
_context = ResilienceContextPool.Shared.Get();
}

[Benchmark]
public ValueTask<Outcome<int>> CompositeComponent_ExecuteCore()
=> _component!.ExecuteCore((_, state) => Outcome.FromResultAsValueTask(state), _context!, 42);
}
2 changes: 1 addition & 1 deletion bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private class TelemetryEventStrategy : ResilienceStrategy

public TelemetryEventStrategy(ResilienceStrategyTelemetry telemetry) => _telemetry = telemetry;

protected override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
protected internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Polly.Core.Benchmarks.Utils;

internal class EmptyResilienceStrategy : ResilienceStrategy
{
protected override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
protected internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
Expand Down
2 changes: 1 addition & 1 deletion bench/Polly.Core.Benchmarks/Utils/Helper.CircuitBreaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public static object CreateCircuitBreaker(PollyVersion technology)

private class OutcomeHandlingStrategy : ResilienceStrategy
{
protected override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
protected internal override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
Expand Down
6 changes: 3 additions & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.Build(); // Builds the resilience pipeline
// Execute the pipeline asynchronously
await pipeline.ExecuteAsync(static async cancellationToken => { /*Your custom logic here */ }, cancellationToken);
await pipeline.ExecuteAsync(static async token => { /*Your custom logic goes here */ }, cancellationToken);
```
<!-- endSnippet -->

Expand All @@ -46,7 +46,7 @@ services.AddResiliencePipeline("my-pipeline", builder =>
});

// Build the service provider
IServiceProvider serviceProvider = services.BuildServiceProvider();
var serviceProvider = services.BuildServiceProvider();

// Retrieve ResiliencePipelineProvider that caches and dynamically creates the resilience pipelines
var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<string>>();
Expand All @@ -57,7 +57,7 @@ ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline");
// Execute the pipeline
await pipeline.ExecuteAsync(static async token =>
{
// Your custom logic here
// Your custom logic goes here
});
```
<!-- endSnippet -->
18 changes: 9 additions & 9 deletions docs/migration-v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ IAsyncPolicy retryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(3, _ =>

IAsyncPolicy timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(3));

// Wrap the policies. The policies are executed in the following order (i.e. Last-In-First-Out):
// 1. Retry
// 2. Timeout
IAsyncPolicy wrappedPolicy = Policy.WrapAsync(timeoutPolicy, retryPolicy);
// Wrap the policies. The policies are executed in the following order:
// 1. Retry <== outer
// 2. Timeout <== inner
IAsyncPolicy wrappedPolicy = Policy.WrapAsync(retryPolicy, timeoutPolicy);
```
<!-- endSnippet -->

Expand All @@ -169,9 +169,9 @@ In v8, there's no need to use policy wrap explicitly. Instead, policy wrapping i

<!-- snippet: migration-policy-wrap-v8 -->
```cs
// The "PolicyWrap" is integrated directly. Strategies are executed in the same order as they were added (i.e. First-In-First-Out):
// 1. Retry
// 2. Timeout
// The "PolicyWrap" is integrated directly. The strategies are executed in the following order:
// 1. Retry <== outer
// 2. Timeout <== outer
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddRetry(new()
{
Expand All @@ -185,8 +185,8 @@ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
```
<!-- endSnippet -->

> [!IMPORTANT]
> In v7, the policy wrap ordering is different; the policy added first was executed last (FILO). In v8, the execution order matches the order in which they were added (FIFO). See [fallback after retries](strategies/fallback.md#fallback-after-retries) for an example on how the strategies are executed.
> [!NOTE]
> See [fallback after retries](strategies/fallback.md#fallback-after-retries) for an example on how the strategies are executed.
## Migrating retry policies

Expand Down
44 changes: 24 additions & 20 deletions docs/strategies/circuit-breaker.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,21 @@

<!-- snippet: circuit-breaker -->
```cs
// Add circuit breaker with default options.
// Circuit breaker with default options.
// See https://www.pollydocs.org/strategies/circuit-breaker#defaults for defaults.
new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions());
var optionsDefaults = new CircuitBreakerStrategyOptions();

// Add circuit breaker with customized options:
//
// Circuit breaker with customized options:
// The circuit will break if more than 50% of actions result in handled exceptions,
// within any 10-second sampling duration, and at least 8 actions are processed.
new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions
var optionsComplex = new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(10),
MinimumThroughput = 8,
BreakDuration = TimeSpan.FromSeconds(30),
ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>()
});
};

// Adds a circuit breaker with dynamic break duration:
//
Expand All @@ -53,20 +52,21 @@ new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOpti
});

// Handle specific failed results for HttpResponseMessage:
new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddCircuitBreaker(new CircuitBreakerStrategyOptions<HttpResponseMessage>
{
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<SomeExceptionType>()
.HandleResult(response => response.StatusCode == HttpStatusCode.InternalServerError)
});
var optionsShouldHandle = new CircuitBreakerStrategyOptions<HttpResponseMessage>
{
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<SomeExceptionType>()
.HandleResult(response => response.StatusCode == HttpStatusCode.InternalServerError)
};

// Monitor the circuit state, useful for health reporting:
var stateProvider = new CircuitBreakerStateProvider();
var optionsStateProvider = new CircuitBreakerStrategyOptions<HttpResponseMessage>
{
StateProvider = stateProvider
};

new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddCircuitBreaker(new() { StateProvider = stateProvider })
.Build();
var circuitState = stateProvider.CircuitState;

/*
CircuitState.Closed - Normal operation; actions are executed.
Expand All @@ -77,16 +77,20 @@ CircuitState.Isolated - Circuit is manually held open; actions are blocked.

// Manually control the Circuit Breaker state:
var manualControl = new CircuitBreakerManualControl();

new ResiliencePipelineBuilder()
.AddCircuitBreaker(new() { ManualControl = manualControl })
.Build();
var optionsManualControl = new CircuitBreakerStrategyOptions
{
ManualControl = manualControl
};

// Manually isolate a circuit, e.g., to isolate a downstream service.
await manualControl.IsolateAsync();

// Manually close the circuit to allow actions to be executed again.
await manualControl.CloseAsync();

// Add a circuit breaker strategy with a CircuitBreakerStrategyOptions{<TResult>} instance to the pipeline
new ResiliencePipelineBuilder().AddCircuitBreaker(optionsDefaults);
new ResiliencePipelineBuilder<HttpResponseMessage>().AddCircuitBreaker(optionsStateProvider);
```
<!-- endSnippet -->

Expand Down
72 changes: 36 additions & 36 deletions docs/strategies/fallback.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,48 @@

<!-- snippet: fallback -->
```cs
// Add a fallback/substitute value if an operation fails.
new ResiliencePipelineBuilder<UserAvatar>()
.AddFallback(new FallbackStrategyOptions<UserAvatar>
{
ShouldHandle = new PredicateBuilder<UserAvatar>()
.Handle<SomeExceptionType>()
.HandleResult(r => r is null),
FallbackAction = args => Outcome.FromResultAsValueTask(UserAvatar.Blank)
});
// A fallback/substitute value if an operation fails.
var optionsSubstitute = new FallbackStrategyOptions<UserAvatar>
{
ShouldHandle = new PredicateBuilder<UserAvatar>()
.Handle<SomeExceptionType>()
.HandleResult(r => r is null),
FallbackAction = static args => Outcome.FromResultAsValueTask(UserAvatar.Blank)
};

// Use a dynamically generated value if an operation fails.
new ResiliencePipelineBuilder<UserAvatar>()
.AddFallback(new FallbackStrategyOptions<UserAvatar>
var optionsFallbackAction = new FallbackStrategyOptions<UserAvatar>
{
ShouldHandle = new PredicateBuilder<UserAvatar>()
.Handle<SomeExceptionType>()
.HandleResult(r => r is null),
FallbackAction = static args =>
{
ShouldHandle = new PredicateBuilder<UserAvatar>()
.Handle<SomeExceptionType>()
.HandleResult(r => r is null),
FallbackAction = args =>
{
var avatar = UserAvatar.GetRandomAvatar();
return Outcome.FromResultAsValueTask(avatar);
}
});
var avatar = UserAvatar.GetRandomAvatar();
return Outcome.FromResultAsValueTask(avatar);
}
};

// Use a default or dynamically generated value, and execute an additional action if the fallback is triggered.
new ResiliencePipelineBuilder<UserAvatar>()
.AddFallback(new FallbackStrategyOptions<UserAvatar>
var optionsOnFallback = new FallbackStrategyOptions<UserAvatar>
{
ShouldHandle = new PredicateBuilder<UserAvatar>()
.Handle<SomeExceptionType>()
.HandleResult(r => r is null),
FallbackAction = static args =>
{
ShouldHandle = new PredicateBuilder<UserAvatar>()
.Handle<SomeExceptionType>()
.HandleResult(r => r is null),
FallbackAction = args =>
{
var avatar = UserAvatar.GetRandomAvatar();
return Outcome.FromResultAsValueTask(UserAvatar.Blank);
},
OnFallback = args =>
{
// Add extra logic to be executed when the fallback is triggered, such as logging.
return default; // Returns an empty ValueTask
}
});
var avatar = UserAvatar.GetRandomAvatar();
return Outcome.FromResultAsValueTask(UserAvatar.Blank);
},
OnFallback = static args =>
{
// Add extra logic to be executed when the fallback is triggered, such as logging.
return default; // Returns an empty ValueTask
}
};

// Add a fallback strategy with a FallbackStrategyOptions<TResult> instance to the pipeline
new ResiliencePipelineBuilder<UserAvatar>().AddFallback(optionsOnFallback);
```
<!-- endSnippet -->

Expand Down
Loading

0 comments on commit 5f7095d

Please sign in to comment.