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

v7.2.3: Rate-limit policy and codebase maintenance #912

Merged
merged 21 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ UpgradeLog*.XML

artifacts
build
BenchmarkDotNet.Artifacts
tools

*.lock.json
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 7.3.0

- Add RateLimit policy - Thanks to [@reisenberger](https://github.com/reisenberger)
- Various codebase health updates - Thanks to [@SimonCropp](https://github.com/SimonCropp)
- Add benchmarks project - Thanks to [@martincostello](https://github.com/martincostello)
- Fix broken README image - Thanks to [@GitHubPang](https://github.com/GitHubPang)

## 7.2.2

- Recursively search all `AggregateException` inner exceptions for predicate matches when using `HandleInner()` ([#818](https://github.com/App-vNext/Polly/issues/818)) - Thanks to [@sideproject](https://github.com/sideproject)
Expand Down
2 changes: 1 addition & 1 deletion GitVersionConfig.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
next-version: 7.2.2
next-version: 7.3.0
296 changes: 179 additions & 117 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
os: Visual Studio 2019
os: Visual Studio 2022

# Build script
build_script:
Expand Down
4 changes: 2 additions & 2 deletions build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ var configuration = Argument<string>("configuration", "Release");
// EXTERNAL NUGET LIBRARIES
//////////////////////////////////////////////////////////////////////

#addin "Cake.FileHelpers"
#addin nuget:?package=Cake.Yaml
#addin nuget:?package=Cake.FileHelpers&version=3.3.0
#addin nuget:?package=Cake.Yaml&version=3.1.1
#addin nuget:?package=YamlDotNet&version=5.2.1

///////////////////////////////////////////////////////////////////////////////
Expand Down
2 changes: 1 addition & 1 deletion build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ if(-Not $SkipToolPackageRestore.IsPresent)
# Install just Cake if missing config
else
{
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install Cake -Version 0.25.0 -ExcludeVersion" # Pin Cake version to 0.25.0; see https://github.com/App-vNext/Polly/issues/416
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install Cake -Version 0.38.5 -ExcludeVersion" # Pin Cake version to 0.38.5; see https://github.com/App-vNext/Polly/issues/416
Write-Verbose ($NuGetOutput | Out-String)
}
Pop-Location
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\Polly.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>
49 changes: 49 additions & 0 deletions src/Polly.Benchmarks/Bulkhead.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;

namespace Polly.Benchmarks
{
[Config(typeof(PollyConfig))]
public class Bulkhead
{
private static readonly Policy SyncPolicy = Policy.Bulkhead(2);
private static readonly AsyncPolicy AsyncPolicy = Policy.BulkheadAsync(2);

[Benchmark]
public void Bulkhead_Synchronous()
{
SyncPolicy.Execute(() => Workloads.Action());
}

[Benchmark]
public async Task Bulkhead_Asynchronous()
{
await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync());
}

[Benchmark]
public async Task Bulkhead_Asynchronous_With_CancellationToken()
{
await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None);
}

[Benchmark]
public int Bulkhead_Synchronous_With_Result()
{
return SyncPolicy.Execute(() => Workloads.Func<int>());
}

[Benchmark]
public async Task<int> Bulkhead_Asynchronous_With_Result()
{
return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync<int>());
}

[Benchmark]
public async Task<int> Bulkhead_Asynchronous_With_Result_With_CancellationToken()
{
return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync<int>(token), CancellationToken.None);
}
}
}
111 changes: 111 additions & 0 deletions src/Polly.Benchmarks/Cache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.Caching.Memory;
using Polly.Caching;

namespace Polly.Benchmarks
{
[Config(typeof(PollyConfig))]
public class Cache
{
private static readonly MemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions());
private static readonly MemoryCacheProvider CacheProvider = new MemoryCacheProvider(MemoryCache);

private static readonly Policy SyncPolicyMiss = Policy.Cache(CacheProvider, TimeSpan.Zero);
private static readonly AsyncPolicy AsyncPolicyMiss = Policy.CacheAsync(CacheProvider, TimeSpan.Zero);

private static readonly Policy SyncPolicyHit = Policy.Cache(CacheProvider, TimeSpan.MaxValue);
private static readonly AsyncPolicy AsyncPolicyHit = Policy.CacheAsync(CacheProvider, TimeSpan.MaxValue);

private static readonly Context HitContext = new Context(nameof(HitContext));
private static readonly Context MissContext = new Context(nameof(MissContext));

[GlobalSetup]
public async Task GlobalSetup()
{
SyncPolicyHit.Execute((context) => GetObject(), HitContext);
await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None);
}

[Benchmark]
public object Cache_Synchronous_Hit()
{
return SyncPolicyHit.Execute((context) => GetObject(), HitContext);
}

[Benchmark]
public async Task<object> Cache_Asynchronous_Hit()
{
return await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None);
}

[Benchmark]
public object Cache_Synchronous_Miss()
{
return SyncPolicyMiss.Execute((context) => GetObject(), MissContext);
}

[Benchmark]
public async Task<object> Cache_Asynchronous_Miss()
{
return await AsyncPolicyMiss.ExecuteAsync((context, token) => GetObjectAsync(token), MissContext, CancellationToken.None);
}

private static object GetObject() => new object();

private static Task<object> GetObjectAsync(CancellationToken cancellationToken) => Task.FromResult(new object());

private sealed class MemoryCacheProvider : ISyncCacheProvider, IAsyncCacheProvider
{
private readonly IMemoryCache _cache;

public MemoryCacheProvider(IMemoryCache memoryCache)
{
_cache = memoryCache;
}

public (bool, object) TryGet(string key)
{
bool cacheHit = _cache.TryGetValue(key, out var value);
return (cacheHit, value);
}

public void Put(string key, object value, Ttl ttl)
{
TimeSpan remaining = DateTimeOffset.MaxValue - DateTimeOffset.UtcNow;
var options = new MemoryCacheEntryOptions();

if (ttl.SlidingExpiration)
{
options.SlidingExpiration = ttl.Timespan < remaining ? ttl.Timespan : remaining;
}
else
{
if (ttl.Timespan == TimeSpan.MaxValue)
{
options.AbsoluteExpiration = DateTimeOffset.MaxValue;
}
else
{
options.AbsoluteExpirationRelativeToNow = ttl.Timespan < remaining ? ttl.Timespan : remaining;
}
}

_cache.Set(key, value, options);
}

public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext)
{
return Task.FromResult(TryGet(key));
}

public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext)
{
Put(key, value, ttl);
return Task.CompletedTask;
}
}
}
}
38 changes: 38 additions & 0 deletions src/Polly.Benchmarks/CircuitBreaker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;

