<PackageReference Include="Polly.Core" Version="8.5.2" />

CircuitStateController<T>

Thread-safe controller that holds and manages the circuit breaker state transitions.
using Polly.Telemetry; using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Polly.CircuitBreaker { [System.Runtime.CompilerServices.NullableContext(1)] [System.Runtime.CompilerServices.Nullable(0)] internal sealed class CircuitStateController<[System.Runtime.CompilerServices.Nullable(2)] T> : IDisposable { private readonly object _lock = new object(); private readonly ScheduledTaskExecutor _executor = new ScheduledTaskExecutor(); [System.Runtime.CompilerServices.Nullable(new byte[] { 2, 0, 1 })] private readonly Func<OnCircuitOpenedArguments<T>, ValueTask> _onOpened; [System.Runtime.CompilerServices.Nullable(new byte[] { 2, 0, 1 })] private readonly Func<OnCircuitClosedArguments<T>, ValueTask> _onClosed; [System.Runtime.CompilerServices.Nullable(2)] private readonly Func<OnCircuitHalfOpenedArguments, ValueTask> _onHalfOpen; private readonly TimeProvider _timeProvider; private readonly ResilienceStrategyTelemetry _telemetry; private readonly CircuitBehavior _behavior; private readonly TimeSpan _breakDuration; [System.Runtime.CompilerServices.Nullable(new byte[] { 2, 0 })] private readonly Func<BreakDurationGeneratorArguments, ValueTask<TimeSpan>> _breakDurationGenerator; private DateTimeOffset _blockedUntil; private CircuitState _circuitState; [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] private Outcome<T>? _lastOutcome; [System.Runtime.CompilerServices.Nullable(2)] private Exception _breakingException; private bool _disposed; private int _halfOpenAttempts; public CircuitState CircuitState { get { EnsureNotDisposed(); lock (_lock) { return _circuitState; } } } [System.Runtime.CompilerServices.Nullable(2)] public Exception LastException { [System.Runtime.CompilerServices.NullableContext(2)] get { EnsureNotDisposed(); lock (_lock) { ref Outcome<T>? lastOutcome = ref _lastOutcome; return lastOutcome.HasValue ? lastOutcome.GetValueOrDefault().Exception : null; } } } [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] public Outcome<T>? LastHandledOutcome { [return: System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] get { EnsureNotDisposed(); lock (_lock) { return _lastOutcome; } } } public CircuitStateController(TimeSpan breakDuration, [System.Runtime.CompilerServices.Nullable(new byte[] { 2, 0, 1 })] Func<OnCircuitOpenedArguments<T>, ValueTask> onOpened, [System.Runtime.CompilerServices.Nullable(new byte[] { 2, 0, 1 })] Func<OnCircuitClosedArguments<T>, ValueTask> onClosed, [System.Runtime.CompilerServices.Nullable(2)] Func<OnCircuitHalfOpenedArguments, ValueTask> onHalfOpen, CircuitBehavior behavior, TimeProvider timeProvider, ResilienceStrategyTelemetry telemetry, [System.Runtime.CompilerServices.Nullable(new byte[] { 2, 0 })] Func<BreakDurationGeneratorArguments, ValueTask<TimeSpan>> breakDurationGenerator) { _breakDuration = breakDuration; _onOpened = onOpened; _onClosed = onClosed; _onHalfOpen = onHalfOpen; _behavior = behavior; _timeProvider = timeProvider; _telemetry = telemetry; _breakDurationGenerator = breakDurationGenerator; } public ValueTask IsolateCircuitAsync(ResilienceContext context) { EnsureNotDisposed(); context.Initialize<T>(false); Task scheduledTask; lock (_lock) { IsolatedCircuitException ex = new IsolatedCircuitException(); _telemetry.SetTelemetrySource(ex); SetLastHandledOutcome_NeedsLock(Outcome.FromException<T>((Exception)ex)); OpenCircuitFor_NeedsLock(Outcome.FromResult<T>(default(T)), TimeSpan.MaxValue, true, context, out scheduledTask); _circuitState = CircuitState.Isolated; } return ExecuteScheduledTaskAsync(scheduledTask, context); } public ValueTask CloseCircuitAsync(ResilienceContext context) { EnsureNotDisposed(); context.Initialize<T>(false); Task scheduledTask; lock (_lock) { CloseCircuit_NeedsLock(Outcome.FromResult<T>(default(T)), true, context, out scheduledTask); } return ExecuteScheduledTaskAsync(scheduledTask, context); } [AsyncStateMachine(typeof(CircuitStateController<>.<OnActionPreExecuteAsync>d__25))] [return: System.Runtime.CompilerServices.Nullable(new byte[] { 0, 0, 1 })] public ValueTask<Outcome<T>?> OnActionPreExecuteAsync(ResilienceContext context) { <OnActionPreExecuteAsync>d__25 stateMachine = default(<OnActionPreExecuteAsync>d__25); stateMachine.<>t__builder = AsyncValueTaskMethodBuilder<Outcome<T>?>.Create(); stateMachine.<>4__this = this; stateMachine.context = context; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } public ValueTask OnUnhandledOutcomeAsync([System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] Outcome<T> outcome, ResilienceContext context) { EnsureNotDisposed(); Task scheduledTask = null; lock (_lock) { _behavior.OnActionSuccess(_circuitState); if (_circuitState == CircuitState.HalfOpen) CloseCircuit_NeedsLock(outcome, false, context, out scheduledTask); } return ExecuteScheduledTaskAsync(scheduledTask, context); } public ValueTask OnHandledOutcomeAsync([System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] Outcome<T> outcome, ResilienceContext context) { EnsureNotDisposed(); Task scheduledTask = null; lock (_lock) { SetLastHandledOutcome_NeedsLock(outcome); _behavior.OnActionFailure(_circuitState, out bool shouldBreak); if (_circuitState == CircuitState.HalfOpen || ((_circuitState == CircuitState.Closed) & shouldBreak)) OpenCircuit_NeedsLock(outcome, false, context, out scheduledTask); } return ExecuteScheduledTaskAsync(scheduledTask, context); } public void Dispose() { _executor.Dispose(); _disposed = true; } [AsyncStateMachine(typeof(CircuitStateController<>.<ExecuteScheduledTaskAsync>d__29))] internal static ValueTask ExecuteScheduledTaskAsync([System.Runtime.CompilerServices.Nullable(2)] Task task, ResilienceContext context) { <ExecuteScheduledTaskAsync>d__29 stateMachine = default(<ExecuteScheduledTaskAsync>d__29); stateMachine.<>t__builder = AsyncValueTaskMethodBuilder.Create(); stateMachine.task = task; stateMachine.context = context; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start<<ExecuteScheduledTaskAsync>d__29>(ref stateMachine); return stateMachine.<>t__builder.Task; } private static bool IsDateTimeOverflow(DateTimeOffset utcNow, TimeSpan breakDuration) { TimeSpan t = DateTimeOffset.MaxValue - utcNow; return breakDuration > t; } private void EnsureNotDisposed() { if (_disposed) throw new ObjectDisposedException("CircuitStateController"); } private void CloseCircuit_NeedsLock([System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] Outcome<T> outcome, bool manual, ResilienceContext context, [System.Runtime.CompilerServices.Nullable(2)] out Task scheduledTask) { scheduledTask = null; _blockedUntil = DateTimeOffset.MinValue; _lastOutcome = null; _halfOpenAttempts = 0; CircuitState circuitState = _circuitState; _circuitState = CircuitState.Closed; _behavior.OnCircuitClosed(); if (circuitState != 0) { OnCircuitClosedArguments<T> args = new OnCircuitClosedArguments<T>(context, outcome, manual); _telemetry.Report<OnCircuitClosedArguments<T>, T>(new ResilienceEvent(ResilienceEventSeverity.Information, "OnCircuitClosed"), args); if (_onClosed != null) _executor.ScheduleTask(() => _onClosed(args).AsTask(), context, out scheduledTask); } } private bool PermitHalfOpenCircuitTest_NeedsLock() { DateTimeOffset utcNow = _timeProvider.GetUtcNow(); if (utcNow >= _blockedUntil) { _blockedUntil = utcNow + _breakDuration; return true; } return false; } private void SetLastHandledOutcome_NeedsLock([System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] Outcome<T> outcome) { _lastOutcome = outcome; _breakingException = outcome.Exception; } private BrokenCircuitException CreateBrokenCircuitException() { TimeSpan retryAfter = _blockedUntil - _timeProvider.GetUtcNow(); Exception breakingException = _breakingException; BrokenCircuitException ex = (breakingException == null) ? new BrokenCircuitException("The circuit is now open and is not allowing calls.", retryAfter) : new BrokenCircuitException("The circuit is now open and is not allowing calls.", retryAfter, breakingException); BrokenCircuitException ex2 = ex; _telemetry.SetTelemetrySource(ex2); return ex2; } private void OpenCircuit_NeedsLock([System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] Outcome<T> outcome, bool manual, ResilienceContext context, [System.Runtime.CompilerServices.Nullable(2)] out Task scheduledTask) { OpenCircuitFor_NeedsLock(outcome, _breakDuration, manual, context, out scheduledTask); } private void OpenCircuitFor_NeedsLock([System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] Outcome<T> outcome, TimeSpan breakDuration, bool manual, ResilienceContext context, [System.Runtime.CompilerServices.Nullable(2)] out Task scheduledTask) { scheduledTask = null; DateTimeOffset utcNow = _timeProvider.GetUtcNow(); if (_breakDurationGenerator != null) breakDuration = _breakDurationGenerator(new BreakDurationGeneratorArguments(_behavior.FailureRate, _behavior.FailureCount, context, _halfOpenAttempts)).GetAwaiter().GetResult(); _blockedUntil = (IsDateTimeOverflow(utcNow, breakDuration) ? DateTimeOffset.MaxValue : (utcNow + breakDuration)); _circuitState = CircuitState.Open; OnCircuitOpenedArguments<T> args = new OnCircuitOpenedArguments<T>(context, outcome, breakDuration, manual); _telemetry.Report<OnCircuitOpenedArguments<T>, T>(new ResilienceEvent(ResilienceEventSeverity.Error, "OnCircuitOpened"), args); if (_onOpened != null) _executor.ScheduleTask(() => _onOpened(args).AsTask(), context, out scheduledTask); } private Task ScheduleHalfOpenTask(ResilienceContext context) { _executor.ScheduleTask(() => _onHalfOpen(new OnCircuitHalfOpenedArguments(context)).AsTask(), context, out Task task); return task; } } }