@sailboat-computer/resilience
Version:
Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations
488 lines (432 loc) • 12.9 kB
text/typescript
/**
* Bulkhead manager for coordinating multiple resource pools
*/
import { ResourcePool, ResourceRequest, ResourceAllocation } from './ResourcePool';
import {
BulkheadConfig,
ResourcePoolMetrics,
ResilienceEvent,
MarineEnvironmentStatus
} from '../types';
import {
OperationalContext,
OperationalContextType
} from '../types/marine-constants';
/**
* Bulkhead execution context
*/
export interface BulkheadExecutionContext {
poolName: string;
priority: 'critical' | 'normal' | 'low';
marineSystemType: 'navigation' | 'safety' | 'comfort' | 'maintenance';
operationalContext: OperationalContextType;
timeoutMs?: number;
}
/**
* Bulkhead execution result
*/
export interface BulkheadExecutionResult<T> {
success: boolean;
data?: T;
error?: Error;
resourceAllocation: ResourceAllocation;
poolMetrics: ResourcePoolMetrics;
executionTime: number;
}
/**
* Manager for coordinating multiple bulkhead resource pools
*/
export class BulkheadManager {
private static instance: BulkheadManager | null = null;
private pools: Map<string, ResourcePool> = new Map();
private eventListeners: ((event: ResilienceEvent) => void)[] = [];
private marineEnvironment?: MarineEnvironmentStatus;
/**
* Get the singleton instance of BulkheadManager
* This ensures only one instance exists across the application
*/
public static getInstance(): BulkheadManager {
if (!BulkheadManager.instance) {
BulkheadManager.instance = new BulkheadManager();
}
return BulkheadManager.instance;
}
constructor() {
// Initialize with default marine resource pools
this.initializeDefaultPools();
}
/**
* Execute operation with bulkhead protection
*/
async execute<T>(
operation: () => Promise<T>,
context: BulkheadExecutionContext
): Promise<BulkheadExecutionResult<T>> {
const startTime = Date.now();
const pool = this.getPool(context.poolName);
if (!pool) {
return {
success: false,
error: new Error(`Resource pool '${context.poolName}' not found`),
resourceAllocation: {
granted: false,
waitTime: 0,
reason: 'Pool not found'
},
poolMetrics: this.getEmptyMetrics(context.poolName),
executionTime: Date.now() - startTime
};
}
// Request resource from pool
const resourceRequest: Omit<ResourceRequest, 'id' | 'requestedAt'> = {
priority: context.priority,
operationalContext: context.operationalContext,
timeoutMs: context.timeoutMs || 30000, // Default 30 seconds
marineSystemType: context.marineSystemType
};
const allocation = await pool.requestResource(resourceRequest);
if (!allocation.granted) {
return {
success: false,
error: new Error(`Resource allocation failed: ${allocation.reason}`),
resourceAllocation: allocation,
poolMetrics: pool.getMetrics(),
executionTime: Date.now() - startTime
};
}
// Execute operation with resource
try {
const result = await operation();
const executionTime = Date.now() - startTime;
// Release resource
if (allocation.resourceId) {
pool.releaseResource(allocation.resourceId);
}
return {
success: true,
data: result,
resourceAllocation: allocation,
poolMetrics: pool.getMetrics(),
executionTime
};
} catch (error) {
const executionTime = Date.now() - startTime;
// Release resource on error
if (allocation.resourceId) {
pool.releaseResource(allocation.resourceId);
}
return {
success: false,
error: error as Error,
resourceAllocation: allocation,
poolMetrics: pool.getMetrics(),
executionTime
};
}
}
/**
* Create or update a resource pool
*/
createPool(config: BulkheadConfig): void {
const pool = new ResourcePool(config);
// Forward events from pool
pool.onEvent((event) => {
this.emitEvent(event);
});
// Update with current marine environment
if (this.marineEnvironment) {
pool.updateMarineEnvironment(this.marineEnvironment);
}
this.pools.set(config.poolName, pool);
}
/**
* Get resource pool by name
*/
getPool(poolName: string): ResourcePool | undefined {
return this.pools.get(poolName);
}
/**
* Get all pool metrics
*/
getAllPoolMetrics(): Record<string, ResourcePoolMetrics> {
const metrics: Record<string, ResourcePoolMetrics> = {};
for (const [poolName, pool] of this.pools) {
metrics[poolName] = pool.getMetrics();
}
return metrics;
}
/**
* Get system-wide bulkhead status
*/
getSystemStatus() {
const poolStatuses: Record<string, any> = {};
let totalCapacity = 0;
let totalActive = 0;
let totalQueued = 0;
let totalRejected = 0;
let totalPowerImpact = 0;
for (const [poolName, pool] of this.pools) {
const status = pool.getStatus();
poolStatuses[poolName] = status;
totalCapacity += status.metrics.totalCapacity;
totalActive += status.metrics.activeRequests;
totalQueued += status.metrics.queuedRequests;
totalRejected += status.metrics.rejectedRequests;
totalPowerImpact += status.metrics.powerImpact;
}
return {
pools: poolStatuses,
systemTotals: {
totalCapacity,
totalActive,
totalQueued,
totalRejected,
totalPowerImpact,
utilizationPercentage: totalCapacity > 0 ? (totalActive / totalCapacity) * 100 : 0
},
marineEnvironment: this.marineEnvironment
};
}
/**
* Update marine environment for all pools
*/
updateMarineEnvironment(environment: MarineEnvironmentStatus): void {
this.marineEnvironment = environment;
for (const pool of this.pools.values()) {
pool.updateMarineEnvironment(environment);
}
// Emit environment update event
this.emitEvent({
timestamp: new Date(),
eventType: 'recovery',
component: 'bulkhead-manager',
details: {
action: 'marine_environment_updated',
seaState: environment.seaState,
powerStatus: environment.powerStatus,
criticalOperationsOnly: environment.criticalOperationsOnly
},
severity: 'info',
marineContext: {
operationalContext: environment.powerStatus === 'critical' ?
OperationalContext.EMERGENCY : OperationalContext.SAILING,
environmentalImpact: environment.seaState === 'very_rough',
safetyImpact: environment.criticalOperationsOnly
}
});
}
/**
* Emergency release all resources across all pools
*/
emergencyReleaseAll(reason: string = 'Emergency shutdown'): void {
let totalReleased = 0;
for (const [poolName, pool] of this.pools) {
const status = pool.getStatus();
const activeCount = status.metrics.activeRequests;
pool.forceReleaseAll(reason);
totalReleased += activeCount;
}
this.emitEvent({
timestamp: new Date(),
eventType: 'recovery',
component: 'bulkhead-manager',
details: {
action: 'emergency_release_all',
totalResourcesReleased: totalReleased,
reason
},
severity: 'critical',
marineContext: {
operationalContext: OperationalContext.EMERGENCY,
environmentalImpact: true,
safetyImpact: true
}
});
}
/**
* Add event listener
*/
onEvent(listener: (event: ResilienceEvent) => void): void {
this.eventListeners.push(listener);
}
/**
* Remove a resource pool
*/
removePool(poolName: string): boolean {
const pool = this.pools.get(poolName);
if (pool) {
// Force release all resources before removing
pool.forceReleaseAll('Pool being removed');
this.pools.delete(poolName);
return true;
}
return false;
}
/**
* Get pool names
*/
getPoolNames(): string[] {
return Array.from(this.pools.keys());
}
/**
* Check if pool exists
*/
hasPool(poolName: string): boolean {
return this.pools.has(poolName);
}
/**
* Initialize default marine resource pools
*/
private initializeDefaultPools(): void {
// Navigation pool - critical for vessel safety
this.createPool({
poolName: 'navigation',
maxConcurrentRequests: 8,
maxWaitTime: 5000, // 5 seconds
priority: 'critical',
marineSystemType: 'navigation',
powerConsumption: 'medium',
operationalDependency: 'essential'
});
// Safety pool - highest priority
this.createPool({
poolName: 'safety',
maxConcurrentRequests: 6,
maxWaitTime: 3000, // 3 seconds
priority: 'critical',
marineSystemType: 'safety',
powerConsumption: 'low',
operationalDependency: 'essential'
});
// Comfort pool - lower priority, can be throttled
this.createPool({
poolName: 'comfort',
maxConcurrentRequests: 4,
maxWaitTime: 15000, // 15 seconds
priority: 'low',
marineSystemType: 'comfort',
powerConsumption: 'high',
operationalDependency: 'optional'
});
// Maintenance pool - for system maintenance operations
this.createPool({
poolName: 'maintenance',
maxConcurrentRequests: 2,
maxWaitTime: 60000, // 1 minute
priority: 'normal',
marineSystemType: 'maintenance',
powerConsumption: 'high',
operationalDependency: 'important'
});
// Communication pool - for network operations
this.createPool({
poolName: 'communication',
maxConcurrentRequests: 3,
maxWaitTime: 30000, // 30 seconds
priority: 'normal',
marineSystemType: 'comfort', // Communication is comfort/convenience
powerConsumption: 'medium',
operationalDependency: 'important'
});
}
/**
* Emit resilience event
*/
private emitEvent(event: ResilienceEvent): void {
this.eventListeners.forEach(listener => {
try {
listener(event);
} catch (error) {
console.error('Error in bulkhead manager event listener:', error);
}
});
}
/**
* Get empty metrics for error cases
*/
private getEmptyMetrics(poolName: string): ResourcePoolMetrics {
return {
poolName,
totalCapacity: 0,
availableResources: 0,
activeRequests: 0,
queuedRequests: 0,
rejectedRequests: 0,
averageWaitTime: 0,
maxWaitTime: 0,
powerImpact: 0,
criticalRequestsActive: 0,
systemHealthImpact: 0
};
}
}
/**
* Default bulkhead manager instance
* Using the getInstance method ensures we always get the same instance
*/
export const defaultBulkheadManager = BulkheadManager.getInstance();
/**
* Convenience function to execute with bulkhead protection
* Always uses the singleton instance to ensure consistency
*/
export async function executeWithBulkhead<T>(
operation: () => Promise<T>,
context: BulkheadExecutionContext
): Promise<BulkheadExecutionResult<T>> {
return BulkheadManager.getInstance().execute(operation, context);
}
/**
* Pre-configured bulkhead execution contexts
*/
export const BulkheadContexts = {
/**
* Navigation operations (GPS, compass, autopilot)
*/
navigation: (operationalContext: OperationalContextType = OperationalContext.SAILING): BulkheadExecutionContext => ({
poolName: 'navigation',
priority: 'critical',
marineSystemType: 'navigation',
operationalContext,
timeoutMs: 5000
}),
/**
* Safety operations (anchor alarm, collision avoidance)
*/
safety: (operationalContext: OperationalContextType = OperationalContext.SAILING): BulkheadExecutionContext => ({
poolName: 'safety',
priority: 'critical',
marineSystemType: 'safety',
operationalContext,
timeoutMs: 3000
}),
/**
* Comfort operations (lighting, entertainment, climate)
*/
comfort: (operationalContext: OperationalContextType = OperationalContext.SAILING): BulkheadExecutionContext => ({
poolName: 'comfort',
priority: 'low',
marineSystemType: 'comfort',
operationalContext,
timeoutMs: 15000
}),
/**
* Maintenance operations (diagnostics, updates)
*/
maintenance: (operationalContext: OperationalContextType = OperationalContext.MAINTENANCE): BulkheadExecutionContext => ({
poolName: 'maintenance',
priority: 'normal',
marineSystemType: 'maintenance',
operationalContext,
timeoutMs: 60000
}),
/**
* Communication operations (weather, email, messaging)
*/
communication: (operationalContext: OperationalContextType = OperationalContext.SAILING): BulkheadExecutionContext => ({
poolName: 'communication',
priority: 'normal',
marineSystemType: 'comfort',
operationalContext,
timeoutMs: 30000
})
};