Skip to content

Commit 149ed82

Browse files
authored
ResiliencePipelineRegistry is now disposable (#1496)
1 parent 46dc47f commit 149ed82

37 files changed

+888
-205
lines changed

src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ namespace Polly.CircuitBreaker;
66
/// <remarks>
77
/// The instance of this class can be reused across multiple circuit breakers.
88
/// </remarks>
9-
public sealed class CircuitBreakerManualControl : IDisposable
9+
public sealed class CircuitBreakerManualControl
1010
{
11-
private readonly HashSet<Action> _onDispose = new();
11+
private readonly object _lock = new();
1212
private readonly HashSet<Func<ResilienceContext, Task>> _onIsolate = new();
1313
private readonly HashSet<Func<ResilienceContext, Task>> _onReset = new();
1414
private bool _isolated;
@@ -23,21 +23,34 @@ public CircuitBreakerManualControl()
2323
/// <summary>
2424
/// Initializes a new instance of the <see cref="CircuitBreakerManualControl"/> class.
2525
/// </summary>
26-
/// <param name="isIsolated">Determines whether the circit breaker is isolated immediately after construction.</param>
26+
/// <param name="isIsolated">Determines whether the circuit breaker is isolated immediately after construction.</param>
2727
public CircuitBreakerManualControl(bool isIsolated) => _isolated = isIsolated;
2828

29-
internal void Initialize(Func<ResilienceContext, Task> onIsolate, Func<ResilienceContext, Task> onReset, Action onDispose)
30-
{
31-
_onDispose.Add(onDispose);
32-
_onIsolate.Add(onIsolate);
33-
_onReset.Add(onReset);
29+
internal bool IsEmpty => _onIsolate.Count == 0;
3430

35-
if (_isolated)
31+
internal IDisposable Initialize(Func<ResilienceContext, Task> onIsolate, Func<ResilienceContext, Task> onReset)
32+
{
33+
lock (_lock)
3634
{
37-
var context = ResilienceContextPool.Shared.Get().Initialize<VoidResult>(isSynchronous: true);
38-
39-
// if the control indicates that circuit breaker should be isolated, we isolate it right away
40-
IsolateAsync(context).GetAwaiter().GetResult();
35+
_onIsolate.Add(onIsolate);
36+
_onReset.Add(onReset);
37+
38+
if (_isolated)
39+
{
40+
var context = ResilienceContextPool.Shared.Get().Initialize<VoidResult>(isSynchronous: true);
41+
42+
// if the control indicates that circuit breaker should be isolated, we isolate it right away
43+
IsolateAsync(context).GetAwaiter().GetResult();
44+
}
45+
46+
return new RegistrationDisposable(() =>
47+
{
48+
lock (_lock)
49+
{
50+
_onIsolate.Remove(onIsolate);
51+
_onReset.Remove(onReset);
52+
}
53+
});
4154
}
4255
}
4356

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

5568
_isolated = true;
5669

57-
foreach (var action in _onIsolate)
70+
Func<ResilienceContext, Task>[] callbacks;
71+
72+
lock (_lock)
73+
{
74+
callbacks = _onIsolate.ToArray();
75+
}
76+
77+
foreach (var action in callbacks)
5878
{
5979
await action(context).ConfigureAwait(context.ContinueOnCapturedContext);
6080
}
@@ -95,7 +115,14 @@ internal async Task CloseAsync(ResilienceContext context)
95115

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

98-
foreach (var action in _onReset)
118+
Func<ResilienceContext, Task>[] callbacks;
119+
120+
lock (_lock)
121+
{
122+
callbacks = _onReset.ToArray();
123+
}
124+
125+
foreach (var action in callbacks)
99126
{
100127
await action(context).ConfigureAwait(context.ContinueOnCapturedContext);
101128
}
@@ -121,18 +148,12 @@ public async Task CloseAsync(CancellationToken cancellationToken = default)
121148
}
122149
}
123150

124-
/// <summary>
125-
/// Disposes the current class.
126-
/// </summary>
127-
public void Dispose()
151+
private class RegistrationDisposable : IDisposable
128152
{
129-
foreach (var action in _onDispose)
130-
{
131-
action();
132-
}
153+
private readonly Action _disposeAction;
154+
155+
public RegistrationDisposable(Action disposeAction) => _disposeAction = disposeAction;
133156

134-
_onDispose.Clear();
135-
_onIsolate.Clear();
136-
_onReset.Clear();
157+
public void Dispose() => _disposeAction();
137158
}
138159
}

src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategy.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
namespace Polly.CircuitBreaker;
22

3-
internal sealed class CircuitBreakerResilienceStrategy<T> : ResilienceStrategy<T>
3+
internal sealed class CircuitBreakerResilienceStrategy<T> : ResilienceStrategy<T>, IDisposable
44
{
55
private readonly Func<OutcomeArguments<T, CircuitBreakerPredicateArguments>, ValueTask<bool>> _handler;
66
private readonly CircuitStateController<T> _controller;
7+
private readonly IDisposable? _manualControlRegistration;
78

89
public CircuitBreakerResilienceStrategy(
910
Func<OutcomeArguments<T, CircuitBreakerPredicateArguments>, ValueTask<bool>> handler,
@@ -15,10 +16,15 @@ public CircuitBreakerResilienceStrategy(
1516
_controller = controller;
1617

1718
stateProvider?.Initialize(() => _controller.CircuitState, () => _controller.LastHandledOutcome);
18-
manualControl?.Initialize(
19+
_manualControlRegistration = manualControl?.Initialize(
1920
async c => await _controller.IsolateCircuitAsync(c).ConfigureAwait(c.ContinueOnCapturedContext),
20-
async c => await _controller.CloseCircuitAsync(c).ConfigureAwait(c.ContinueOnCapturedContext),
21-
_controller.Dispose);
21+
async c => await _controller.CloseCircuitAsync(c).ConfigureAwait(c.ContinueOnCapturedContext));
22+
}
23+
24+
public void Dispose()
25+
{
26+
_manualControlRegistration?.Dispose();
27+
_controller.Dispose();
2228
}
2329

2430
protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func<ResilienceContext, TState, ValueTask<Outcome<T>>> callback, ResilienceContext context, TState state)

src/Polly.Core/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ Polly.CircuitBreaker.CircuitBreakerManualControl
2424
Polly.CircuitBreaker.CircuitBreakerManualControl.CircuitBreakerManualControl() -> void
2525
Polly.CircuitBreaker.CircuitBreakerManualControl.CircuitBreakerManualControl(bool isIsolated) -> void
2626
Polly.CircuitBreaker.CircuitBreakerManualControl.CloseAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
27-
Polly.CircuitBreaker.CircuitBreakerManualControl.Dispose() -> void
2827
Polly.CircuitBreaker.CircuitBreakerManualControl.IsolateAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
2928
Polly.CircuitBreaker.CircuitBreakerPredicateArguments
3029
Polly.CircuitBreaker.CircuitBreakerPredicateArguments.CircuitBreakerPredicateArguments() -> void
@@ -167,6 +166,8 @@ Polly.Registry.ConfigureBuilderContext<TKey>.PipelineKey.get -> TKey
167166
Polly.Registry.ResiliencePipelineProvider<TKey>
168167
Polly.Registry.ResiliencePipelineProvider<TKey>.ResiliencePipelineProvider() -> void
169168
Polly.Registry.ResiliencePipelineRegistry<TKey>
169+
Polly.Registry.ResiliencePipelineRegistry<TKey>.Dispose() -> void
170+
Polly.Registry.ResiliencePipelineRegistry<TKey>.DisposeAsync() -> System.Threading.Tasks.ValueTask
170171
Polly.Registry.ResiliencePipelineRegistry<TKey>.GetOrAddPipeline(TKey key, System.Action<Polly.ResiliencePipelineBuilder!, Polly.Registry.ConfigureBuilderContext<TKey>!>! configure) -> Polly.ResiliencePipeline!
171172
Polly.Registry.ResiliencePipelineRegistry<TKey>.GetOrAddPipeline(TKey key, System.Action<Polly.ResiliencePipelineBuilder!>! configure) -> Polly.ResiliencePipeline!
172173
Polly.Registry.ResiliencePipelineRegistry<TKey>.GetOrAddPipeline<TResult>(TKey key, System.Action<Polly.ResiliencePipelineBuilder<TResult>!, Polly.Registry.ConfigureBuilderContext<TKey>!>! configure) -> Polly.ResiliencePipeline<TResult>!

src/Polly.Core/Registry/ResiliencePipelineRegistry.TResult.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Polly.Registry;
55
public sealed partial class ResiliencePipelineRegistry<TKey> : ResiliencePipelineProvider<TKey>
66
where TKey : notnull
77
{
8-
private sealed class GenericRegistry<TResult>
8+
private sealed class GenericRegistry<TResult> : IDisposable, IAsyncDisposable
99
{
1010
private readonly Func<ResiliencePipelineBuilder<TResult>> _activator;
1111
private readonly ConcurrentDictionary<TKey, Action<ResiliencePipelineBuilder<TResult>, ConfigureBuilderContext<TKey>>> _builders;
@@ -52,14 +52,34 @@ public ResiliencePipeline<TResult> GetOrAdd(TKey key, Action<ResiliencePipelineB
5252
#if NETCOREAPP3_0_OR_GREATER
5353
return _strategies.GetOrAdd(key, static (_, factory) =>
5454
{
55-
return new ResiliencePipeline<TResult>(CreatePipelineComponent(factory.instance._activator, factory.context, factory.configure));
55+
return new ResiliencePipeline<TResult>(CreatePipelineComponent(factory.instance._activator, factory.context, factory.configure), DisposeBehavior.Reject);
5656
},
5757
(instance: this, context, configure));
5858
#else
59-
return _strategies.GetOrAdd(key, _ => new ResiliencePipeline<TResult>(CreatePipelineComponent(_activator, context, configure)));
59+
return _strategies.GetOrAdd(key, _ => new ResiliencePipeline<TResult>(CreatePipelineComponent(_activator, context, configure), DisposeBehavior.Reject));
6060
#endif
6161
}
6262

6363
public bool TryAddBuilder(TKey key, Action<ResiliencePipelineBuilder<TResult>, ConfigureBuilderContext<TKey>> configure) => _builders.TryAdd(key, configure);
64+
65+
public void Dispose()
66+
{
67+
foreach (var strategy in _strategies.Values)
68+
{
69+
strategy.DisposeHelper.ForceDispose();
70+
}
71+
72+
_strategies.Clear();
73+
}
74+
75+
public async ValueTask DisposeAsync()
76+
{
77+
foreach (var strategy in _strategies.Values)
78+
{
79+
await strategy.DisposeHelper.ForceDisposeAsync().ConfigureAwait(false);
80+
}
81+
82+
_strategies.Clear();
83+
}
6484
}
6585
}

0 commit comments

Comments
 (0)