UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

367 lines (313 loc) 9.06 kB
/** * Circuit breaker pattern for fault tolerance */ import type { ILogger } from '../core/logger.js'; import type { IEventBus } from '../core/event-bus.js'; export interface CircuitBreakerConfig { failureThreshold: number; // Number of failures before opening successThreshold: number; // Number of successes before closing timeout: number; // Time in ms before attempting to close halfOpenLimit: number; // Max requests in half-open state } export enum CircuitState { CLOSED = 'closed', OPEN = 'open', HALF_OPEN = 'half-open', } export interface CircuitBreakerMetrics { state: CircuitState; failures: number; successes: number; lastFailureTime?: Date; lastSuccessTime?: Date; totalRequests: number; rejectedRequests: number; halfOpenRequests: number; } /** * Circuit breaker for protecting against cascading failures */ export class CircuitBreaker { private state: CircuitState = CircuitState.CLOSED; private failures = 0; private successes = 0; private lastFailureTime?: Date; private lastSuccessTime?: Date; private nextAttempt?: Date; private halfOpenRequests = 0; private totalRequests = 0; private rejectedRequests = 0; constructor( private name: string, private config: CircuitBreakerConfig, private logger: ILogger, private eventBus?: IEventBus, ) {} /** * Execute a function with circuit breaker protection */ async execute<T>(fn: () => Promise<T>): Promise<T> { this.totalRequests++; // Check if we should execute if (!this.canExecute()) { this.rejectedRequests++; const error = new Error(`Circuit breaker '${this.name}' is OPEN`); this.logStateChange('Request rejected'); throw error; } try { // Execute the function const result = await fn(); // Record success this.onSuccess(); return result; } catch (error) { // Record failure this.onFailure(); throw error; } } /** * Check if execution is allowed */ private canExecute(): boolean { switch (this.state) { case CircuitState.CLOSED: return true; case CircuitState.OPEN: // Check if we should transition to half-open if (this.nextAttempt && new Date() >= this.nextAttempt) { this.transitionTo(CircuitState.HALF_OPEN); return true; } return false; case CircuitState.HALF_OPEN: // Allow limited requests in half-open state return this.halfOpenRequests < this.config.halfOpenLimit; default: return false; } } /** * Handle successful execution */ private onSuccess(): void { this.lastSuccessTime = new Date(); switch (this.state) { case CircuitState.CLOSED: this.failures = 0; // Reset failure count break; case CircuitState.HALF_OPEN: this.successes++; this.halfOpenRequests++; // Check if we should close the circuit if (this.successes >= this.config.successThreshold) { this.transitionTo(CircuitState.CLOSED); } break; case CircuitState.OPEN: // Shouldn't happen, but handle gracefully this.transitionTo(CircuitState.HALF_OPEN); break; } } /** * Handle failed execution */ private onFailure(): void { this.lastFailureTime = new Date(); switch (this.state) { case CircuitState.CLOSED: this.failures++; // Check if we should open the circuit if (this.failures >= this.config.failureThreshold) { this.transitionTo(CircuitState.OPEN); } break; case CircuitState.HALF_OPEN: // Single failure in half-open state reopens the circuit this.transitionTo(CircuitState.OPEN); break; case CircuitState.OPEN: // Already open, update next attempt time this.nextAttempt = new Date(Date.now() + this.config.timeout); break; } } /** * Transition to a new state */ private transitionTo(newState: CircuitState): void { const oldState = this.state; this.state = newState; this.logger.info(`Circuit breaker '${this.name}' state change`, { from: oldState, to: newState, failures: this.failures, successes: this.successes, }); // Reset counters based on new state switch (newState) { case CircuitState.CLOSED: this.failures = 0; this.successes = 0; this.halfOpenRequests = 0; delete this.nextAttempt; break; case CircuitState.OPEN: this.successes = 0; this.halfOpenRequests = 0; this.nextAttempt = new Date(Date.now() + this.config.timeout); break; case CircuitState.HALF_OPEN: this.successes = 0; this.failures = 0; this.halfOpenRequests = 0; break; } // Emit state change event if (this.eventBus) { this.eventBus.emit('circuitbreaker:state-change', { name: this.name, from: oldState, to: newState, metrics: this.getMetrics(), }); } } /** * Force the circuit to a specific state */ forceState(state: CircuitState): void { this.logger.warn(`Forcing circuit breaker '${this.name}' to state`, { state }); this.transitionTo(state); } /** * Get current state */ getState(): CircuitState { return this.state; } /** * Get circuit breaker metrics */ getMetrics(): CircuitBreakerMetrics { const metrics: CircuitBreakerMetrics = { state: this.state, failures: this.failures, successes: this.successes, totalRequests: this.totalRequests, rejectedRequests: this.rejectedRequests, halfOpenRequests: this.halfOpenRequests, }; if (this.lastFailureTime !== undefined) { metrics.lastFailureTime = this.lastFailureTime; } if (this.lastSuccessTime !== undefined) { metrics.lastSuccessTime = this.lastSuccessTime; } return metrics; } /** * Reset the circuit breaker */ reset(): void { this.logger.info(`Resetting circuit breaker '${this.name}'`); this.state = CircuitState.CLOSED; this.failures = 0; this.successes = 0; delete this.lastFailureTime; delete this.lastSuccessTime; delete this.nextAttempt; this.halfOpenRequests = 0; this.totalRequests = 0; this.rejectedRequests = 0; } /** * Log state change with consistent format */ private logStateChange(message: string): void { this.logger.debug(`Circuit breaker '${this.name}': ${message}`, { state: this.state, failures: this.failures, successes: this.successes, nextAttempt: this.nextAttempt, }); } } /** * Manager for multiple circuit breakers */ export class CircuitBreakerManager { private breakers = new Map<string, CircuitBreaker>(); constructor( private defaultConfig: CircuitBreakerConfig, private logger: ILogger, private eventBus?: IEventBus, ) {} /** * Get or create a circuit breaker */ getBreaker(name: string, config?: Partial<CircuitBreakerConfig>): CircuitBreaker { let breaker = this.breakers.get(name); if (!breaker) { const finalConfig = { ...this.defaultConfig, ...config }; breaker = new CircuitBreaker(name, finalConfig, this.logger, this.eventBus); this.breakers.set(name, breaker); } return breaker; } /** * Execute with circuit breaker */ async execute<T>( name: string, fn: () => Promise<T>, config?: Partial<CircuitBreakerConfig>, ): Promise<T> { const breaker = this.getBreaker(name, config); return breaker.execute(fn); } /** * Get all circuit breakers */ getAllBreakers(): Map<string, CircuitBreaker> { return new Map(this.breakers); } /** * Get metrics for all breakers */ getAllMetrics(): Record<string, CircuitBreakerMetrics> { const metrics: Record<string, CircuitBreakerMetrics> = {}; for (const [name, breaker] of this.breakers) { metrics[name] = breaker.getMetrics(); } return metrics; } /** * Reset a specific breaker */ resetBreaker(name: string): void { const breaker = this.breakers.get(name); if (breaker) { breaker.reset(); } } /** * Reset all breakers */ resetAll(): void { for (const breaker of this.breakers.values()) { breaker.reset(); } } /** * Force a breaker to a specific state */ forceState(name: string, state: CircuitState): void { const breaker = this.breakers.get(name); if (breaker) { breaker.forceState(state); } } }