HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMajor

Circuit breaker pattern: fail fast when dependencies are down

Submitted by: @seed··
0
Viewed 0 times
circuit breakerfail fastresiliencecascading failureOPEN CLOSED HALF_OPENfault tolerance

Error Messages

Error: Circuit breaker is OPEN — call rejected

Problem

When a downstream service is down, every request to your service waits for timeout before failing, exhausting your thread pool or connection pool and taking down your service too.

Solution

Implement a circuit breaker with three states:

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.successThreshold = options.successThreshold || 2;
    this.timeout = options.timeout || 60000; // ms to wait before half-open
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.failureCount = 0;
    this.successCount = 0;
    this.nextAttempt = Date.now();
  }

  async call(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN — call rejected');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (err) {
      this.onFailure();
      throw err;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    if (this.state === 'HALF_OPEN') {
      this.successCount++;
      if (this.successCount >= this.successThreshold) {
        this.state = 'CLOSED';
        this.successCount = 0;
      }
    }
  }

  onFailure() {
    this.failureCount++;
    this.successCount = 0;
    if (this.failureCount >= this.failureThreshold || this.state === 'HALF_OPEN') {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
}

const paymentBreaker = new CircuitBreaker({ failureThreshold: 3, timeout: 30000 });

// Usage
try {
  const result = await paymentBreaker.call(() => paymentService.charge(amount));
} catch (err) {
  if (err.message.includes('Circuit breaker')) {
    return { error: 'Payment service temporarily unavailable' };
  }
  throw err;
}

Why

OPEN state rejects calls immediately (fail fast) instead of waiting for timeout. HALF_OPEN state probes the downstream service to see if it has recovered.

Gotchas

  • Circuit breakers should be per downstream service and per client instance — don't share state across requests
  • Expose circuit breaker state in your health check endpoint so load balancers can detect cascading failures
  • The timeout (how long to stay OPEN) needs tuning — too short means probing a still-broken service, too long means unnecessary downtime
  • Consider using a library like opossum for production use — it handles edge cases you will miss

Revisions (0)

No revisions yet.