Skip to content

Commit

Permalink
Introduce ResilienceContextPool (ApiReview) (#1421)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk committed Jul 21, 2023
1 parent 02a1944 commit 7f6b8a4
Show file tree
Hide file tree
Showing 64 changed files with 387 additions and 334 deletions.
4 changes: 2 additions & 2 deletions bench/Polly.Core.Benchmarks/MultipleStrategiesBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ public void Setup()
[Benchmark]
public async ValueTask ExecuteStrategyPipeline_NonGeneric_V8()
{
var context = ResilienceContext.Get();
var context = ResilienceContextPool.Shared.Get();

await _nonGeneric!.ExecuteOutcomeAsync(
static (_, _) => new ValueTask<Outcome<string>>(Outcome.FromResult("dummy")),
context,
string.Empty).ConfigureAwait(false);

ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
}
}
2 changes: 1 addition & 1 deletion bench/Polly.Core.Benchmarks/PredicateBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Polly.Core.Benchmarks;
public class PredicateBenchmark
{
private readonly OutcomeArguments<HttpResponseMessage, RetryPredicateArguments> _args = new(
ResilienceContext.Get(),
ResilienceContextPool.Shared.Get(),
Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)),
new RetryPredicateArguments(0));

Expand Down
12 changes: 6 additions & 6 deletions bench/Polly.Core.Benchmarks/ResilienceStrategyBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ public class ResilienceStrategyBenchmark
[Benchmark(Baseline = true)]
public async ValueTask ExecuteOutcomeAsync()
{
var context = ResilienceContext.Get();
var context = ResilienceContextPool.Shared.Get();
await NullResilienceStrategy.Instance.ExecuteOutcomeAsync((_, _) => Outcome.FromResultAsTask("dummy"), context, "state").ConfigureAwait(false);
ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
}

[Benchmark]
public async ValueTask ExecuteAsync_ResilienceContextAndState()
{
var context = ResilienceContext.Get();
var context = ResilienceContextPool.Shared.Get();
await NullResilienceStrategy.Instance.ExecuteAsync((_, _) => new ValueTask<string>("dummy"), context, "state").ConfigureAwait(false);
ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
}

[Benchmark]
Expand All @@ -37,9 +37,9 @@ public async ValueTask ExecuteAsync_GenericStrategy_CancellationToken()
[Benchmark]
public void Execute_ResilienceContextAndState()
{
var context = ResilienceContext.Get();
var context = ResilienceContextPool.Shared.Get();
NullResilienceStrategy.Instance.Execute((_, _) => "dummy", context, "state");
ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
}

[Benchmark]
Expand Down
4 changes: 2 additions & 2 deletions bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ public void Prepare()
[Benchmark]
public async ValueTask Execute()
{
var context = ResilienceContext.Get();
var context = ResilienceContextPool.Shared.Get();
await _strategy!.ExecuteOutcomeAsync((_, _) => Outcome.FromResultAsTask("dummy"), context, "state").ConfigureAwait(false);
ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
}

private ResilienceStrategy Build(ResilienceStrategyBuilder builder)
Expand Down
4 changes: 2 additions & 2 deletions bench/Polly.Core.Benchmarks/Utils/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ public static async ValueTask ExecuteAsync(this object obj, PollyVersion version
await ((IAsyncPolicy<string>)obj).ExecuteAsync(static _ => Task.FromResult("dummy"), CancellationToken.None).ConfigureAwait(false);
return;
case PollyVersion.V8:
var context = ResilienceContext.Get();
var context = ResilienceContextPool.Shared.Get();

await ((ResilienceStrategy<string>)obj).ExecuteOutcomeAsync(
static (_, _) => Outcome.FromResultAsTask("dummy"),
context,
string.Empty).ConfigureAwait(false);

ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
return;
}

