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

ResiliencePipelineRegistry is now disposable #1496

Merged
merged 9 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
73 changes: 47 additions & 26 deletions src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace Polly.CircuitBreaker;
/// <remarks>
/// The instance of this class can be reused across multiple circuit breakers.
/// </remarks>
public sealed class CircuitBreakerManualControl : IDisposable
public sealed class CircuitBreakerManualControl
{
private readonly HashSet<Action> _onDispose = new();
private readonly object _lock = new();
private readonly HashSet<Func<ResilienceContext, Task>> _onIsolate = new();
private readonly HashSet<Func<ResilienceContext, Task>> _onReset = new();
private bool _isolated;
Expand All @@ -23,21 +23,34 @@ public CircuitBreakerManualControl()
/// <summary>
/// Initializes a new instance of the <see cref="CircuitBreakerManualControl"/> class.
/// </summary>
/// <param name="isIsolated">Determines whether the circit breaker is isolated immediately after construction.</param>
/// <param name="isIsolated">Determines whether the circuit breaker is isolated immediately after construction.</param>
public CircuitBreakerManualControl(bool isIsolated) => _isolated = isIsolated;

internal void Initialize(Func<ResilienceContext, Task> onIsolate, Func<ResilienceContext, Task> onReset, Action onDispose)
{
_onDispose.Add(onDispose);
_onIsolate.Add(onIsolate);
_onReset.Add(onReset);
internal bool IsEmpty => _onIsolate.Count == 0;

if (_isolated)
internal IDisposable Initialize(Func<ResilienceContext, Task> onIsolate, Func<ResilienceContext, Task> onReset)
{
lock (_lock)
{
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();
_onIsolate.Add(onIsolate);
_onReset.Add(onReset);

if (_isolated)
{
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();
}

return new RegistrationDisposable(() =>
{
lock (_lock)
{
_onIsolate.Remove(onIsolate);
_onReset.Remove(onReset);
}
});
}
}

Expand All @@ -54,7 +67,14 @@ internal async Task IsolateAsync(ResilienceContext context)

_isolated = true;

foreach (var action in _onIsolate)
Func<ResilienceContext, Task>[] callbacks;

lock (_lock)
{
callbacks = _onIsolate.ToArray();
}

foreach (var action in callbacks)
{
await action(context).ConfigureAwait(context.ContinueOnCapturedContext);
}
Expand Down Expand Up @@ -95,7 +115,14 @@ internal async Task CloseAsync(ResilienceContext context)

context.Initialize<VoidResult>(isSynchronous: false);

foreach (var action in _onReset)
Func<ResilienceContext, Task>[] callbacks;

lock (_lock)
{
callbacks = _onReset.ToArray();
}

foreach (var action in callbacks)
{
await action(context).ConfigureAwait(context.ContinueOnCapturedContext);
}
Expand All @@ -121,18 +148,12 @@ public async Task CloseAsync(CancellationToken cancellationToken = default)
}
}

/// <summary>
/// Disposes the current class.
/// </summary>
public void Dispose()
private class RegistrationDisposable : IDisposable
{
foreach (var action in _onDispose)
{
action();
}
private readonly Action _disposeAction;

public RegistrationDisposable(Action disposeAction) => _disposeAction = disposeAction;

_onDispose.Clear();
_onIsolate.Clear();
_onReset.Clear();
public void Dispose() => _disposeAction();
}
}
14 changes: 10 additions & 4 deletions src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
namespace Polly.CircuitBreaker;

