UNPKG

@sailboat-computer/resilience

Version:

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

444 lines 16.2 kB
"use strict"; /** * Hierarchical timeout management for marine operations */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TimeoutContexts = exports.executeWithTimeout = exports.defaultTimeoutManager = exports.TimeoutManager = void 0; const marine_constants_1 = require("../types/marine-constants"); /** * Hierarchical timeout manager for marine operations */ class TimeoutManager { constructor(config = {}) { this.activeTimeouts = new Map(); this.eventListeners = []; this.nextTimeoutId = 1; this.config = { operationTimeout: 5000, // 5 seconds serviceTimeout: 30000, // 30 seconds systemTimeout: 120000, // 2 minutes environmentalMultiplier: 1.0, operationalContext: marine_constants_1.OperationalContext.SAILING, maxRetries: 3, retryBackoffMultiplier: 2.0, maxRetryDelay: 30000, // 30 seconds criticalOperationTimeout: 2000, // 2 seconds sensorReadTimeout: 3000, // 3 seconds networkOperationTimeout: 15000, // 15 seconds ...config }; this.metrics = this.initializeMetrics(); } /** * Execute operation with hierarchical timeout protection */ async execute(operation, context) { const startTime = Date.now(); let retryCount = 0; let lastError; // Calculate timeout based on context and marine conditions const timeoutMs = this.calculateTimeout(context); while (retryCount <= this.config.maxRetries) { try { const result = await this.executeWithTimeout(operation, timeoutMs, context); const executionTime = Date.now() - startTime; // Update metrics for successful operation this.updateMetrics(executionTime, false, retryCount); return { success: true, data: result, executionTime, timeoutApplied: timeoutMs, timedOut: false, retryCount }; } catch (error) { lastError = error; const isTimeout = this.isTimeoutError(error); if (isTimeout) { this.metrics.timedOutOperations++; // Update marine-specific timeout counters this.updateMarineTimeoutCounters(context); } // Check if we should retry if (retryCount < this.config.maxRetries && this.shouldRetry(error, context)) { retryCount++; this.metrics.totalRetries++; // Calculate retry delay with exponential backoff const retryDelay = this.calculateRetryDelay(retryCount); this.emitEvent('retry', context.operationName, { retryCount, retryDelay, error: error.message, isTimeout, marineSystemType: context.marineSystemType }, 'warning'); // Wait before retry await this.delay(retryDelay); } else { break; } } } // All retries exhausted const executionTime = Date.now() - startTime; const isTimeout = lastError ? this.isTimeoutError(lastError) : false; this.updateMetrics(executionTime, isTimeout, retryCount); if (retryCount > 0) { this.metrics.failedRetries++; } return { success: false, error: lastError || new Error('Unknown error occurred'), executionTime, timeoutApplied: timeoutMs, timedOut: isTimeout, retryCount }; } /** * Update marine environment for adaptive timeouts */ updateMarineEnvironment(environment) { this.marineEnvironment = environment; // Update environmental multiplier based on conditions this.config.environmentalMultiplier = environment.recommendedTimeoutMultiplier; this.emitEvent('recovery', 'timeout-manager', { action: 'marine_environment_updated', environmentalMultiplier: this.config.environmentalMultiplier, seaState: environment.seaState, powerStatus: environment.powerStatus }, 'info'); } /** * Get current timeout metrics */ getMetrics() { return { ...this.metrics }; } /** * Get active timeouts status */ getActiveTimeouts() { return Array.from(this.activeTimeouts.values()).map(timeout => ({ id: timeout.id, operationName: timeout.operationName, startTime: timeout.startTime, timeoutMs: timeout.timeoutMs, elapsedTime: Date.now() - timeout.startTime.getTime(), remainingTime: Math.max(0, timeout.timeoutMs - (Date.now() - timeout.startTime.getTime())), context: { operationalContext: timeout.context.operationalContext, marineSystemType: timeout.context.marineSystemType, priority: timeout.context.priority } })); } /** * Cancel all active timeouts (emergency) */ cancelAllTimeouts(reason = 'Emergency cancellation') { const cancelledCount = this.activeTimeouts.size; for (const timeout of this.activeTimeouts.values()) { clearTimeout(timeout.timer); } this.activeTimeouts.clear(); this.emitEvent('recovery', 'timeout-manager', { action: 'cancel_all_timeouts', cancelledCount, reason }, 'warning'); } /** * Add event listener */ onEvent(listener) { this.eventListeners.push(listener); } /** * Execute operation with timeout */ async executeWithTimeout(operation, timeoutMs, context) { const timeoutId = `timeout-${this.nextTimeoutId++}`; return new Promise((resolve, reject) => { // Create timeout const timer = setTimeout(() => { this.activeTimeouts.delete(timeoutId); reject(new Error(`Operation '${context.operationName}' timed out after ${timeoutMs}ms`)); }, timeoutMs); // Track active timeout const activeTimeout = { id: timeoutId, operationName: context.operationName, startTime: new Date(), timeoutMs, context, timer }; this.activeTimeouts.set(timeoutId, activeTimeout); // Execute operation operation() .then(result => { clearTimeout(timer); this.activeTimeouts.delete(timeoutId); resolve(result); }) .catch(error => { clearTimeout(timer); this.activeTimeouts.delete(timeoutId); reject(error); }); }); } /** * Calculate timeout based on context and marine conditions */ calculateTimeout(context) { let baseTimeout; // Determine base timeout based on operation type and priority if (context.priority === 'critical') { baseTimeout = this.config.criticalOperationTimeout; } else { switch (context.marineSystemType) { case 'navigation': baseTimeout = this.config.sensorReadTimeout; break; case 'safety': baseTimeout = this.config.criticalOperationTimeout; break; case 'comfort': baseTimeout = this.config.networkOperationTimeout; break; case 'maintenance': baseTimeout = this.config.operationTimeout * 3; // Longer for maintenance break; default: baseTimeout = this.config.operationTimeout; } } // Apply custom timeouts if provided if (context.customTimeouts?.operationTimeout) { baseTimeout = context.customTimeouts.operationTimeout; } // Apply environmental multiplier let timeout = baseTimeout * this.config.environmentalMultiplier; // Apply operational context adjustments switch (context.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; case marine_constants_1.OperationalContext.DOCKED: timeout *= 1.2; // Slightly more relaxed when docked break; } // Apply marine environment specific adjustments if (this.marineEnvironment) { if (this.marineEnvironment.criticalOperationsOnly && context.priority !== 'critical') { timeout *= 0.5; // Reduce timeouts for non-critical operations } if (this.marineEnvironment.powerStatus === 'critical') { timeout *= 0.7; // Reduce timeouts to save power } } // Ensure timeout is within reasonable bounds const minTimeout = context.priority === 'critical' ? 1000 : 2000; // 1-2 seconds minimum const maxTimeout = context.marineSystemType === 'maintenance' ? 300000 : 60000; // 1-5 minutes maximum return Math.max(minTimeout, Math.min(timeout, maxTimeout)); } /** * Check if error is a timeout error */ isTimeoutError(error) { return error.message.toLowerCase().includes('timeout') || error.message.toLowerCase().includes('timed out'); } /** * Check if operation should be retried */ shouldRetry(error, context) { // Always retry timeouts if (this.isTimeoutError(error)) { return true; } // Don't retry critical safety operations - fail fast if (context.marineSystemType === 'safety' && context.priority === 'critical') { return false; } // Retry network-related errors if (error.message.toLowerCase().includes('network') || error.message.toLowerCase().includes('connection')) { return true; } // Don't retry validation errors if (error.message.toLowerCase().includes('validation')) { return false; } // Default: retry for most errors return true; } /** * Calculate retry delay with exponential backoff */ calculateRetryDelay(retryCount) { const baseDelay = 1000; // 1 second const delay = baseDelay * Math.pow(this.config.retryBackoffMultiplier, retryCount - 1); // Add jitter to prevent thundering herd const jitter = Math.random() * 0.1 * delay; return Math.min(delay + jitter, this.config.maxRetryDelay); } /** * Update marine-specific timeout counters */ updateMarineTimeoutCounters(context) { if (this.marineEnvironment?.seaState === 'very_rough' || this.marineEnvironment?.weather === 'storm') { this.metrics.environmentalTimeouts++; } if (context.priority === 'critical') { this.metrics.criticalOperationTimeouts++; } if (context.marineSystemType === 'navigation' || context.operationName.toLowerCase().includes('sensor')) { this.metrics.sensorTimeouts++; } } /** * Update metrics */ updateMetrics(executionTime, timedOut, retryCount) { this.metrics.totalOperations++; if (timedOut) { this.metrics.timedOutOperations++; } // Update execution time metrics const totalTime = this.metrics.averageOperationTime * (this.metrics.totalOperations - 1); this.metrics.averageOperationTime = (totalTime + executionTime) / this.metrics.totalOperations; this.metrics.maxOperationTime = Math.max(this.metrics.maxOperationTime, executionTime); // Update retry metrics if (retryCount > 0) { if (!timedOut) { this.metrics.successfulRetries++; } } } /** * Delay helper function */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Emit resilience event */ emitEvent(eventType, component, details, severity) { const event = { timestamp: new Date(), eventType, component, details, severity, marineContext: { operationalContext: this.config.operationalContext, environmentalImpact: this.marineEnvironment?.seaState === 'very_rough', safetyImpact: details['marineSystemType'] === 'safety' } }; this.eventListeners.forEach(listener => { try { listener(event); } catch (error) { console.error('Error in timeout manager event listener:', error); } }); } /** * Initialize metrics */ initializeMetrics() { return { totalOperations: 0, timedOutOperations: 0, averageOperationTime: 0, maxOperationTime: 0, totalRetries: 0, successfulRetries: 0, failedRetries: 0, environmentalTimeouts: 0, criticalOperationTimeouts: 0, sensorTimeouts: 0 }; } } exports.TimeoutManager = TimeoutManager; /** * Default timeout manager instance */ exports.defaultTimeoutManager = new TimeoutManager(); /** * Convenience function to execute with timeout protection */ async function executeWithTimeout(operation, context) { return exports.defaultTimeoutManager.execute(operation, context); } exports.executeWithTimeout = executeWithTimeout; /** * Pre-configured timeout contexts for marine operations */ exports.TimeoutContexts = { /** * Navigation sensor reading */ navigationSensor: (operationName, operationalContext = marine_constants_1.OperationalContext.SAILING) => ({ operationName, operationalContext, marineSystemType: 'navigation', priority: 'critical' }), /** * Safety system operation */ safetyOperation: (operationName, operationalContext = marine_constants_1.OperationalContext.SAILING) => ({ operationName, operationalContext, marineSystemType: 'safety', priority: 'critical' }), /** * Network communication */ networkOperation: (operationName, operationalContext = marine_constants_1.OperationalContext.SAILING) => ({ operationName, operationalContext, marineSystemType: 'comfort', priority: 'normal' }), /** * Maintenance operation */ maintenanceOperation: (operationName) => ({ operationName, operationalContext: marine_constants_1.OperationalContext.MAINTENANCE, marineSystemType: 'maintenance', priority: 'normal' }), /** * Comfort system operation */ comfortOperation: (operationName, operationalContext = marine_constants_1.OperationalContext.SAILING) => ({ operationName, operationalContext, marineSystemType: 'comfort', priority: 'low' }) }; //# sourceMappingURL=TimeoutManager.js.map