Skip to content

Commit

Permalink
Pooling of CancellationTokenSources (#1116)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk authored Apr 13, 2023
1 parent 97c2307 commit 80f5791
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 10 deletions.
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

0 comments on commit 80f5791

Please sign in to comment.