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

Pooling of CancellationTokenSources #1116

Merged
merged 3 commits into from
Apr 13, 2023
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
12 changes: 6 additions & 6 deletions src/Polly.Core.Benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ LaunchCount=2 WarmupCount=10

## TIMEOUT

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------------------ |---------:|--------:|--------:|------:|--------:|-------:|----------:|------------:|
| ExecuteTimeout_V7 | 281.5 ns | 5.76 ns | 8.08 ns | 1.00 | 0.00 | 0.0868 | 728 B | 1.00 |
| ExecuteTimeout_V8 | 268.9 ns | 3.86 ns | 5.53 ns | 0.96 | 0.04 | 0.0257 | 216 B | 0.30 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------------------ |---------:|--------:|---------:|------:|--------:|-------:|----------:|------------:|
| ExecuteTimeout_V7 | 304.9 ns | 7.53 ns | 11.27 ns | 1.00 | 0.00 | 0.0868 | 728 B | 1.00 |
| ExecuteTimeout_V8 | 266.5 ns | 5.95 ns | 8.72 ns | 0.88 | 0.04 | - | - | 0.00 |

## RETRY

Expand All @@ -51,5 +51,5 @@ LaunchCount=2 WarmupCount=10

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------------------------- |---------:|----------:|----------:|------:|--------:|-------:|----------:|------------:|
| ExecuteStrategyPipeline_V7 | 1.321 us | 0.0355 us | 0.0520 us | 1.00 | 0.00 | 0.2861 | 2400 B | 1.00 |
| ExecuteStrategyPipeline_V8 | 1.126 us | 0.0193 us | 0.0283 us | 0.85 | 0.03 | 0.0763 | 640 B | 0.27 |
| ExecuteStrategyPipeline_V7 | 1.265 us | 0.0372 us | 0.0558 us | 1.00 | 0.00 | 0.2861 | 2400 B | 1.00 |
| ExecuteStrategyPipeline_V8 | 1.032 us | 0.0165 us | 0.0236 us | 0.82 | 0.04 | 0.0076 | 64 B | 0.03 |
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public void AddTimeout_InvalidOptions_Throws()
.Throw<ValidationException>().WithMessage("The timeout strategy options are invalid.*");
}

private static TimeSpan GetTimeout(TimeoutResilienceStrategy strategy) => strategy.GetTimeoutAsync(ResilienceContext.Get()).GetAwaiter().GetResult();
private static TimeSpan GetTimeout(TimeoutResilienceStrategy strategy) => strategy.GetTimeoutAsync(ResilienceContext.Get()).Preserve().GetAwaiter().GetResult();

private static void OnTimeout(TimeoutResilienceStrategy strategy)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task Execute_EnsureOnTimeoutCalled()
args.Exception.Should().BeAssignableTo<OperationCanceledException>();
args.Timeout.Should().Be(_delay);
args.Context.Should().NotBeNull();
args.Context.CancellationToken.IsCancellationRequested.Should().BeTrue();
args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse();
called = true;
});

Expand Down
33 changes: 33 additions & 0 deletions src/Polly.Core.Tests/Utils/CancellationTokenSourcePoolTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Polly.Utils;

namespace Polly.Core.Tests.Utils;

public class CancellationTokenSourcePoolTests
{
[Fact]
public void RentReturn_Reusable_EnsureProperBehavior()
{
var cts = CancellationTokenSourcePool.Get();
CancellationTokenSourcePool.Return(cts);

var cts2 = CancellationTokenSourcePool.Get();
#if NET6_0_OR_GREATER
cts2.Should().BeSameAs(cts);
#else
cts2.Should().NotBeSameAs(cts);
#endif
}

[Fact]
public void RentReturn_NotReusable_EnsureProperBehavior()
{
var cts = CancellationTokenSourcePool.Get();
cts.Cancel();
CancellationTokenSourcePool.Return(cts);

cts.Invoking(c => c.Token).Should().Throw<ObjectDisposedException>();

var cts2 = CancellationTokenSourcePool.Get();
cts2.Token.Should().NotBeNull();
}
}
7 changes: 5 additions & 2 deletions src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ protected internal override async ValueTask<TResult> ExecuteCoreAsync<TResult, T
}

var previousToken = context.CancellationToken;
using var cancellationSource = new CancellationTokenSource();
var cancellationSource = CancellationTokenSourcePool.Get();
_timeProvider.CancelAfter(cancellationSource, timeout);
context.CancellationToken = cancellationSource.Token;

Expand All @@ -56,6 +56,8 @@ protected internal override async ValueTask<TResult> ExecuteCoreAsync<TResult, T
}
catch (OperationCanceledException e) when (cancellationSource.IsCancellationRequested && !previousToken.IsCancellationRequested)
{
context.CancellationToken = previousToken;

_telemetry.Report(TimeoutConstants.OnTimeoutEvent, context);

if (OnTimeout != null)
Expand All @@ -73,10 +75,11 @@ protected internal override async ValueTask<TResult> ExecuteCoreAsync<TResult, T
finally
{
context.CancellationToken = previousToken;
CancellationTokenSourcePool.Return(cancellationSource);
}
}

internal async Task<TimeSpan> GetTimeoutAsync(ResilienceContext context)
internal async ValueTask<TimeSpan> GetTimeoutAsync(ResilienceContext context)
{
if (TimeoutGenerator == null)
{
Expand Down
31 changes: 31 additions & 0 deletions src/Polly.Core/Utils/CancellationTokenSourcePool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Polly.Utils
{
internal static class CancellationTokenSourcePool
{
#if NET6_0_OR_GREATER
private static readonly ObjectPool<CancellationTokenSource> Pool = new(
static () => new CancellationTokenSource(),
static cts => true);
#endif
public static CancellationTokenSource Get()
{
#if NET6_0_OR_GREATER
return Pool.Get();
#else
return new CancellationTokenSource();
#endif
}

public static void Return(CancellationTokenSource source)
{
#if NET6_0_OR_GREATER
if (source.TryReset())
{
Pool.Return(source);
return;
}
#endif
source.Dispose();
}
}
}
1 change: 1 addition & 0 deletions src/Polly.Core/Utils/TimeProviderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static Task DelayAsync(this TimeProvider timeProvider, TimeSpan delay, Re

if (context.IsSynchronous && timeProvider == TimeProvider.System)
{
// Stryker disable once boolean : no means to test this
if (context.CancellationToken.CanBeCanceled)
{
context.CancellationToken.WaitHandle.WaitOne(delay);
Expand Down