Expand Down
10 changes: 5 additions & 5 deletions src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal void Initialize(Func<ResilienceContext, Task> onIsolate, Func<Resilienc

if (_isolated)
{
var context = ResilienceContext.Get().Initialize<VoidResult>(isSynchronous: true);
var context = ResilienceContextPool.Shared.Get().Initialize<VoidResult>(isSynchronous: true);

// if the control indicates that circuit breaker should be isolated, we isolate it right away
IsolateAsync(context).GetAwaiter().GetResult();
Expand Down Expand Up @@ -57,15 +57,15 @@ internal async Task IsolateAsync(ResilienceContext context)
/// <exception cref="ObjectDisposedException">Thrown when calling this method after this object is disposed.</exception>
public async Task IsolateAsync(CancellationToken cancellationToken = default)
{
var context = ResilienceContext.Get(cancellationToken).Initialize<VoidResult>(isSynchronous: false);
var context = ResilienceContextPool.Shared.Get(cancellationToken).Initialize<VoidResult>(isSynchronous: false);

try
{
await IsolateAsync(context).ConfigureAwait(false);
}
finally
{
ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
}
}

Expand Down Expand Up @@ -98,15 +98,15 @@ internal async Task CloseAsync(ResilienceContext context)
/// <exception cref="ObjectDisposedException">Thrown when calling this method after this object is disposed.</exception>
public async Task CloseAsync(CancellationToken cancellationToken = default)
{
var context = ResilienceContext.Get(cancellationToken);
var context = ResilienceContextPool.Shared.Get(cancellationToken);

try
{
await CloseAsync(context).ConfigureAwait(false);
}
finally
{
ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Polly.Core/Hedging/Controller/TaskExecution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Polly.Hedging.Controller;
/// </summary>
internal sealed class TaskExecution<T>
{
private readonly ResilienceContext _cachedContext = ResilienceContext.Get();
private readonly ResilienceContext _cachedContext = ResilienceContextPool.Shared.Get();
private readonly CancellationTokenSourcePool _cancellationTokenSourcePool;
private readonly TimeProvider _timeProvider;
private readonly ResilienceStrategyTelemetry _telemetry;
Expand Down
9 changes: 6 additions & 3 deletions src/Polly.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#nullable enable
abstract Polly.Registry.ResilienceStrategyProvider<TKey>.TryGetStrategy(TKey key, out Polly.ResilienceStrategy? strategy) -> bool
abstract Polly.Registry.ResilienceStrategyProvider<TKey>.TryGetStrategy<TResult>(TKey key, out Polly.ResilienceStrategy<TResult>? strategy) -> bool
abstract Polly.ResilienceContextPool.Get(string? operationKey, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Polly.ResilienceContext!
abstract Polly.ResilienceContextPool.Return(Polly.ResilienceContext! context) -> void
abstract Polly.ResilienceStrategy.ExecuteCoreAsync<TResult, TState>(System.Func<Polly.ResilienceContext!, TState, System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>
override Polly.Outcome<TResult>.ToString() -> string!
override Polly.Registry.ResilienceStrategyRegistry<TKey>.TryGetStrategy(TKey key, out Polly.ResilienceStrategy? strategy) -> bool
Expand Down Expand Up @@ -221,6 +223,9 @@ Polly.ResilienceContext.OperationKey.get -> string?
Polly.ResilienceContext.Properties.get -> Polly.ResilienceProperties!
Polly.ResilienceContext.ResilienceEvents.get -> System.Collections.Generic.IReadOnlyList<Polly.Telemetry.ResilienceEvent>!
Polly.ResilienceContext.ResultType.get -> System.Type!
Polly.ResilienceContextPool
Polly.ResilienceContextPool.Get(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Polly.ResilienceContext!
Polly.ResilienceContextPool.ResilienceContextPool() -> void
Polly.ResilienceProperties
Polly.ResilienceProperties.GetValue<TValue>(Polly.ResiliencePropertyKey<TValue> key, TValue defaultValue) -> TValue
Polly.ResilienceProperties.ResilienceProperties() -> void
Expand Down Expand Up @@ -428,9 +433,7 @@ static Polly.PredicateBuilder<TResult>.implicit operator System.Func<Polly.Outco
static Polly.PredicateBuilder<TResult>.implicit operator System.Func<Polly.OutcomeArguments<TResult, Polly.Retry.RetryPredicateArguments>, System.Threading.Tasks.ValueTask<bool>>!(Polly.PredicateBuilder<TResult>! builder) -> System.Func<Polly.OutcomeArguments<TResult, Polly.Retry.RetryPredicateArguments>, System.Threading.Tasks.ValueTask<bool>>!
static Polly.PredicateResult.False.get -> System.Threading.Tasks.ValueTask<bool>
static Polly.PredicateResult.True.get -> System.Threading.Tasks.ValueTask<bool>
static Polly.ResilienceContext.Get(string! operationKey, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Polly.ResilienceContext!
static Polly.ResilienceContext.Get(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Polly.ResilienceContext!
static Polly.ResilienceContext.Return(Polly.ResilienceContext! context) -> void
static Polly.ResilienceContextPool.Shared.get -> Polly.ResilienceContextPool!
static Polly.ResiliencePropertyKey<TValue>.operator !=(Polly.ResiliencePropertyKey<TValue> left, Polly.ResiliencePropertyKey<TValue> right) -> bool
static Polly.ResiliencePropertyKey<TValue>.operator ==(Polly.ResiliencePropertyKey<TValue> left, Polly.ResiliencePropertyKey<TValue> right) -> bool
static Polly.ResilienceStrategyBuilderExtensions.AddStrategy<TBuilder>(this TBuilder! builder, Polly.ResilienceStrategy! strategy) -> TBuilder!
Expand Down
4 changes: 2 additions & 2 deletions src/Polly.Core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ For example, the synchronous `Execute` method is implemented as:
``` csharp
public void Execute(Action execute)
{
var context = ResilienceContext.Get();
var context = ResilienceContextPool.Shared.Get();

context.IsSynchronous = true;
context.ResultType = typeof(VoidResult);
Expand All @@ -83,7 +83,7 @@ public void Execute(Action execute)
}
finally
{
ResilienceContext.Return(context);
ResilienceContextPool.Shared.Return(context);
}
}
```
Expand Down
57 changes: 4 additions & 53 deletions src/Polly.Core/ResilienceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,22 @@

namespace Polly;

#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters

/// <summary>
/// A context assigned to a single execution of <see cref="ResilienceStrategy"/>. It is created manually or automatically
/// when the user calls the various extensions on top of <see cref="ResilienceStrategy"/>. After every execution the context should be discarded and returned to the pool.
/// </summary>
/// <remarks>
/// Do not re-use an instance of <see cref="ResilienceContext"/> across more than one execution. The <see cref="ResilienceContext"/> is retrieved from the pool
/// by calling the <see cref="Get(CancellationToken)"/> method. After you are done with it you should return it to the pool by calling the <see cref="Return"/> method.
/// by calling the <see cref="ResilienceContextPool.Get(CancellationToken)"/> method. After you are done with it you should return it to the pool
/// by calling the <see cref="ResilienceContextPool.Return(ResilienceContext)"/> method.
/// </remarks>
public sealed class ResilienceContext
{
private const bool ContinueOnCapturedContextDefault = false;

private static readonly ObjectPool<ResilienceContext> Pool = new(static () => new ResilienceContext(), static c => c.Reset());

private readonly List<ResilienceEvent> _resilienceEvents = new();

private ResilienceContext()
internal ResilienceContext()
{
}

Expand All @@ -34,7 +31,7 @@ private ResilienceContext()
/// The operation key value should have a low cardinality (i.e. do not assign values such as <see cref="Guid"/> to this property).
/// </remarks>
/// <value>The default value is <see langword="null"/>.</value>
public string? OperationKey { get; private set; }
public string? OperationKey { get; internal set; }

/// <summary>
/// Gets the <see cref="CancellationToken"/> associated with the execution.
Expand Down Expand Up @@ -79,40 +76,6 @@ private ResilienceContext()
/// </remarks>
public IReadOnlyList<ResilienceEvent> ResilienceEvents => _resilienceEvents;

/// <summary>
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An instance of <see cref="ResilienceContext"/>.</returns>
/// <remarks>
/// After the execution is finished you should return the <see cref="ResilienceContext"/> back to the pool
/// by calling <see cref="Return(ResilienceContext)"/> method.
/// </remarks>
public static ResilienceContext Get(CancellationToken cancellationToken = default)
{
var context = Pool.Get();
context.CancellationToken = cancellationToken;
return context;
}

/// <summary>
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
/// </summary>
/// <param name="operationKey">An operation key associated with the context.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An instance of <see cref="ResilienceContext"/>.</returns>
/// <remarks>
/// After the execution is finished you should return the <see cref="ResilienceContext"/> back to the pool
/// by calling <see cref="Return(ResilienceContext)"/> method.
/// </remarks>
public static ResilienceContext Get(string operationKey, CancellationToken cancellationToken = default)
{
var context = Pool.Get();
context.OperationKey = operationKey;
context.CancellationToken = cancellationToken;
return context;
}

internal void InitializeFrom(ResilienceContext context)
{
OperationKey = context.OperationKey;
Expand All @@ -124,18 +87,6 @@ internal void InitializeFrom(ResilienceContext context)
_resilienceEvents.AddRange(context.ResilienceEvents);
}

/// <summary>
/// Returns a <paramref name="context"/> back to the pool.
/// </summary>
/// <param name="context">The context instance.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="context"/> is <see langword="null"/>.</exception>
public static void Return(ResilienceContext context)
{
Guard.NotNull(context);

Pool.Return(context);
}

[ExcludeFromCodeCoverage]
[Conditional("DEBUG")]
internal void AssertInitialized()
Expand Down
21 changes: 21 additions & 0 deletions src/Polly.Core/ResilienceContextPool.Shared.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Polly;

public abstract partial class ResilienceContextPool
{
private sealed class SharedPool : ResilienceContextPool
{
private readonly ObjectPool<ResilienceContext> _pool = new(static () => new ResilienceContext(), static c => c.Reset());

public override ResilienceContext Get(string? operationKey, CancellationToken cancellationToken = default)
{
var context = _pool.Get();

context.OperationKey = operationKey;
context.CancellationToken = cancellationToken;

return context;
}

public override void Return(ResilienceContext context) => _pool.Return(Guard.NotNull(context));
}
}
45 changes: 45 additions & 0 deletions src/Polly.Core/ResilienceContextPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Polly;

#pragma warning disable CA1716 // Identifiers should not match keywords
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters

/// <summary>
/// The pool of <see cref="ResilienceContext"/> instances.
/// </summary>
public abstract partial class ResilienceContextPool
{
/// <summary>
/// Gets the shared pool instance.
/// </summary>
public static ResilienceContextPool Shared { get; } = new SharedPool();

/// <summary>
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An instance of <see cref="ResilienceContext"/>.</returns>
/// <remarks>
/// After the execution is finished you should return the <see cref="ResilienceContext"/> back to the pool
/// by calling <see cref="Return(ResilienceContext)"/> method.
/// </remarks>
public ResilienceContext Get(CancellationToken cancellationToken = default) => Get(null, cancellationToken);

/// <summary>
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
/// </summary>
/// <param name="operationKey">An operation key associated with the context.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An instance of <see cref="ResilienceContext"/>.</returns>
/// <remarks>
/// After the execution is finished you should return the <see cref="ResilienceContext"/> back to the pool
/// by calling <see cref="Return(ResilienceContext)"/> method.
/// </remarks>
public abstract ResilienceContext Get(string? operationKey, CancellationToken cancellationToken = default);

/// <summary>
/// Returns a <paramref name="context"/> back to the pool.
/// </summary>
/// <param name="context">The context instance.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="context"/> is <see langword="null"/>.</exception>
public abstract void Return(ResilienceContext context);
}
4 changes: 2 additions & 2 deletions src/Polly.Core/ResilienceStrategy.Async.ValueTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ static async (context, state) =>
}
finally
{
ResilienceContext.Return(context);
Pool.Return(context);
}
}

Expand Down Expand Up @@ -160,7 +160,7 @@ static async (context, state) =>
}
finally
{
ResilienceContext.Return(context);
Pool.Return(context);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/Polly.Core/ResilienceStrategy.Async.ValueTaskT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ static async (context, state) =>
}
finally
{
ResilienceContext.Return(context);
Pool.Return(context);
}
}

Expand Down Expand Up @@ -186,13 +186,13 @@ static async (context, state) =>
}
finally
{
ResilienceContext.Return(context);
Pool.Return(context);
}
}

private static ResilienceContext GetAsyncContext<TResult>(CancellationToken cancellationToken)
{
var context = ResilienceContext.Get(cancellationToken);
var context = Pool.Get(cancellationToken);

InitializeAsyncContext<TResult>(context);

Expand Down
Loading

0 comments on commit 7f6b8a4

Please sign in to comment.