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;
        }
    }
}