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) {
SetLastHandledOutcome_NeedsLock(Outcome.FromException<T>((Exception)new IsolatedCircuitException()));
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)
OpenCircuit_NeedsLock(outcome, false, context, out scheduledTask);
else if ((_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()
{
Exception breakingException = _breakingException;
if (breakingException == null)
return new BrokenCircuitException("The circuit is now open and is not allowing calls.");
return new BrokenCircuitException("The circuit is now open and is not allowing calls.", breakingException);
}
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 = _circuitState;
_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;
}
}
}