UNPKG

@sailboat-computer/resilience

Version:

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

556 lines (481 loc) 15 kB
/** * Resource pool implementation for bulkhead pattern */ import { BulkheadConfig, ResourcePoolMetrics, ResilienceResult, ResilienceEvent, MarineEnvironmentStatus } from '../types'; import { OperationalContext, OperationalContextType } from '../types/marine-constants'; /** * Resource request with priority and context */ export interface ResourceRequest { id: string; priority: 'critical' | 'normal' | 'low'; operationalContext: OperationalContextType; requestedAt: Date; timeoutMs: number; marineSystemType: 'navigation' | 'safety' | 'comfort' | 'maintenance'; } /** * Resource allocation result */ export interface ResourceAllocation { granted: boolean; resourceId?: string; waitTime: number; queuePosition?: number; estimatedWaitTime?: number; reason?: string; } /** * Active resource usage tracking */ interface ActiveResource { resourceId: string; request: ResourceRequest; allocatedAt: Date; powerConsumption: number; systemHealthImpact: number; } /** * Resource pool for marine bulkhead pattern */ export class ResourcePool { private config: BulkheadConfig; private metrics: ResourcePoolMetrics; private activeResources: Map<string, ActiveResource> = new Map(); private requestQueue: ResourceRequest[] = []; private eventListeners: ((event: ResilienceEvent) => void)[] = []; private marineEnvironment?: MarineEnvironmentStatus; private nextResourceId = 1; constructor(config: BulkheadConfig) { this.config = { ...config }; this.metrics = this.initializeMetrics(); } /** * Request a resource from the pool */ async requestResource( request: Omit<ResourceRequest, 'id' | 'requestedAt'> ): Promise<ResourceAllocation> { const fullRequest: ResourceRequest = { ...request, id: `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, requestedAt: new Date() }; const startTime = Date.now(); // Check if resource can be allocated immediately if (this.canAllocateImmediately(fullRequest)) { const allocation = this.allocateResource(fullRequest); const waitTime = Date.now() - startTime; this.updateMetrics(waitTime, false); return { granted: true, resourceId: allocation.resourceId, waitTime, reason: 'Immediate allocation' }; } // Check if request should be queued or rejected if (!this.shouldQueue(fullRequest)) { this.metrics.rejectedRequests++; this.emitEvent('bulkhead_rejection', fullRequest.id, { reason: 'Pool capacity exceeded', poolName: this.config.poolName, priority: fullRequest.priority, marineSystemType: fullRequest.marineSystemType }, 'warning'); return { granted: false, waitTime: Date.now() - startTime, reason: 'Pool capacity exceeded - request rejected' }; } // Queue the request return this.queueRequest(fullRequest); } /** * Release a resource back to the pool */ releaseResource(resourceId: string): boolean { const activeResource = this.activeResources.get(resourceId); if (!activeResource) { return false; } // Remove from active resources this.activeResources.delete(resourceId); this.metrics.activeRequests--; // Update metrics const usageTime = Date.now() - activeResource.allocatedAt.getTime(); this.updateResourceMetrics(activeResource, usageTime); // Process next request in queue if any this.processQueue(); this.emitEvent('recovery', resourceId, { poolName: this.config.poolName, usageTime, marineSystemType: activeResource.request.marineSystemType }, 'info'); return true; } /** * Update marine environment for adaptive behavior */ updateMarineEnvironment(environment: MarineEnvironmentStatus): void { this.marineEnvironment = environment; this.adjustPoolCapacity(); } /** * Get current pool metrics */ getMetrics(): ResourcePoolMetrics { return { ...this.metrics }; } /** * Get current pool status */ getStatus() { return { config: this.config, metrics: this.metrics, activeResources: Array.from(this.activeResources.values()).map(r => ({ resourceId: r.resourceId, priority: r.request.priority, marineSystemType: r.request.marineSystemType, allocatedAt: r.allocatedAt, powerConsumption: r.powerConsumption })), queuedRequests: this.requestQueue.map(r => ({ id: r.id, priority: r.priority, marineSystemType: r.marineSystemType, waitTime: Date.now() - r.requestedAt.getTime() })) }; } /** * Add event listener */ onEvent(listener: (event: ResilienceEvent) => void): void { this.eventListeners.push(listener); } /** * Force release all resources (emergency/maintenance) */ forceReleaseAll(reason: string = 'Emergency release'): void { const releasedCount = this.activeResources.size; this.activeResources.clear(); this.requestQueue = []; this.metrics.activeRequests = 0; this.metrics.queuedRequests = 0; this.emitEvent('recovery', 'pool-reset', { poolName: this.config.poolName, releasedCount, reason }, 'warning'); } /** * Check if resource can be allocated immediately */ private canAllocateImmediately(request: ResourceRequest): boolean { // Check basic capacity if (this.metrics.activeRequests >= this.config.maxConcurrentRequests) { return false; } // Check marine-specific constraints if (this.marineEnvironment?.criticalOperationsOnly) { return request.priority === 'critical' || request.marineSystemType === 'safety' || request.marineSystemType === 'navigation'; } // Check power constraints if (this.marineEnvironment?.powerStatus === 'critical') { const powerConsumption = this.estimatePowerConsumption(request); const currentPowerUsage = this.metrics.powerImpact; if (currentPowerUsage + powerConsumption > this.getMaxPowerAllowance()) { return false; } } return true; } /** * Allocate a resource */ private allocateResource(request: ResourceRequest): { resourceId: string } { const resourceId = `${this.config.poolName}-${this.nextResourceId++}`; const activeResource: ActiveResource = { resourceId, request, allocatedAt: new Date(), powerConsumption: this.estimatePowerConsumption(request), systemHealthImpact: this.estimateSystemHealthImpact(request) }; this.activeResources.set(resourceId, activeResource); this.metrics.activeRequests++; this.metrics.powerImpact += activeResource.powerConsumption; this.metrics.systemHealthImpact = Math.max( this.metrics.systemHealthImpact, activeResource.systemHealthImpact ); if (request.priority === 'critical') { this.metrics.criticalRequestsActive++; } return { resourceId }; } /** * Check if request should be queued */ private shouldQueue(request: ResourceRequest): boolean { // Always queue critical requests if (request.priority === 'critical') { return true; } // Check queue capacity const maxQueueSize = Math.floor(this.config.maxConcurrentRequests * 2); if (this.requestQueue.length >= maxQueueSize) { return false; } // Check wait time limits if (request.timeoutMs < this.config.maxWaitTime) { return false; } return true; } /** * Queue a request */ private async queueRequest(request: ResourceRequest): Promise<ResourceAllocation> { // Insert request in priority order this.insertRequestByPriority(request); this.metrics.queuedRequests++; const queuePosition = this.requestQueue.findIndex(r => r.id === request.id) + 1; const estimatedWaitTime = this.estimateWaitTime(queuePosition); // Wait for allocation or timeout return new Promise((resolve) => { const startTime = Date.now(); const timeoutId = setTimeout(() => { // Remove from queue if timed out this.removeFromQueue(request.id); resolve({ granted: false, waitTime: Date.now() - startTime, reason: 'Request timed out in queue' }); }, request.timeoutMs); // Store timeout ID for cleanup (request as any).timeoutId = timeoutId; (request as any).resolve = (allocation: ResourceAllocation) => { clearTimeout(timeoutId); resolve(allocation); }; }); } /** * Process queued requests */ private processQueue(): void { while (this.requestQueue.length > 0) { const nextRequest = this.requestQueue[0]; if (!nextRequest) break; if (this.canAllocateImmediately(nextRequest)) { // Remove from queue this.requestQueue.shift(); this.metrics.queuedRequests--; // Allocate resource const allocation = this.allocateResource(nextRequest); const waitTime = Date.now() - nextRequest.requestedAt.getTime(); this.updateMetrics(waitTime, true); // Resolve the promise if ((nextRequest as any).resolve) { (nextRequest as any).resolve({ granted: true, resourceId: allocation.resourceId, waitTime, reason: 'Allocated from queue' }); } } else { // Can't allocate more resources break; } } } /** * Insert request by priority */ private insertRequestByPriority(request: ResourceRequest): void { const priorityOrder = { critical: 0, normal: 1, low: 2 }; const requestPriority = priorityOrder[request.priority]; let insertIndex = this.requestQueue.length; for (let i = 0; i < this.requestQueue.length; i++) { const queuedPriority = priorityOrder[this.requestQueue[i]!.priority]; if (requestPriority < queuedPriority) { insertIndex = i; break; } } this.requestQueue.splice(insertIndex, 0, request); } /** * Remove request from queue */ private removeFromQueue(requestId: string): boolean { const index = this.requestQueue.findIndex(r => r.id === requestId); if (index >= 0) { this.requestQueue.splice(index, 1); this.metrics.queuedRequests--; this.metrics.rejectedRequests++; return true; } return false; } /** * Estimate power consumption for request */ private estimatePowerConsumption(request: ResourceRequest): number { const basePower = { navigation: 5, // Watts safety: 3, comfort: 10, maintenance: 15 }; let power = basePower[request.marineSystemType] || 5; // Adjust for priority if (request.priority === 'critical') { power *= 1.5; // Critical operations may use more power } return power; } /** * Estimate system health impact */ private estimateSystemHealthImpact(request: ResourceRequest): number { const baseImpact = { navigation: 0.1, safety: 0.05, comfort: 0.3, maintenance: 0.5 }; return baseImpact[request.marineSystemType] || 0.1; } /** * Get maximum power allowance based on marine environment */ private getMaxPowerAllowance(): number { if (!this.marineEnvironment) { return 100; // Default 100W } switch (this.marineEnvironment.powerStatus) { case 'critical': return 30; // Minimal power usage case 'conservation': return 60; // Reduced power usage case 'normal': default: return 100; // Normal power usage } } /** * Estimate wait time for queue position */ private estimateWaitTime(queuePosition: number): number { if (this.metrics.averageWaitTime === 0) { return queuePosition * 5000; // Default 5 seconds per position } return queuePosition * this.metrics.averageWaitTime; } /** * Adjust pool capacity based on marine conditions */ private adjustPoolCapacity(): void { if (!this.marineEnvironment) return; // Reduce capacity in critical conditions if (this.marineEnvironment.criticalOperationsOnly) { this.config.maxConcurrentRequests = Math.max( Math.floor(this.config.maxConcurrentRequests * 0.5), 2 // Minimum 2 resources ); } // Adjust wait times based on conditions if (this.marineEnvironment.seaState === 'very_rough') { this.config.maxWaitTime *= 2; // Allow longer waits in rough seas } } /** * Update metrics */ private updateMetrics(waitTime: number, fromQueue: boolean): void { // Update wait time metrics const totalWaitTime = this.metrics.averageWaitTime * this.metrics.totalCapacity; this.metrics.averageWaitTime = (totalWaitTime + waitTime) / (this.metrics.totalCapacity + 1); this.metrics.maxWaitTime = Math.max(this.metrics.maxWaitTime, waitTime); } /** * Update resource-specific metrics */ private updateResourceMetrics(resource: ActiveResource, usageTime: number): void { this.metrics.powerImpact -= resource.powerConsumption; if (resource.request.priority === 'critical') { this.metrics.criticalRequestsActive--; } // Recalculate system health impact this.metrics.systemHealthImpact = Math.max( ...Array.from(this.activeResources.values()).map(r => r.systemHealthImpact), 0 ); } /** * Emit resilience event */ private emitEvent( eventType: ResilienceEvent['eventType'], component: string, details: Record<string, any>, severity: ResilienceEvent['severity'] ): void { const event: ResilienceEvent = { timestamp: new Date(), eventType, component, details, severity, marineContext: { operationalContext: this.marineEnvironment?.powerStatus === 'critical' ? OperationalContext.EMERGENCY : OperationalContext.SAILING, environmentalImpact: this.marineEnvironment?.seaState === 'very_rough', safetyImpact: details['marineSystemType'] === 'safety' } }; this.eventListeners.forEach(listener => { try { listener(event); } catch (error) { console.error('Error in resource pool event listener:', error); } }); } /** * Initialize metrics */ private initializeMetrics(): ResourcePoolMetrics { return { poolName: this.config.poolName, totalCapacity: this.config.maxConcurrentRequests, availableResources: this.config.maxConcurrentRequests, activeRequests: 0, queuedRequests: 0, rejectedRequests: 0, averageWaitTime: 0, maxWaitTime: 0, powerImpact: 0, criticalRequestsActive: 0, systemHealthImpact: 0 }; } }