Skip to content

Instantly share code, notes, and snippets.

@loosechainsaw
Created May 12, 2015 14:36
Show Gist options
  • Select an option

  • Save loosechainsaw/edf6388e5f1c20d93aa0 to your computer and use it in GitHub Desktop.

Select an option

Save loosechainsaw/edf6388e5f1c20d93aa0 to your computer and use it in GitHub Desktop.

Revisions

  1. loosechainsaw created this gist May 12, 2015.
    296 changes: 296 additions & 0 deletions cb.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,296 @@
    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;
    }