3using System.Threading.Tasks;
13 private enum State { Closed, Open, HalfOpen }
15 private readonly
int threshold;
16 private readonly TimeSpan breakDuration;
17 private readonly Action<string>? onStateChange;
20 private State state = State.Closed;
21 private DateTimeOffset openedAt;
22 private int halfOpenInProgress;
23 private readonly
object sync =
new();
25 public CircuitBreakerPolicy(
int failureThreshold, TimeSpan breakDuration, Action<string>? onStateChange =
null)
27 ArgumentOutOfRangeException.ThrowIfLessThan(failureThreshold, 1);
28 ArgumentOutOfRangeException.ThrowIfLessThan(breakDuration, TimeSpan.Zero);
30 this.threshold = failureThreshold;
31 this.breakDuration = breakDuration;
32 this.onStateChange = onStateChange;
35 public async Task ExecuteAsync(Func<CancellationToken, Task> action, CancellationToken ct)
38 this.EnsureEntryAllowed();
42 await action(ct).ConfigureAwait(
false);
45 catch (OperationCanceledException) when (ct.IsCancellationRequested)
57 public async Task<T>
ExecuteAsync<T>(Func<CancellationToken, Task<T>> action, CancellationToken ct)
60 this.EnsureEntryAllowed();
64 T result = await action(ct).ConfigureAwait(
false);
68 catch (OperationCanceledException) when (ct.IsCancellationRequested)
80 private void EnsureEntryAllowed()
90 if (DateTimeOffset.UtcNow -
this.openedAt >=
this.breakDuration)
92 this.SetState(State.HalfOpen);
94 goto case State.HalfOpen;
96 throw new InvalidOperationException(
"Circuit is open");
99 if (Interlocked.CompareExchange(ref
this.halfOpenInProgress, 1, 0) == 0)
103 throw new InvalidOperationException(
"Circuit is half-open; probe in progress");
108 private void OnSuccess()
113 if (this.state != State.Closed)
114 this.SetState(State.Closed);
115 if (this.state != State.HalfOpen)
116 Interlocked.Exchange(ref this.halfOpenInProgress, 0);
120 private void OnFailure()
124 if (this.state == State.HalfOpen)
127 Interlocked.Exchange(ref this.halfOpenInProgress, 0);
131 if (++this.failures >= this.threshold)
138 this.openedAt = DateTimeOffset.UtcNow;
139 this.SetState(State.Open);
142 private void SetState(State newState)
144 if (this.state == newState)
146 this.state = newState;
147 this.onStateChange?.Invoke(newState.ToString());
Simple circuit breaker: trips after N consecutive failures and stays open for a cool-down....
async Task< T > ExecuteAsync< T >(Func< CancellationToken, Task< T > > action, CancellationToken ct)
Executes an async action producing a value through the policy pipeline.