CircuitStateController<TResult>
using Polly.Utilities;
using System;
using System.Threading;
namespace Polly.CircuitBreaker
{
internal abstract class CircuitStateController<TResult> : ICircuitController<TResult>
{
protected readonly TimeSpan DurationOfBreak;
protected readonly Action<DelegateResult<TResult>, CircuitState, TimeSpan, Context> OnBreak;
protected readonly Action<Context> OnReset;
protected readonly Action OnHalfOpen;
protected readonly object Lock = new object();
protected long BlockedTill;
protected CircuitState InternalCircuitState;
protected DelegateResult<TResult> LastOutcome;
public CircuitState CircuitState {
get {
if ((int)InternalCircuitState != 1)
return InternalCircuitState;
using (TimedLock.Lock(Lock)) {
if ((int)InternalCircuitState == 1 && !IsInAutomatedBreak_NeedsLock) {
InternalCircuitState = 2;
OnHalfOpen();
}
return InternalCircuitState;
}
}
}
public Exception LastException {
get {
using (TimedLock.Lock(Lock)) {
DelegateResult<TResult> lastOutcome = LastOutcome;
return (lastOutcome != null) ? lastOutcome.Exception : null;
}
}
}
public TResult LastHandledResult {
get {
using (TimedLock.Lock(Lock))
return (LastOutcome != null) ? LastOutcome.Result : default(TResult);
}
}
protected bool IsInAutomatedBreak_NeedsLock => SystemClock.UtcNow().Ticks < BlockedTill;
protected CircuitStateController(TimeSpan durationOfBreak, Action<DelegateResult<TResult>, CircuitState, TimeSpan, Context> onBreak, Action<Context> onReset, Action onHalfOpen)
{
DurationOfBreak = durationOfBreak;
OnBreak = onBreak;
OnReset = onReset;
OnHalfOpen = onHalfOpen;
InternalCircuitState = 0;
Reset();
}
public void Isolate()
{
using (TimedLock.Lock(Lock)) {
LastOutcome = new DelegateResult<TResult>((Exception)new IsolatedCircuitException("The circuit is manually held open and is not allowing calls."));
BreakFor_NeedsLock(TimeSpan.MaxValue, Context.None());
InternalCircuitState = 3;
}
}
protected void Break_NeedsLock(Context context)
{
BreakFor_NeedsLock(DurationOfBreak, context);
}
private void BreakFor_NeedsLock(TimeSpan durationOfBreak, Context context)
{
DateTime dateTime;
long ticks;
if (!(durationOfBreak > DateTime.MaxValue - SystemClock.UtcNow())) {
dateTime = SystemClock.UtcNow() + durationOfBreak;
ticks = dateTime.Ticks;
} else {
dateTime = DateTime.MaxValue;
ticks = dateTime.Ticks;
}
BlockedTill = ticks;
CircuitState internalCircuitState = InternalCircuitState;
InternalCircuitState = 1;
OnBreak(LastOutcome, internalCircuitState, durationOfBreak, context);
}
public void Reset()
{
OnCircuitReset(Context.None());
}
protected void ResetInternal_NeedsLock(Context context)
{
BlockedTill = DateTime.MinValue.Ticks;
LastOutcome = null;
CircuitState internalCircuitState = InternalCircuitState;
InternalCircuitState = 0;
if ((int)internalCircuitState != 0)
OnReset(context);
}
protected bool PermitHalfOpenCircuitTest()
{
long blockedTill = BlockedTill;
DateTime dateTime = SystemClock.UtcNow();
if (dateTime.Ticks < blockedTill)
return false;
ref long blockedTill2 = ref BlockedTill;
dateTime = SystemClock.UtcNow();
return Interlocked.CompareExchange(ref blockedTill2, dateTime.Ticks + DurationOfBreak.Ticks, blockedTill) == blockedTill;
}
private BrokenCircuitException GetBreakingException()
{
DelegateResult<TResult> lastOutcome = LastOutcome;
if (lastOutcome == null)
return new BrokenCircuitException("The circuit is now open and is not allowing calls.");
if (lastOutcome.Exception != null)
return new BrokenCircuitException("The circuit is now open and is not allowing calls.", lastOutcome.Exception);
return new BrokenCircuitException<TResult>("The circuit is now open and is not allowing calls.", lastOutcome.Result);
}
public void OnActionPreExecute()
{
CircuitState circuitState = CircuitState;
switch ((int)circuitState) {
case 0:
break;
case 2:
if (!PermitHalfOpenCircuitTest())
throw GetBreakingException();
break;
case 1:
throw GetBreakingException();
case 3:
throw new IsolatedCircuitException("The circuit is manually held open and is not allowing calls.");
default:
throw new InvalidOperationException("Unhandled CircuitState.");
}
}
public abstract void OnActionSuccess(Context context);
public abstract void OnActionFailure(DelegateResult<TResult> outcome, Context context);
public abstract void OnCircuitReset(Context context);
}
}