namespace Polly.Benchmarks
{
[Config(typeof(PollyConfig))]
public class CircuitBreaker
{
private static readonly Policy SyncPolicy = Policy.Handle<InvalidOperationException>().CircuitBreaker(2, TimeSpan.FromMinutes(1));
private static readonly AsyncPolicy AsyncPolicy = Policy.Handle<InvalidOperationException>().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1));

[Benchmark]
public void CircuitBreaker_Synchronous_Succeeds()
{
SyncPolicy.Execute(() => Workloads.Action());
}

[Benchmark]
public async Task CircuitBreaker_Asynchronous_Succeeds()
{
await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None);
}

[Benchmark]
public int CircuitBreaker_Synchronous_With_Result_Succeeds()
{
return SyncPolicy.Execute(() => Workloads.Func<int>());
}

[Benchmark]
public async Task<int> CircuitBreaker_Asynchronous_With_Result_Succeeds()
{
return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync<int>(token), CancellationToken.None);
}
}
}
37 changes: 37 additions & 0 deletions src/Polly.Benchmarks/Fallback.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;

namespace Polly.Benchmarks
{
[Config(typeof(PollyConfig))]
public class Fallback
{
private static readonly Policy<int> SyncPolicy = Policy<int>.Handle<InvalidOperationException>().Fallback(0);
private static readonly AsyncPolicy<int> AsyncPolicy = Policy<int>.Handle<InvalidOperationException>().FallbackAsync(0);

[Benchmark]
public int Fallback_Synchronous_Succeeds()
{
return SyncPolicy.Execute(() => Workloads.Func<int>());
}

[Benchmark]
public async Task<int> Fallback_Asynchronous_Succeeds()
{
return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync<int>());
}

[Benchmark]
public int Fallback_Synchronous_Throws()
{
return SyncPolicy.Execute(() => Workloads.FuncThrows<int, InvalidOperationException>());
}

[Benchmark]
public async Task<int> Fallback_Asynchronous_Throws()
{
return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncThrowsAsync<int, InvalidOperationException>());
}
}
}
37 changes: 37 additions & 0 deletions src/Polly.Benchmarks/NoOp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;

namespace Polly.Benchmarks
{
[Config(typeof(PollyConfig))]
public class NoOp
{
private static readonly Policy SyncPolicy = Policy.NoOp();
private static readonly AsyncPolicy AsyncPolicy = Policy.NoOpAsync();

[Benchmark]
public void NoOp_Synchronous()
{
SyncPolicy.Execute(() => Workloads.Action());
}

[Benchmark]
public async Task NoOp_Asynchronous()
{
await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None);
}

[Benchmark]
public int NoOp_Synchronous_With_Result()
{
return SyncPolicy.Execute(() => Workloads.Func<int>());
}

[Benchmark]
public async Task<int> NoOp_Asynchronous_With_Result()
{
return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync<int>(token), CancellationToken.None);
}
}
}
47 changes: 47 additions & 0 deletions src/Polly.Benchmarks/PolicyWrap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;

namespace Polly.Benchmarks
{
[Config(typeof(PollyConfig))]
public class PolicyWrap
{
private static readonly Policy SyncPolicy = Policy.Wrap(
Policy.Handle<InvalidOperationException>().Retry(),
Policy.Handle<InvalidOperationException>().CircuitBreaker(2, TimeSpan.FromMinutes(1)),
Policy.Timeout(TimeSpan.FromMilliseconds(10)),
Policy.Bulkhead(2));

private static readonly AsyncPolicy AsyncPolicy = Policy.WrapAsync(
Policy.Handle<InvalidOperationException>().RetryAsync(),
Policy.Handle<InvalidOperationException>().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)),
Policy.TimeoutAsync(TimeSpan.FromMilliseconds(10)),
Policy.BulkheadAsync(2));

[Benchmark]
public void PolicyWrap_Synchronous()
{
SyncPolicy.Execute(() => Workloads.Action());
}

[Benchmark]
public async Task PolicyWrap_Asynchronous()
{
await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None);
}

[Benchmark]
public int PolicyWrap_Synchronous_With_Result()
{
return SyncPolicy.Execute(() => Workloads.Func<int>());
}

[Benchmark]
public async Task<int> PolicyWrap_Asynchronous_With_Result()
{
return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync<int>(token), CancellationToken.None);
}
}
}
15 changes: 15 additions & 0 deletions src/Polly.Benchmarks/Polly.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
</ItemGroup>
<ItemGroup Condition=" '$(BenchmarkFromNuGet)' != 'True' ">
<ProjectReference Include="..\Polly\Polly.csproj" />
</ItemGroup>
</Project>
Loading