UNPKG

@sailboat-computer/resilience

Version:

Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations

449 lines 16.7 kB
"use strict"; /** * Marine-specific circuit breaker implementation */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MarineCircuitBreaker = void 0; const marine_constants_1 = require("../types/marine-constants"); /** * Enhanced circuit breaker with marine-specific adaptations */ class MarineCircuitBreaker { constructor(name, config = {}) { this.eventListeners = []; this.config = { failureThreshold: 5, successThreshold: 3, timeout: 60000, // 1 minute operationalContext: marine_constants_1.OperationalContext.SAILING, failureTypes: [marine_constants_1.MarineFailureType.SENSOR_FAILURE, marine_constants_1.MarineFailureType.TIMEOUT], environmentalAdjustments: { roughSeas: false, lowPower: false, limitedConnectivity: false }, volumeThreshold: 10, errorPercentageThreshold: 50, slowCallDurationThreshold: 5000, // 5 seconds slowCallRateThreshold: 50, ...config }; this.metrics = this.initializeMetrics(); } /** * Execute operation with circuit breaker protection */ async execute(operation, operationName = 'unknown') { const startTime = Date.now(); // Check if circuit breaker allows execution if (!this.canExecute()) { this.metrics.rejectedRequests++; this.emitEvent('circuit_breaker_opened', operationName, { reason: 'Circuit breaker is open', state: this.metrics.state }, 'warning'); return { success: false, error: new Error(`Circuit breaker is ${this.metrics.state} for ${operationName}`), metrics: { executionTime: 0, retryCount: 0, circuitBreakerState: this.metrics.state }, marineContext: this.getMarineContext() }; } this.metrics.totalRequests++; try { // Execute the operation with timeout const timeoutMs = this.calculateTimeout(); const result = await this.executeWithTimeout(operation, timeoutMs); const executionTime = Date.now() - startTime; // Check if call was slow const isSlow = executionTime > this.config.slowCallDurationThreshold; if (isSlow) { this.metrics.slowCalls++; } // Record success this.onSuccess(executionTime); return { success: true, data: result, metrics: { executionTime, retryCount: 0, circuitBreakerState: this.metrics.state, timeoutApplied: timeoutMs }, marineContext: this.getMarineContext() }; } catch (error) { const executionTime = Date.now() - startTime; // Classify the failure type const failureType = this.classifyFailure(error); // Record failure this.onFailure(failureType, executionTime); return { success: false, error: error, metrics: { executionTime, retryCount: 0, circuitBreakerState: this.metrics.state }, marineContext: this.getMarineContext() }; } } /** * Update marine environment status for adaptive behavior */ updateMarineEnvironment(environment) { this.marineEnvironment = environment; // Adjust configuration based on marine conditions this.adjustForMarineConditions(); } /** * Get current circuit breaker metrics */ getMetrics() { return { ...this.metrics }; } /** * Get current circuit breaker state */ getState() { return this.metrics.state; } /** * Force circuit breaker to specific state (for testing/emergency) */ forceState(state, reason = 'Manual override') { const previousState = this.metrics.state; this.metrics.state = state; this.metrics.stateChangedAt = new Date(); if (state === marine_constants_1.CircuitBreakerState.OPEN) { this.metrics.nextAttemptAt = new Date(Date.now() + this.config.timeout); } else { delete this.metrics.nextAttemptAt; } this.emitEvent('circuit_breaker_opened', 'manual', { previousState, newState: state, reason }, 'info'); } /** * Add event listener for circuit breaker events */ onEvent(listener) { this.eventListeners.push(listener); } /** * Reset circuit breaker metrics (for testing/maintenance) */ reset() { this.metrics = this.initializeMetrics(); } /** * Check if circuit breaker allows execution */ canExecute() { const now = new Date(); switch (this.metrics.state) { case marine_constants_1.CircuitBreakerState.CLOSED: return true; case marine_constants_1.CircuitBreakerState.OPEN: // Check if timeout period has passed if (this.metrics.nextAttemptAt && now >= this.metrics.nextAttemptAt) { this.transitionToHalfOpen(); return true; } return false; case marine_constants_1.CircuitBreakerState.HALF_OPEN: return true; default: return false; } } /** * Execute operation with timeout */ async executeWithTimeout(operation, timeoutMs) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(`Operation timed out after ${timeoutMs}ms`)); }, timeoutMs); operation() .then(result => { clearTimeout(timer); resolve(result); }) .catch(error => { clearTimeout(timer); reject(error); }); }); } /** * Calculate timeout based on marine conditions */ calculateTimeout() { let timeout = this.config.slowCallDurationThreshold; // Adjust for marine environment if (this.marineEnvironment) { timeout *= this.marineEnvironment.recommendedTimeoutMultiplier; } // Adjust for operational context switch (this.config.operationalContext) { case marine_constants_1.OperationalContext.EMERGENCY: timeout *= 0.5; // Faster response needed in emergency break; case marine_constants_1.OperationalContext.MAINTENANCE: timeout *= 2.0; // More tolerance during maintenance break; case marine_constants_1.OperationalContext.ANCHORED: timeout *= 1.5; // More relaxed when anchored break; } // Adjust for environmental conditions if (this.config.environmentalAdjustments.roughSeas) { timeout *= 1.5; } if (this.config.environmentalAdjustments.limitedConnectivity) { timeout *= 2.0; } return Math.max(1000, Math.min(timeout, 30000)); // Between 1s and 30s } /** * Classify failure type for marine-specific handling */ classifyFailure(error) { const message = error.message.toLowerCase(); if (message.includes('timeout')) { return marine_constants_1.MarineFailureType.TIMEOUT; } if (message.includes('network') || message.includes('connection')) { return marine_constants_1.MarineFailureType.CONNECTIVITY_LOSS; } if (message.includes('sensor') || message.includes('reading')) { return marine_constants_1.MarineFailureType.SENSOR_FAILURE; } if (message.includes('power') || message.includes('voltage')) { return marine_constants_1.MarineFailureType.POWER_FLUCTUATION; } if (message.includes('validation') || message.includes('quality')) { return marine_constants_1.MarineFailureType.DATA_QUALITY; } if (message.includes('resource') || message.includes('memory')) { return marine_constants_1.MarineFailureType.RESOURCE_EXHAUSTION; } return marine_constants_1.MarineFailureType.ENVIRONMENTAL; // Default for unknown failures } /** * Handle successful operation */ onSuccess(executionTime) { this.metrics.successfulRequests++; this.metrics.lastSuccessTime = new Date(); this.updateAverageResponseTime(executionTime); if (this.metrics.state === marine_constants_1.CircuitBreakerState.HALF_OPEN) { // Count successes in half-open state const recentSuccesses = this.calculateRecentSuccesses(); if (recentSuccesses >= this.config.successThreshold) { this.transitionToClosed(); } } } /** * Handle failed operation */ onFailure(failureType, executionTime) { this.metrics.failedRequests++; this.metrics.lastFailureTime = new Date(); this.updateAverageResponseTime(executionTime); // Update marine-specific failure counters this.updateMarineFailureCounters(failureType); // Check if circuit should open if (this.shouldOpenCircuit()) { this.transitionToOpen(failureType); } } /** * Update marine-specific failure counters */ updateMarineFailureCounters(failureType) { switch (failureType) { case marine_constants_1.MarineFailureType.ENVIRONMENTAL: this.metrics.environmentalFailures++; break; case marine_constants_1.MarineFailureType.POWER_FLUCTUATION: this.metrics.powerRelatedFailures++; break; case marine_constants_1.MarineFailureType.CONNECTIVITY_LOSS: this.metrics.connectivityFailures++; break; case marine_constants_1.MarineFailureType.SENSOR_FAILURE: this.metrics.sensorFailures++; break; } } /** * Check if circuit breaker should open */ shouldOpenCircuit() { // Don't open if we haven't reached minimum volume if (this.metrics.totalRequests < this.config.volumeThreshold) { return false; } // Check failure count threshold if (this.metrics.failedRequests >= this.config.failureThreshold) { return true; } // Check error percentage threshold const errorPercentage = (this.metrics.failedRequests / this.metrics.totalRequests) * 100; if (errorPercentage >= this.config.errorPercentageThreshold) { return true; } // Check slow call rate threshold const slowCallPercentage = (this.metrics.slowCalls / this.metrics.totalRequests) * 100; if (slowCallPercentage >= this.config.slowCallRateThreshold) { return true; } return false; } /** * Transition to OPEN state */ transitionToOpen(failureType) { this.metrics.state = marine_constants_1.CircuitBreakerState.OPEN; this.metrics.stateChangedAt = new Date(); this.metrics.nextAttemptAt = new Date(Date.now() + this.config.timeout); this.emitEvent('circuit_breaker_opened', 'automatic', { failureType, failureCount: this.metrics.failedRequests, totalRequests: this.metrics.totalRequests, errorPercentage: (this.metrics.failedRequests / this.metrics.totalRequests) * 100 }, 'warning'); } /** * Transition to HALF_OPEN state */ transitionToHalfOpen() { this.metrics.state = marine_constants_1.CircuitBreakerState.HALF_OPEN; this.metrics.stateChangedAt = new Date(); delete this.metrics.nextAttemptAt; this.emitEvent('circuit_breaker_opened', 'automatic', { previousState: marine_constants_1.CircuitBreakerState.OPEN, newState: marine_constants_1.CircuitBreakerState.HALF_OPEN, reason: 'Timeout period elapsed, testing service' }, 'info'); } /** * Transition to CLOSED state */ transitionToClosed() { this.metrics.state = marine_constants_1.CircuitBreakerState.CLOSED; this.metrics.stateChangedAt = new Date(); delete this.metrics.nextAttemptAt; this.emitEvent('circuit_breaker_closed', 'automatic', { successCount: this.calculateRecentSuccesses(), reason: 'Service recovered successfully' }, 'info'); } /** * Calculate recent successes (for half-open state) */ calculateRecentSuccesses() { // Simplified: return recent success count // In a full implementation, this would track recent operations return this.metrics.successfulRequests; } /** * Update average response time */ updateAverageResponseTime(executionTime) { const totalTime = this.metrics.averageResponseTime * (this.metrics.totalRequests - 1); this.metrics.averageResponseTime = (totalTime + executionTime) / this.metrics.totalRequests; } /** * Adjust configuration for marine conditions */ adjustForMarineConditions() { if (!this.marineEnvironment) return; // Adjust thresholds based on expected failure rate const baseFailureThreshold = this.config.failureThreshold; const adjustedThreshold = Math.ceil(baseFailureThreshold * this.marineEnvironment.expectedFailureRate); // Don't make it too sensitive or too tolerant this.config.failureThreshold = Math.max(3, Math.min(adjustedThreshold, 20)); // Adjust timeout based on conditions if (this.marineEnvironment.criticalOperationsOnly) { this.config.environmentalAdjustments.roughSeas = true; this.config.environmentalAdjustments.limitedConnectivity = true; } } /** * Get current marine context */ getMarineContext() { const connectivityQuality = this.marineEnvironment?.connectivityQuality ?? 0; const connectivityStatus = connectivityQuality > 0.7 ? 'online' : connectivityQuality > 0.3 ? 'intermittent' : 'offline'; return { operationalContext: this.config.operationalContext, environmentalConditions: this.marineEnvironment || {}, powerStatus: this.marineEnvironment?.powerStatus || 'normal', connectivityStatus }; } /** * Emit resilience event */ emitEvent(eventType, component, details, severity) { const event = { timestamp: new Date(), eventType, component, details, severity, marineContext: { operationalContext: this.config.operationalContext, failureType: details['failureType'], environmentalImpact: this.config.environmentalAdjustments.roughSeas, safetyImpact: this.config.operationalContext === marine_constants_1.OperationalContext.EMERGENCY } }; this.eventListeners.forEach(listener => { try { listener(event); } catch (error) { console.error('Error in circuit breaker event listener:', error); } }); } /** * Initialize metrics */ initializeMetrics() { return { state: marine_constants_1.CircuitBreakerState.CLOSED, totalRequests: 0, successfulRequests: 0, failedRequests: 0, rejectedRequests: 0, averageResponseTime: 0, slowCalls: 0, environmentalFailures: 0, powerRelatedFailures: 0, connectivityFailures: 0, sensorFailures: 0, stateChangedAt: new Date() }; } } exports.MarineCircuitBreaker = MarineCircuitBreaker; //# sourceMappingURL=MarineCircuitBreaker.js.map