internal sealed class CircuitBreakerResilienceStrategy<T> : ResilienceStrategy<T>
internal sealed class CircuitBreakerResilienceStrategy<T> : ResilienceStrategy<T>, IDisposable
{
private readonly Func<OutcomeArguments<T, CircuitBreakerPredicateArguments>, ValueTask<bool>> _handler;
private readonly CircuitStateController<T> _controller;
private readonly IDisposable? _manualControlRegistration;

public CircuitBreakerResilienceStrategy(
Func<OutcomeArguments<T, CircuitBreakerPredicateArguments>, ValueTask<bool>> handler,
Expand All @@ -15,10 +16,15 @@ public CircuitBreakerResilienceStrategy(
_controller = controller;

stateProvider?.Initialize(() => _controller.CircuitState, () => _controller.LastHandledOutcome);
manualControl?.Initialize(
_manualControlRegistration = manualControl?.Initialize(
async c => await _controller.IsolateCircuitAsync(c).ConfigureAwait(c.ContinueOnCapturedContext),
async c => await _controller.CloseCircuitAsync(c).ConfigureAwait(c.ContinueOnCapturedContext),
_controller.Dispose);
async c => await _controller.CloseCircuitAsync(c).ConfigureAwait(c.ContinueOnCapturedContext));
}

public void Dispose()
{
_manualControlRegistration?.Dispose();
_controller.Dispose();
}

protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func<ResilienceContext, TState, ValueTask<Outcome<T>>> callback, ResilienceContext context, TState state)
Expand Down
3 changes: 2 additions & 1 deletion src/Polly.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ Polly.CircuitBreaker.CircuitBreakerManualControl
Polly.CircuitBreaker.CircuitBreakerManualControl.CircuitBreakerManualControl() -> void
Polly.CircuitBreaker.CircuitBreakerManualControl.CircuitBreakerManualControl(bool isIsolated) -> void
Polly.CircuitBreaker.CircuitBreakerManualControl.CloseAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Polly.CircuitBreaker.CircuitBreakerManualControl.Dispose() -> void
Polly.CircuitBreaker.CircuitBreakerManualControl.IsolateAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Polly.CircuitBreaker.CircuitBreakerPredicateArguments
Polly.CircuitBreaker.CircuitBreakerPredicateArguments.CircuitBreakerPredicateArguments() -> void
Expand Down Expand Up @@ -167,6 +166,8 @@ Polly.Registry.ConfigureBuilderContext<TKey>.PipelineKey.get -> TKey
Polly.Registry.ResiliencePipelineProvider<TKey>
Polly.Registry.ResiliencePipelineProvider<TKey>.ResiliencePipelineProvider() -> void
Polly.Registry.ResiliencePipelineRegistry<TKey>
Polly.Registry.ResiliencePipelineRegistry<TKey>.Dispose() -> void
Polly.Registry.ResiliencePipelineRegistry<TKey>.DisposeAsync() -> System.Threading.Tasks.ValueTask
Polly.Registry.ResiliencePipelineRegistry<TKey>.GetOrAddPipeline(TKey key, System.Action<Polly.ResiliencePipelineBuilder!, Polly.Registry.ConfigureBuilderContext<TKey>!>! configure) -> Polly.ResiliencePipeline!
Polly.Registry.ResiliencePipelineRegistry<TKey>.GetOrAddPipeline(TKey key, System.Action<Polly.ResiliencePipelineBuilder!>! configure) -> Polly.ResiliencePipeline!
Polly.Registry.ResiliencePipelineRegistry<TKey>.GetOrAddPipeline<TResult>(TKey key, System.Action<Polly.ResiliencePipelineBuilder<TResult>!, Polly.Registry.ConfigureBuilderContext<TKey>!>! configure) -> Polly.ResiliencePipeline<TResult>!
Expand Down
4 changes: 4 additions & 0 deletions src/Polly.Core/Registry/ResiliencePipelineProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public abstract class ResiliencePipelineProvider<TKey>
/// <param name="key">The key used to identify the resilience pipeline.</param>
/// <returns>The resilience pipeline associated with the specified key.</returns>
/// <exception cref="KeyNotFoundException">Thrown when no resilience pipeline is found for the specified key.</exception>
/// <exception cref="ObjectDisposedException">Thrown when the provider is already disposed.</exception>
public virtual ResiliencePipeline GetPipeline(TKey key)
{
if (TryGetPipeline(key, out var pipeline))
Expand All @@ -35,6 +36,7 @@ public virtual ResiliencePipeline GetPipeline(TKey key)
/// <param name="key">The key used to identify the resilience pipeline.</param>
/// <returns>The resilience pipeline associated with the specified key.</returns>
/// <exception cref="KeyNotFoundException">Thrown when no resilience pipeline is found for the specified key.</exception>
/// <exception cref="ObjectDisposedException">Thrown when the provider is already disposed.</exception>
public virtual ResiliencePipeline<TResult> GetPipeline<TResult>(TKey key)
{
if (TryGetPipeline<TResult>(key, out var pipeline))
Expand All @@ -52,6 +54,7 @@ public virtual ResiliencePipeline<TResult> GetPipeline<TResult>(TKey key)
/// <param name="key">The key used to identify the resilience pipeline.</param>
/// <param name="pipeline">The output resilience pipeline if found, <see langword="null"/> otherwise.</param>
/// <returns><see langword="true"/> if the pipeline was found, <see langword="false"/> otherwise.</returns>
/// <exception cref="ObjectDisposedException">Thrown when the provider is already disposed.</exception>
martincostello marked this conversation as resolved.
Show resolved Hide resolved
public abstract bool TryGetPipeline(TKey key, [NotNullWhen(true)] out ResiliencePipeline? pipeline);

/// <summary>
Expand All @@ -61,5 +64,6 @@ public virtual ResiliencePipeline<TResult> GetPipeline<TResult>(TKey key)
/// <param name="key">The key used to identify the resilience pipeline.</param>
/// <param name="pipeline">The output resilience pipeline if found, <see langword="null"/> otherwise.</param>
/// <returns><see langword="true"/> if the pipeline was found, <see langword="false"/> otherwise.</returns>
/// <exception cref="ObjectDisposedException">Thrown when the provider is already disposed.</exception>
public abstract bool TryGetPipeline<TResult>(TKey key, [NotNullWhen(true)] out ResiliencePipeline<TResult>? pipeline);
}
26 changes: 23 additions & 3 deletions src/Polly.Core/Registry/ResiliencePipelineRegistry.TResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Polly.Registry;
public sealed partial class ResiliencePipelineRegistry<TKey> : ResiliencePipelineProvider<TKey>
where TKey : notnull
{
private sealed class GenericRegistry<TResult>
private sealed class GenericRegistry<TResult> : IDisposable, IAsyncDisposable
{
private readonly Func<ResiliencePipelineBuilder<TResult>> _activator;
private readonly ConcurrentDictionary<TKey, Action<ResiliencePipelineBuilder<TResult>, ConfigureBuilderContext<TKey>>> _builders;
Expand Down Expand Up @@ -52,14 +52,34 @@ public ResiliencePipeline<TResult> GetOrAdd(TKey key, Action<ResiliencePipelineB
#if NETCOREAPP3_0_OR_GREATER
return _strategies.GetOrAdd(key, static (_, factory) =>
{
return new ResiliencePipeline<TResult>(CreatePipelineComponent(factory.instance._activator, factory.context, factory.configure));
return new ResiliencePipeline<TResult>(CreatePipelineComponent(factory.instance._activator, factory.context, factory.configure), DisposeBehavior.Reject);
},
(instance: this, context, configure));
#else
return _strategies.GetOrAdd(key, _ => new ResiliencePipeline<TResult>(CreatePipelineComponent(_activator, context, configure)));
return _strategies.GetOrAdd(key, _ => new ResiliencePipeline<TResult>(CreatePipelineComponent(_activator, context, configure), DisposeBehavior.Reject));
#endif
}

public bool TryAddBuilder(TKey key, Action<ResiliencePipelineBuilder<TResult>, ConfigureBuilderContext<TKey>> configure) => _builders.TryAdd(key, configure);

public void Dispose()
{
foreach (var strategy in _strategies.Values)
{
strategy.DisposeHelper.ForceDispose();
}

_strategies.Clear();
}

public async ValueTask DisposeAsync()
{
foreach (var strategy in _strategies.Values)
{
await strategy.DisposeHelper.ForceDisposeAsync().ConfigureAwait(false);
}

_strategies.Clear();
}
martincostello marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading