datapilot-cli
Version:
Enterprise-grade streaming multi-format data analysis with comprehensive statistical insights and intelligent relationship detection - supports CSV, JSON, Excel, TSV, Parquet - memory-efficient, cross-platform
341 lines • 11.5 kB
JavaScript
"use strict";
/**
* Circuit Breaker Pattern Implementation
* Prevents cascading failures and enables graceful degradation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CircuitBreakerManager = exports.CircuitBreakerTimeoutError = exports.CircuitBreakerOpenError = exports.CircuitBreaker = exports.CircuitState = void 0;
exports.getGlobalCircuitBreakerManager = getGlobalCircuitBreakerManager;
exports.shutdownGlobalCircuitBreakerManager = shutdownGlobalCircuitBreakerManager;
const events_1 = require("events");
const perf_hooks_1 = require("perf_hooks");
const logger_1 = require("../utils/logger");
var CircuitState;
(function (CircuitState) {
CircuitState["CLOSED"] = "closed";
CircuitState["OPEN"] = "open";
CircuitState["HALF_OPEN"] = "half-open";
})(CircuitState || (exports.CircuitState = CircuitState = {}));
class CircuitBreaker extends events_1.EventEmitter {
state = CircuitState.CLOSED;
failureCount = 0;
successCount = 0;
totalCalls = 0;
lastFailureTime = 0;
lastSuccessTime = 0;
stateChangeTime = Date.now();
recentCalls = [];
resetTimer;
options;
operation;
name;
constructor(operation, name, options = {}) {
super();
this.operation = operation;
this.name = name;
this.options = {
failureThreshold: options.failureThreshold || 5,
resetTimeout: options.resetTimeout || 60000, // 1 minute
monitoringPeriod: options.monitoringPeriod || 60000, // 1 minute
successThreshold: options.successThreshold || 3,
timeoutMs: options.timeoutMs || 30000, // 30 seconds
volumeThreshold: options.volumeThreshold || 10,
};
}
/**
* Execute the wrapped operation with circuit breaker protection
*/
async execute(...args) {
this.cleanupOldCalls();
if (this.state === CircuitState.OPEN) {
this.emit('circuit-open-rejection', { name: this.name, args });
throw new CircuitBreakerOpenError(`Circuit breaker ${this.name} is OPEN`);
}
const startTime = perf_hooks_1.performance.now();
this.totalCalls++;
try {
// Add timeout protection
const result = await this.executeWithTimeout(args);
const executionTime = perf_hooks_1.performance.now() - startTime;
this.onSuccess(executionTime);
return result;
}
catch (error) {
const executionTime = perf_hooks_1.performance.now() - startTime;
this.onFailure(error, executionTime);
throw error;
}
}
/**
* Execute operation with timeout
*/
async executeWithTimeout(args) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new CircuitBreakerTimeoutError(`Operation ${this.name} timed out after ${this.options.timeoutMs}ms`));
}, this.options.timeoutMs);
this.operation(...args)
.then((result) => {
clearTimeout(timeout);
resolve(result);
})
.catch((error) => {
clearTimeout(timeout);
reject(error);
});
});
}
/**
* Handle successful execution
*/
onSuccess(executionTime) {
this.successCount++;
this.lastSuccessTime = Date.now();
this.recentCalls.push({ success: true, timestamp: Date.now() });
this.emit('success', {
name: this.name,
executionTime,
state: this.state,
metrics: this.getMetrics(),
});
if (this.state === CircuitState.HALF_OPEN) {
if (this.successCount >= this.options.successThreshold) {
this.transitionTo(CircuitState.CLOSED);
}
}
}
/**
* Handle failed execution
*/
onFailure(error, executionTime) {
this.failureCount++;
this.lastFailureTime = Date.now();
this.recentCalls.push({ success: false, timestamp: Date.now() });
this.emit('failure', {
name: this.name,
error: error.message,
executionTime,
state: this.state,
metrics: this.getMetrics(),
});
if (this.state === CircuitState.CLOSED || this.state === CircuitState.HALF_OPEN) {
if (this.shouldOpenCircuit()) {
this.transitionTo(CircuitState.OPEN);
}
}
}
/**
* Determine if circuit should open based on failure criteria
*/
shouldOpenCircuit() {
const recentFailures = this.recentCalls.filter((call) => !call.success).length;
const failureRate = this.recentCalls.length > 0 ? recentFailures / this.recentCalls.length : 0;
// Need minimum volume and either threshold failures or high failure rate
return (this.recentCalls.length >= this.options.volumeThreshold &&
(recentFailures >= this.options.failureThreshold || failureRate > 0.5));
}
/**
* Transition circuit to new state
*/
transitionTo(newState) {
const oldState = this.state;
this.state = newState;
this.stateChangeTime = Date.now();
logger_1.logger.info(`Circuit breaker ${this.name} transitioned from ${oldState} to ${newState}`);
this.emit('state-change', {
name: this.name,
oldState,
newState,
metrics: this.getMetrics(),
});
if (newState === CircuitState.OPEN) {
this.scheduleReset();
}
else if (newState === CircuitState.CLOSED) {
this.reset();
}
}
/**
* Schedule transition to half-open state
*/
scheduleReset() {
if (this.resetTimer) {
clearTimeout(this.resetTimer);
}
this.resetTimer = setTimeout(() => {
if (this.state === CircuitState.OPEN) {
this.transitionTo(CircuitState.HALF_OPEN);
}
}, this.options.resetTimeout);
}
/**
* Reset circuit breaker metrics
*/
reset() {
this.failureCount = 0;
this.successCount = 0;
this.recentCalls = [];
if (this.resetTimer) {
clearTimeout(this.resetTimer);
this.resetTimer = undefined;
}
}
/**
* Clean up old call records outside monitoring period
*/
cleanupOldCalls() {
const cutoff = Date.now() - this.options.monitoringPeriod;
this.recentCalls = this.recentCalls.filter((call) => call.timestamp > cutoff);
}
/**
* Force circuit to specific state (for testing/recovery)
*/
forceState(state) {
this.transitionTo(state);
}
/**
* Get current circuit breaker metrics
*/
getMetrics() {
this.cleanupOldCalls();
const recentFailures = this.recentCalls.filter((call) => !call.success).length;
const failureRate = this.recentCalls.length > 0 ? recentFailures / this.recentCalls.length : 0;
return {
state: this.state,
failureCount: this.failureCount,
successCount: this.successCount,
totalCalls: this.totalCalls,
lastFailureTime: this.lastFailureTime,
lastSuccessTime: this.lastSuccessTime,
stateChangeTime: this.stateChangeTime,
failureRate,
};
}
/**
* Check if circuit is available for requests
*/
isAvailable() {
return this.state !== CircuitState.OPEN;
}
/**
* Get current state
*/
getState() {
return this.state;
}
/**
* Shutdown circuit breaker
*/
shutdown() {
if (this.resetTimer) {
clearTimeout(this.resetTimer);
this.resetTimer = undefined;
}
this.emit('shutdown', { name: this.name });
}
}
exports.CircuitBreaker = CircuitBreaker;
/**
* Circuit breaker specific error types
*/
class CircuitBreakerOpenError extends Error {
constructor(message) {
super(message);
this.name = 'CircuitBreakerOpenError';
}
}
exports.CircuitBreakerOpenError = CircuitBreakerOpenError;
class CircuitBreakerTimeoutError extends Error {
constructor(message) {
super(message);
this.name = 'CircuitBreakerTimeoutError';
}
}
exports.CircuitBreakerTimeoutError = CircuitBreakerTimeoutError;
/**
* Circuit breaker manager for multiple operations
*/
class CircuitBreakerManager extends events_1.EventEmitter {
breakers = new Map();
/**
* Create or get circuit breaker for operation
*/
getCircuitBreaker(name, operation, options) {
let breaker = this.breakers.get(name);
if (!breaker) {
breaker = new CircuitBreaker(operation, name, options);
this.breakers.set(name, breaker);
// Forward events
breaker.on('state-change', (data) => this.emit('breaker-state-change', data));
breaker.on('failure', (data) => this.emit('breaker-failure', data));
breaker.on('success', (data) => this.emit('breaker-success', data));
}
return breaker;
}
/**
* Get all circuit breaker metrics
*/
getAllMetrics() {
const metrics = {};
for (const [name, breaker] of this.breakers) {
metrics[name] = breaker.getMetrics();
}
return metrics;
}
/**
* Get overall system health
*/
getSystemHealth() {
const metrics = this.getAllMetrics();
const breakers = Object.values(metrics);
const openCount = breakers.filter((b) => b.state === CircuitState.OPEN).length;
const halfOpenCount = breakers.filter((b) => b.state === CircuitState.HALF_OPEN).length;
const closedCount = breakers.filter((b) => b.state === CircuitState.CLOSED).length;
const overallHealth = breakers.length > 0 ? (closedCount / breakers.length) * 100 : 100;
return {
totalBreakers: breakers.length,
openBreakers: openCount,
halfOpenBreakers: halfOpenCount,
closedBreakers: closedCount,
overallHealth,
};
}
/**
* Force all circuits to closed state (emergency recovery)
*/
forceAllClosed() {
for (const breaker of this.breakers.values()) {
breaker.forceState(CircuitState.CLOSED);
}
logger_1.logger.warn('Forced all circuit breakers to CLOSED state');
this.emit('emergency-reset');
}
/**
* Shutdown all circuit breakers
*/
shutdown() {
for (const breaker of this.breakers.values()) {
breaker.shutdown();
}
this.breakers.clear();
this.emit('shutdown');
}
}
exports.CircuitBreakerManager = CircuitBreakerManager;
/**
* Global circuit breaker manager
*/
let globalCircuitBreakerManager = null;
function getGlobalCircuitBreakerManager() {
if (!globalCircuitBreakerManager) {
globalCircuitBreakerManager = new CircuitBreakerManager();
}
return globalCircuitBreakerManager;
}
function shutdownGlobalCircuitBreakerManager() {
if (globalCircuitBreakerManager) {
globalCircuitBreakerManager.shutdown();
globalCircuitBreakerManager = null;
}
}
//# sourceMappingURL=circuit-breaker.js.map