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

Introduce ResilienceContextPool (ApiReview) #1421

Merged
merged 4 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
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,6 @@ 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.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 All @@ -443,5 +445,6 @@ static Polly.TimeoutResilienceStrategyBuilderExtensions.AddTimeout<TBuilder>(thi
static Polly.Utils.LegacySupport.SetProperties(this Polly.ResilienceProperties! resilienceProperties, System.Collections.Generic.IDictionary<string!, object?>! properties, out System.Collections.Generic.IDictionary<string!, object?>! oldProperties) -> void
static readonly Polly.NullResilienceStrategy.Instance -> Polly.NullResilienceStrategy!
static readonly Polly.NullResilienceStrategy<TResult>.Instance -> Polly.NullResilienceStrategy<TResult>!
static readonly Polly.ResilienceContextPool.Shared -> Polly.ResilienceContextPool!
virtual Polly.Registry.ResilienceStrategyProvider<TKey>.GetStrategy(TKey key) -> Polly.ResilienceStrategy!
virtual Polly.Registry.ResilienceStrategyProvider<TKey>.GetStrategy<TResult>(TKey key) -> Polly.ResilienceStrategy<TResult>!
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>
/// The shared pool instance.
/// </summary>
public static readonly ResilienceContextPool Shared = new SharedPool();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a property rather than a field?


/// <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
Loading
Loading