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

Use Polly resilience pipelines #1270

Merged
merged 1 commit into from
Nov 1, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Use Polly resilience pipelines
Update the Polly samples to use the new Polly v8 APIs.
martincostello committed Nov 1, 2023
commit c4f1635ecc5553b9fd88341c95c3f9b29d6d6937
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@
<PackageVersion Include="MinVer" Version="4.3.0" />
<PackageVersion Include="morelinq" Version="4.0.0" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="Polly" Version="8.1.0" />
<PackageVersion Include="Polly.Core" Version="8.1.0" />
<PackageVersion Include="ReportGenerator" Version="5.1.26" />
<PackageVersion Include="Serilog" Version="3.0.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="7.0.0" />
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Polly" />
<PackageReference Include="Polly.Core" />
<PackageReference Include="Serilog.AspNetCore" />
</ItemGroup>

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using JustSaying.Messaging.Middleware;
using JustSaying.Messaging.Middleware;
using JustSaying.Sample.Middleware.Exceptions;
using Polly;
using Polly.Retry;
using Serilog;

namespace JustSaying.Sample.Middleware.Middlewares;
@@ -10,11 +11,11 @@ namespace JustSaying.Sample.Middleware.Middlewares;
/// </summary>
public class PollyJustSayingMiddleware : MiddlewareBase<HandleMessageContext, bool>
{
private readonly AsyncPolicy _policy;
private readonly ResiliencePipeline _pipeline;

public PollyJustSayingMiddleware()
{
_policy = CreateMessageRetryPolicy();
_pipeline = CreateResiliencePipeline();
}

protected override async Task<bool> RunInnerAsync(HandleMessageContext context, Func<CancellationToken, Task<bool>> func, CancellationToken stoppingToken)
@@ -23,18 +24,41 @@ protected override async Task<bool> RunInnerAsync(HandleMessageContext context,

try
{
return await _policy.ExecuteAsync(func, stoppingToken);
var pool = ResilienceContextPool.Shared;
var resilienceContext = pool.Get(stoppingToken);

try
{
return await _pipeline.ExecuteAsync(
static async (context, func) =>
await func(context.CancellationToken), resilienceContext, func);
}
finally
{
pool.Return(resilienceContext);
}
}
finally
{
Log.Information("[{MiddlewareName}] Finished {MessageType}", nameof(PollyJustSayingMiddleware), context.Message.GetType().Name);
}
}

private static AsyncPolicy CreateMessageRetryPolicy()
private static ResiliencePipeline CreateResiliencePipeline()
{
return Policy.Handle<BusinessException>()
.WaitAndRetryAsync(3, count => TimeSpan.FromMilliseconds(Math.Max(count * 100, 1000)),
onRetry: (e, ts, retryCount, ctx) => Log.Information(e, "Retrying failed operation on count {RetryCount}", retryCount));
return new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<BusinessException>(),
Delay = TimeSpan.FromSeconds(1),
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
OnRetry = (args) =>
{
Log.Information("Retrying failed operation on count {RetryCount}", args.AttemptNumber);
return ValueTask.CompletedTask;
}
})
.Build();
}
}
4 changes: 2 additions & 2 deletions src/JustSaying.Tools/JustSaying.Tools.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<TargetFramework>net471</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
2 changes: 1 addition & 1 deletion tests/JustSaying.UnitTests/JustSaying.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
<PackageReference Include="MELT" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Polly" />
<PackageReference Include="Polly.Core" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
23 changes: 13 additions & 10 deletions tests/JustSaying.UnitTests/Messaging/Policies/CanCreatePolicy.cs
Original file line number Diff line number Diff line change
@@ -45,27 +45,30 @@ public async Task MiddlewareBuilder_Error_Handler_Async()
[Fact]
public async Task Can_Integrate_With_Polly_Policies_Async()
{
var pollyPolicy = Policy
.Handle<CustomException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromSeconds(1));
var pipeline = new ResiliencePipelineBuilder()
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<CustomException>(),
MinimumThroughput = 2,
BreakDuration = TimeSpan.FromSeconds(1),
})
.Build();

var policy = MiddlewareBuilder.BuildAsync(
new PollyMiddleware<string, int>(pollyPolicy));
var middleware = MiddlewareBuilder.BuildAsync(
new PollyMiddleware<string, int>(pipeline));

var calledCount = 0;
await Assert.ThrowsAsync<CustomException>(() => policy.RunAsync("context", ct =>
await Assert.ThrowsAsync<CustomException>(() => middleware.RunAsync("context", ct =>
{
calledCount++;
throw new CustomException();
}, CancellationToken.None));
await Assert.ThrowsAsync<CustomException>(() => policy.RunAsync("context", ct =>
await Assert.ThrowsAsync<CustomException>(() => middleware.RunAsync("context", ct =>
{
calledCount++;
throw new CustomException();
}, CancellationToken.None));
await Assert.ThrowsAsync<BrokenCircuitException>(() => policy.RunAsync("context", ct =>
await Assert.ThrowsAsync<BrokenCircuitException>(() => middleware.RunAsync("context", ct =>
{
calledCount++;
throw new CustomException();
Original file line number Diff line number Diff line change
@@ -5,18 +5,30 @@ namespace JustSaying.UnitTests.Messaging.Policies.ExamplePolicies;

public class PollyMiddleware<TContext, TOut> : MiddlewareBase<TContext, TOut>
{
private readonly IAsyncPolicy _policy;
private readonly ResiliencePipeline _pipeline;

public PollyMiddleware(IAsyncPolicy policy)
public PollyMiddleware(ResiliencePipeline pipeline)
{
_policy = policy;
_pipeline = pipeline;
}

protected override async Task<TOut> RunInnerAsync(
TContext context,
Func<CancellationToken, Task<TOut>> func,
CancellationToken stoppingToken)
{
return await _policy.ExecuteAsync(() => func(stoppingToken));
var pool = ResilienceContextPool.Shared;
var resilienceContext = pool.Get(stoppingToken);

try
{
return await _pipeline.ExecuteAsync(
static async (context, func) =>
await func(context.CancellationToken), resilienceContext, func);
}
finally
{
pool.Return(resilienceContext);
}
}
}
}