@sailboat-computer/resilience
Version:
Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations
556 lines (481 loc) • 15 kB
text/typescript
/**
* 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
};
}
}