claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
241 lines • 7.08 kB
JavaScript
/**
* Production Circuit Breaker
*
* Implements the circuit breaker pattern to prevent cascading failures:
* - Closed: Normal operation
* - Open: Failing fast, not calling service
* - Half-Open: Testing if service recovered
*
* @module @claude-flow/cli/production/circuit-breaker
*/
// ============================================================================
// Default Configuration
// ============================================================================
const DEFAULT_CONFIG = {
failureThreshold: 5,
resetTimeoutMs: 30000,
successThreshold: 3,
failureWindowMs: 60000,
halfOpenRequestPercentage: 0.1,
};
// ============================================================================
// Circuit Breaker Implementation
// ============================================================================
export class CircuitBreaker {
config;
state = 'closed';
failures = [];
successCount = 0;
stateChangedAt = Date.now();
totalRequests = 0;
totalFailures = 0;
totalSuccesses = 0;
lastFailure = null;
lastSuccess = null;
constructor(config = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
}
/**
* Get current circuit state
*/
getState() {
this.checkStateTransition();
return this.state;
}
/**
* Check if request is allowed
*/
isAllowed() {
this.checkStateTransition();
switch (this.state) {
case 'closed':
return true;
case 'open':
return false;
case 'half-open':
// Allow a percentage of requests through
return Math.random() < this.config.halfOpenRequestPercentage;
default:
return true;
}
}
/**
* Execute a function through the circuit breaker
*/
async execute(fn) {
if (!this.isAllowed()) {
throw new Error('Circuit breaker is open. Service temporarily unavailable.');
}
this.totalRequests++;
try {
const result = await fn();
this.recordSuccess();
return result;
}
catch (error) {
this.recordFailure();
throw error;
}
}
/**
* Record a successful operation
*/
recordSuccess() {
this.totalSuccesses++;
this.lastSuccess = new Date();
if (this.state === 'half-open') {
this.successCount++;
if (this.successCount >= this.config.successThreshold) {
this.transitionTo('closed');
}
}
// Reset failure count on success in closed state
if (this.state === 'closed') {
this.failures = [];
}
}
/**
* Record a failed operation
*/
recordFailure() {
const now = Date.now();
this.totalFailures++;
this.lastFailure = new Date();
// Add failure timestamp
this.failures.push(now);
// Clean old failures outside window
this.failures = this.failures.filter(t => t > now - this.config.failureWindowMs);
// Check if we should open the circuit
if (this.state === 'closed' && this.failures.length >= this.config.failureThreshold) {
this.transitionTo('open');
if (this.config.onOpen) {
this.config.onOpen(this.failures.length);
}
}
// If half-open and we fail, go back to open
if (this.state === 'half-open') {
this.transitionTo('open');
}
}
/**
* Manually open the circuit
*/
open() {
if (this.state !== 'open') {
this.transitionTo('open');
}
}
/**
* Manually close the circuit
*/
close() {
if (this.state !== 'closed') {
this.transitionTo('closed');
}
}
/**
* Reset the circuit breaker
*/
reset() {
this.state = 'closed';
this.failures = [];
this.successCount = 0;
this.stateChangedAt = Date.now();
this.totalRequests = 0;
this.totalFailures = 0;
this.totalSuccesses = 0;
this.lastFailure = null;
this.lastSuccess = null;
}
/**
* Get circuit statistics
*/
getStats() {
return {
state: this.state,
failures: this.failures.length,
successes: this.successCount,
lastFailure: this.lastFailure,
lastSuccess: this.lastSuccess,
stateChangedAt: new Date(this.stateChangedAt),
totalRequests: this.totalRequests,
totalFailures: this.totalFailures,
totalSuccesses: this.totalSuccesses,
};
}
/**
* Get failure rate
*/
getFailureRate() {
if (this.totalRequests === 0)
return 0;
return this.totalFailures / this.totalRequests;
}
// ============================================================================
// Private Methods
// ============================================================================
checkStateTransition() {
const now = Date.now();
if (this.state === 'open') {
// Check if we should transition to half-open
if (now - this.stateChangedAt >= this.config.resetTimeoutMs) {
this.transitionTo('half-open');
}
}
}
transitionTo(newState) {
const oldState = this.state;
if (oldState === newState)
return;
this.state = newState;
this.stateChangedAt = Date.now();
// Reset counters on state change
if (newState === 'half-open') {
this.successCount = 0;
}
if (newState === 'closed') {
this.failures = [];
this.successCount = 0;
if (this.config.onClose) {
this.config.onClose();
}
}
// Notify state change
if (this.config.onStateChange) {
this.config.onStateChange(oldState, newState);
}
}
}
// ============================================================================
// Circuit Breaker Registry
// ============================================================================
const circuitBreakers = new Map();
/**
* Get or create a circuit breaker by name
*/
export function getCircuitBreaker(name, config) {
if (!circuitBreakers.has(name)) {
circuitBreakers.set(name, new CircuitBreaker(config));
}
return circuitBreakers.get(name);
}
/**
* Get all circuit breaker stats
*/
export function getAllCircuitStats() {
const stats = {};
for (const [name, breaker] of circuitBreakers) {
stats[name] = breaker.getStats();
}
return stats;
}
/**
* Reset all circuit breakers
*/
export function resetAllCircuits() {
for (const breaker of circuitBreakers.values()) {
breaker.reset();
}
}
export default CircuitBreaker;
//# sourceMappingURL=circuit-breaker.js.map