Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
## 8.0.0-alpha.1

- The first public preview of [Polly v8](https://github.com/App-vNext/Polly/issues/1048) with our [new high-performance core API](https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md) and extensions - Thanks to:
- The first public preview of [Polly v8](https://github.com/App-vNext/Polly/issues/1048) with our [new high-performance core API](https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md) and extensions. Feel free to check out the [samples](samples/) to see the new and improved Polly V8 in action.
- The first release of the new NuGet packages:
- [`Polly.Core`](https://nuget.org/packages/Polly.Core) - This package contains the new Polly V8 API.
- [`Polly.Extensions`](https://nuget.org/packages/Polly.Extensions) - This package is designed to integrate Polly with dependency injection and enable telemetry.
- [`Polly.RateLimiting`](https://nuget.org/packages/Polly.RateLimiting) - This package provides an integration between Polly and [`System.Threading.RateLimiting`](https://www.nuget.org/packages/System.Threading.RateLimiting/).

Thanks to:
- [@adamnova](https://github.com/adamnova)
- [@andrey-noskov](https://github.com/andrey-noskov)
- [@joelhulen](https://github.com/joelhulen)
Expand Down
5 changes: 5 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<PollyVersion>8.0.0-alpha.1</PollyVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="coverlet.msbuild" Version="6.0.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.5" />
<PackageVersion Include="FluentAssertions" Version="6.11.0" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.2" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="1.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
Expand All @@ -17,6 +19,9 @@
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="MinVer" Version="4.3.0" />
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="Polly" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.Core" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.Extensions" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageVersion Include="ReportGenerator" Version="5.1.22" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="9.3.0.71466" />
Expand Down
3 changes: 3 additions & 0 deletions samples/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "Samples.sln"
}
16 changes: 16 additions & 0 deletions samples/DependencyInjection/DependencyInjection.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Polly.Extensions" />
</ItemGroup>

</Project>
53 changes: 53 additions & 0 deletions samples/DependencyInjection/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Registry;
using Polly.Timeout;

// ------------------------------------------------------------------------
// 1. Register your resilience strategy
// ------------------------------------------------------------------------

var serviceProvider = new ServiceCollection()
.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug))
// Use "AddResilienceStrategy" extension method to configure your named strategy
.AddResilienceStrategy("my-strategy", (builder, context) =>
{
// You can resolve any service from DI when building the strategy
context.ServiceProvider.GetRequiredService<ILoggerFactory>();

builder.AddTimeout(TimeSpan.FromSeconds(1));
})
// You can also register result-based (generic) resilience strategies
// First generic parameter is the key type, the second one is the result type
// This overload does not use the context argument (simple scenarios)
.AddResilienceStrategy<string, HttpResponseMessage>("my-strategy", builder =>
{
builder.AddTimeout(TimeSpan.FromSeconds(1));
})
.BuildServiceProvider();

// ------------------------------------------------------------------------
// 2. Retrieve and use your resilience strategy
// ------------------------------------------------------------------------

// Resolve the resilience strategy provider for string-based keys
ResilienceStrategyProvider<string> strategyProvider = serviceProvider.GetRequiredService<ResilienceStrategyProvider<string>>();

// Retrieve the strategy by name
ResilienceStrategy strategy = strategyProvider.Get("my-strategy");

// Retrieve the generic strategy by name
ResilienceStrategy<HttpResponseMessage> genericStrategy = strategyProvider.Get<HttpResponseMessage>("my-strategy");

try
{
// Execute the strategy
// Notice in console output that telemetry is automatically enabled
await strategy.ExecuteAsync(async token => await Task.Delay(10000, token), CancellationToken.None);
}
catch (TimeoutRejectedException)
{
// The timeout strategy cancels the user callback and throws this exception
Console.WriteLine("Timeout!");
}
8 changes: 8 additions & 0 deletions samples/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<RunAnalyzers>true</RunAnalyzers>
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>
</Project>
3 changes: 3 additions & 0 deletions samples/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<!-- Intentionally empty to not include Directory.Build.targets in the root. -->
</Project>
14 changes: 14 additions & 0 deletions samples/Extensibility/Extensibility.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Polly.Core" />
</ItemGroup>

</Project>
135 changes: 135 additions & 0 deletions samples/Extensibility/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using Polly;
using Polly.Telemetry;

// ------------------------------------------------------------------------
// Usage of custom strategy
// ------------------------------------------------------------------------
var strategy = new ResilienceStrategyBuilder()
// This is custom extension defined in this sample
.AddMyResilienceStrategy(new MyResilienceStrategyOptions
{
OnCustomEvent = args =>
{
Console.WriteLine("OnCustomEvent");
return default;
}
})
.Build();

// Execute the strategy
strategy.Execute(() => { });

// ------------------------------------------------------------------------
// SIMPLE EXTENSIBILITY MODEL (INLINE STRATEGY)
// ------------------------------------------------------------------------

strategy = new ResilienceStrategyBuilder()
// Just add the strategy instance directly
.AddStrategy(new MySimpleStrategy())
.Build();

// Execute the strategy
strategy.Execute(() => { });

internal class MySimpleStrategy : ResilienceStrategy
{
protected override ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, TState>(Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback, ResilienceContext context, TState state)
{
Console.WriteLine("MySimpleStrategy executing!");

// The "context" holds information about execution mode
Console.WriteLine("context.IsSynchronous: {0}", context.IsSynchronous);
Console.WriteLine("context.ResultType: {0}", context.ResultType);
Console.WriteLine("context.IsVoid: {0}", context.IsVoid);

// The "state" is ambient value passed by the caller that holds his state.
// Here, we do not do anything with it, just pass it to the callback.

// Execute the provided callback
return callback(context, state);
}
}

// ------------------------------------------------------------------------
// STANDARD EXTENSIBILITY MODEL
// ------------------------------------------------------------------------

// ------------------------------------------------------------------------
// 1. Create options for your custom strategy
// ------------------------------------------------------------------------

// 1.A Define arguments for events that your strategy uses (optional)
public readonly record struct OnCustomEventArguments(ResilienceContext Context);

// 1.B Define the options.
public class MyResilienceStrategyOptions : ResilienceStrategyOptions
{
public override string StrategyType => "MyCustomStrategy";

// Use the arguments in the delegates.
// The recommendation is to use asynchronous delegates.
public Func<OnCustomEventArguments, ValueTask>? OnCustomEvent { get; set; }
}

// ------------------------------------------------------------------------
// 2. Create a custom resilience strategy that derives from ResilienceStrategy
// ------------------------------------------------------------------------

// The strategy should be internal and not exposed as part of public API. Instead, expose options and extensions for resilience strategy builder.
internal class MyResilienceStrategy : ResilienceStrategy
{
private readonly ResilienceStrategyTelemetry telemetry;
private readonly Func<OnCustomEventArguments, ValueTask>? onCustomEvent;

public MyResilienceStrategy(ResilienceStrategyTelemetry telemetry, MyResilienceStrategyOptions options)
{
this.telemetry = telemetry;
this.onCustomEvent = options.OnCustomEvent;
}

protected override async ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, TState>(Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback, ResilienceContext context, TState state)
{
// Here, do something before callback execution
// ...

// Execute the provided callback
var outcome = await callback(context, state);

// Here, do something after callback execution
// ...

// You can then report important telemetry events
telemetry.Report("MyCustomEvent", context, new OnCustomEventArguments(context));

// Call the delegate if provided by the user
if (onCustomEvent is not null)
{
await onCustomEvent(new OnCustomEventArguments(context));
}

return outcome;
}
}

// ------------------------------------------------------------------------
// 3. Expose new extensions for ResilienceStrategyBuilder
// ------------------------------------------------------------------------

public static class MyResilienceStrategyExtensions
{
// Add new extension that works for both "ResilienceStrategyBuilder" and "ResilienceStrategyBuilder<T>"
public static TBuilder AddMyResilienceStrategy<TBuilder>(this TBuilder builder, MyResilienceStrategyOptions options)
where TBuilder : ResilienceStrategyBuilderBase
{
builder.AddStrategy(
// Provide a factory that creates the strategy
context => new MyResilienceStrategy(context.Telemetry, options),

// Pass the options, note that the options instance is automatically validated by the builder
options);

return builder;
}
}


14 changes: 14 additions & 0 deletions samples/GenericStrategies/GenericStrategies.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Polly.Core" />
</ItemGroup>

</Project>
74 changes: 74 additions & 0 deletions samples/GenericStrategies/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Polly;
using Polly.Fallback;
using Polly.Retry;
using Polly.Timeout;
using System.Net;

// ----------------------------------------------------------------------------
// Create a generic resilience strategy using ResilienceStrategyBuilder<T>
// ----------------------------------------------------------------------------

// The generic ResilienceStrategyBuilder<T> creates a ResilienceStrategy<T>
// that can execute synchronous and asynchronous callbacks that return T.

ResilienceStrategy<HttpResponseMessage> strategy = new ResilienceStrategyBuilder<HttpResponseMessage>()
.AddFallback(new FallbackStrategyOptions<HttpResponseMessage>
{
FallbackAction = async _ =>
{
await Task.Yield();

// return fallback result
return new Outcome<HttpResponseMessage>(new HttpResponseMessage(System.Net.HttpStatusCode.OK));
},
// You can also use switch expressions for succinct syntax
ShouldHandle = outcome => outcome switch
{
{ Exception: HttpRequestException } => PredicateResult.True,
{ Result: HttpResponseMessage response } when response.StatusCode == HttpStatusCode.InternalServerError => PredicateResult.True,
_ => PredicateResult.False
},
OnFallback = _ => { Console.WriteLine("Fallback!"); return default; }
})
.AddRetry(new RetryStrategyOptions<HttpResponseMessage>
{
ShouldRetry = outcome =>
{
// We can handle specific result
if (outcome.Result?.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{
// The "PredicateResult.True" is shorthand to "new ValueTask<bool>(true)"
return PredicateResult.True;
}

// Or exception
if ( outcome.Exception is HttpRequestException)
{
return PredicateResult.True;
}

return PredicateResult.False;
},
// Register user callback called whenever retry occurs
OnRetry = outcome => { Console.WriteLine($"Retrying '{outcome.Result?.StatusCode}'..."); return default; },
BaseDelay = TimeSpan.FromMilliseconds(400),
BackoffType = RetryBackoffType.Constant,
RetryCount = 3
})
.AddTimeout(new TimeoutStrategyOptions
{
Timeout = TimeSpan.FromMilliseconds(500),
// Register user callback called whenever timeout occurs
OnTimeout = _ => { Console.WriteLine("Timeout occurred!"); return default; }
})
.Build();

var response = await strategy.ExecuteAsync(
async token =>
{
await Task.Yield();
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
},
CancellationToken.None);

Console.WriteLine($"Response: {response.StatusCode}");
14 changes: 14 additions & 0 deletions samples/Intro/Intro.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Polly.Core"/>
</ItemGroup>

</Project>
Loading