@sailboat-computer/resilience
Version:
Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations
444 lines • 16.2 kB
JavaScript
"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