Skip to content

Instantly share code, notes, and snippets.

@loosechainsaw
Created May 12, 2015 14:36
Show Gist options
  • Save loosechainsaw/edf6388e5f1c20d93aa0 to your computer and use it in GitHub Desktop.
Save loosechainsaw/edf6388e5f1c20d93aa0 to your computer and use it in GitHub Desktop.
Circuit Breaker
internal class ScopedAquiredWriteLock : IDisposable
{
internal ScopedAquiredWriteLock(ReaderWriterLockSlim locker)
{
this.locker = locker;
this.locker.EnterWriteLock();
}
public void Dispose()
{
this.locker.ExitWriteLock();
}
private ReaderWriterLockSlim locker;
}
internal class ScopedAquiredReadLock : IDisposable
{
internal ScopedAquiredReadLock(ReaderWriterLockSlim locker)
{
this.locker = locker;
this.locker.EnterReadLock();
}
public void Dispose()
{
this.locker.ExitReadLock();
}
private ReaderWriterLockSlim locker;
}
internal class ScopedAquiredUpgradableLock : IDisposable
{
internal ScopedAquiredUpgradableLock(ReaderWriterLockSlim locker)
{
this.locker = locker;
this.locker.EnterUpgradeableReadLock();
}
public void Dispose()
{
this.locker.ExitUpgradeableReadLock();
}
private ReaderWriterLockSlim locker;
}
internal enum State
{
Closed,
HalfOpen,
Open
}
public enum Notification
{
Success,
Failure
}
public interface IEnvironmentProvider
{
DateTime GetCurrentTime();
int GetMaximumFailuresForClosedContext();
TimeSpan GetDurationForClosedContext();
TimeSpan GetDurationForOpenContext();
int GetSuccessResetCountForHalfOpenContext();
}
public class EnvironmentProvider : IEnvironmentProvider
{
public int GetMaximumFailuresForClosedContext()
{
return 10;
}
public TimeSpan GetDurationForClosedContext()
{
return TimeSpan.FromMinutes(10);
}
public TimeSpan GetDurationForOpenContext()
{
return TimeSpan.FromMinutes(3);
}
public int GetSuccessResetCountForHalfOpenContext()
{
return 2;
}
public DateTime GetCurrentTime()
{
return DateTime.UtcNow;
}
}
public class CircuitBreaker
{
public CircuitBreaker(IEnvironmentProvider environmentProvider, string name)
{
locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
this.environmentProvider = environmentProvider;
this.name = name;
context = new ClosedCircuitBreakerContext(this);
}
internal void ChangeContext(CircuitBreakerContext ctx)
{
context = ctx;
}
public void ChangeEnvironment(IEnvironmentProvider provider)
{
environmentProvider = provider;
}
public IEnvironmentProvider GetEnvironment()
{
return environmentProvider;
}
internal ReaderWriterLockSlim GetLock()
{
return locker;
}
public bool Accepting()
{
using(var lk = new ScopedAquiredReadLock(locker))
return context.Accepting(environmentProvider.GetCurrentTime());
}
public void Notify(Notification notification)
{
using(var lk = new ScopedAquiredUpgradableLock(locker)) {
if(!context.Accepting(environmentProvider.GetCurrentTime()))
return;
context.Notify(notification);
}
}
private IEnvironmentProvider environmentProvider;
private readonly string name;
private CircuitBreakerContext context;
private readonly ReaderWriterLockSlim locker;
}
internal abstract class CircuitBreakerContext
{
internal CircuitBreakerContext(CircuitBreaker circuitbreaker)
{
this.circuitbreaker = circuitbreaker;
}
internal abstract void Notify(Notification notification);
internal abstract bool Accepting(DateTime currentTime);
internal void ChangeContext(CircuitBreakerContext context)
{
circuitbreaker.ChangeContext(context);
}
internal IEnvironmentProvider GetEnvironment()
{
return circuitbreaker.GetEnvironment();
}
protected internal readonly CircuitBreaker circuitbreaker;
}
internal class ClosedCircuitBreakerContext : CircuitBreakerContext
{
internal ClosedCircuitBreakerContext(CircuitBreaker circuitbreaker)
: base(circuitbreaker)
{
var environment = GetEnvironment();
windowEnd = environment.GetCurrentTime().Add(environment.GetDurationForClosedContext());
maxFailures = environment.GetMaximumFailuresForClosedContext();
}
internal override void Notify(Notification notification)
{
if(notification == Notification.Success)
return;
var environment = GetEnvironment();
var locker = circuitbreaker.GetLock();
var time = environment.GetCurrentTime().Add(environment.GetDurationForClosedContext());
if(time >= windowEnd) {
using(new ScopedAquiredWriteLock(locker)) {
failures = 0;
windowEnd = time;
return;
}
}
if(++failures == maxFailures) {
using(new ScopedAquiredWriteLock(locker)) {
ChangeContext(new OpenCircuitBreakerContext(circuitbreaker));
}
}
}
internal override bool Accepting(DateTime currentTime)
{
return true;
}
private DateTime windowEnd;
private int failures;
private readonly int maxFailures;
}
internal class OpenCircuitBreakerContext : CircuitBreakerContext
{
internal OpenCircuitBreakerContext(CircuitBreaker circuitbreaker) : base(circuitbreaker)
{
var environment = GetEnvironment();
expiresAt = environment.GetCurrentTime().Add(environment.GetDurationForOpenContext());
}
internal override void Notify(Notification notification)
{
var currentTime = GetEnvironment().GetCurrentTime();
if(currentTime < expiresAt)
return;
var locker = circuitbreaker.GetLock();
using(new ScopedAquiredWriteLock(locker)) {
ChangeContext(new HalfOpenCircuitBreakerContext(circuitbreaker));
}
}
internal override bool Accepting(DateTime currentTime)
{
return (currentTime > expiresAt);
}
private readonly DateTime expiresAt;
}
internal class HalfOpenCircuitBreakerContext : CircuitBreakerContext
{
internal HalfOpenCircuitBreakerContext(CircuitBreaker circuitbreaker) : base(circuitbreaker)
{
var environment = GetEnvironment();
consecutiveSuccessesRequired = environment.GetSuccessResetCountForHalfOpenContext();
}
internal override void Notify(Notification notification)
{
var currentTime = GetEnvironment().GetCurrentTime();
var locker = circuitbreaker.GetLock();
if(notification == Notification.Success && ++successes == consecutiveSuccessesRequired) {
using(new ScopedAquiredWriteLock(locker)) {
ChangeContext(new OpenCircuitBreakerContext(circuitbreaker));
}
}
if(notification == Notification.Failure) {
using(new ScopedAquiredWriteLock(locker)) {
ChangeContext(new OpenCircuitBreakerContext(circuitbreaker));
}
}
}
internal override bool Accepting(DateTime currentTime)
{
return true;
}
private readonly int consecutiveSuccessesRequired;
private int successes;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment