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