UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

269 lines 10 kB
/** * Resource manager for preventing conflicts and deadlocks */ import { SystemEvents } from "../utils/types.js"; import { ResourceLockError } from "../utils/errors.js"; import { delay } from "../utils/helpers.js"; /** * Resource manager implementation */ export class ResourceManager { config; eventBus; logger; resources = new Map(); locks = new Map(); // resourceId -> agentId waitQueue = new Map(); // resourceId -> queue agentResources = new Map(); // agentId -> resourceIds constructor(config, eventBus, logger) { this.config = config; this.eventBus = eventBus; this.logger = logger; } initialize() { this.logger.info("Initializing resource manager"); // Set up periodic cleanup setInterval(() => this.cleanup(), 30000); // Every 30 seconds return Promise.resolve(); } async shutdown() { this.logger.info("Shutting down resource manager"); // Release all locks for (const [resourceId, agentId] of this.locks) { await this.release(resourceId, agentId); } this.resources.clear(); this.locks.clear(); this.waitQueue.clear(); this.agentResources.clear(); } async acquire(resourceId, agentId, priority = 0) { this.logger.debug("Resource acquisition requested", { resourceId, agentId }); // Check if resource exists if (!this.resources.has(resourceId)) { this.resources.set(resourceId, { id: resourceId, type: "generic", locked: false, }); } const resource = this.resources.get(resourceId); if (!resource) { throw new Error(`Resource ${resourceId} not found after creation`); } // Check if already locked by this agent if (this.locks.get(resourceId) === agentId) { this.logger.debug("Resource already locked by agent", { resourceId, agentId }); return; } // Try to acquire lock if (!resource.locked) { await this.lockResource(resourceId, agentId); return; } // Add to wait queue const request = { agentId, resourceId, timestamp: new Date(), priority, }; if (!this.waitQueue.has(resourceId)) { this.waitQueue.set(resourceId, []); } const queue = this.waitQueue.get(resourceId); if (!queue) { throw new Error(`Wait queue for resource ${resourceId} not found after creation`); } queue.push(request); // Sort by priority and timestamp queue.sort((a, b) => { if (a.priority !== b.priority) { return b.priority - a.priority; // Higher priority first } return a.timestamp.getTime() - b.timestamp.getTime(); // Earlier first }); this.logger.info("Agent added to resource wait queue", { resourceId, agentId, queueLength: queue.length, }); // Wait for resource with timeout const startTime = Date.now(); while (Date.now() - startTime < this.config.resourceTimeout) { // Check if we're next in queue and resource is available const nextRequest = queue[0]; if (nextRequest?.agentId === agentId && !resource.locked) { // Remove from queue and acquire queue.shift(); await this.lockResource(resourceId, agentId); return; } // Check if our request is still in queue const ourRequest = queue.find(req => req.agentId === agentId); if (!ourRequest) { // Request was removed (possibly by cleanup) throw new ResourceLockError("Resource request cancelled"); } await delay(100); } // Timeout - remove from queue const index = queue.findIndex(req => req.agentId === agentId); if (index !== -1) { queue.splice(index, 1); } throw new ResourceLockError(`Resource acquisition timeout for ${resourceId}`, { resourceId, agentId, timeout: this.config.resourceTimeout }); } async release(resourceId, agentId) { this.logger.debug("Resource release requested", { resourceId, agentId }); const currentLock = this.locks.get(resourceId); if (currentLock !== agentId) { this.logger.warn("Attempted to release unowned resource", { resourceId, agentId, currentLock, }); return; } // Release the lock this.unlockResource(resourceId, agentId); // Process wait queue const queue = this.waitQueue.get(resourceId); if (queue && queue.length > 0) { const nextRequest = queue.shift(); if (!nextRequest) { return; // Queue was empty after all } // Grant lock to next in queue await this.lockResource(resourceId, nextRequest.agentId); } } async releaseAllForAgent(agentId) { const resources = this.agentResources.get(agentId); if (!resources) { return; } this.logger.info("Releasing all resources for agent", { agentId, resourceCount: resources.size, }); const promises = Array.from(resources).map(resourceId => this.release(resourceId, agentId)); await Promise.all(promises); this.agentResources.delete(agentId); } getAllocations() { return new Map(this.locks); } getWaitingRequests() { const waiting = new Map(); for (const [resourceId, queue] of this.waitQueue) { if (queue.length > 0) { waiting.set(queue[0].agentId, [...(waiting.get(queue[0].agentId) ?? []), resourceId]); } } return waiting; } getHealthStatus() { const totalResources = this.resources.size; const lockedResources = this.locks.size; const waitingAgents = new Set(); let totalWaiting = 0; for (const queue of this.waitQueue.values()) { totalWaiting += queue.length; queue.forEach(req => waitingAgents.add(req.agentId)); } return Promise.resolve({ healthy: true, metrics: { totalResources, lockedResources, freeResources: totalResources - lockedResources, waitingAgents: waitingAgents.size, totalWaitingRequests: totalWaiting, }, }); } lockResource(resourceId, agentId) { const resource = this.resources.get(resourceId); if (!resource) { throw new Error(`Resource ${resourceId} not found`); } resource.locked = true; resource.lockedBy = agentId; resource.lockedAt = new Date(); this.locks.set(resourceId, agentId); // Track agent resources if (!this.agentResources.has(agentId)) { this.agentResources.set(agentId, new Set()); } const agentResourceSet = this.agentResources.get(agentId); if (!agentResourceSet) { throw new Error(`Agent resource set for ${agentId} not found after creation`); } agentResourceSet.add(resourceId); this.logger.info("Resource locked", { resourceId, agentId }); return Promise.resolve(); // Emit event this.eventBus.emit(SystemEvents.RESOURCE_ACQUIRED, { resourceId, agentId }); } unlockResource(resourceId, agentId) { const resource = this.resources.get(resourceId); if (!resource) { return; } resource.locked = false; delete resource.lockedBy; delete resource.lockedAt; this.locks.delete(resourceId); // Remove from agent resources this.agentResources.get(agentId)?.delete(resourceId); this.logger.info("Resource unlocked", { resourceId, agentId }); // Emit event this.eventBus.emit(SystemEvents.RESOURCE_RELEASED, { resourceId, agentId }); } performMaintenance() { this.logger.debug("Performing resource manager maintenance"); this.cleanup(); return Promise.resolve(); } cleanup() { const now = Date.now(); // Clean up stale wait requests for (const [resourceId, queue] of this.waitQueue) { const filtered = queue.filter(req => { const age = now - req.timestamp.getTime(); if (age > this.config.resourceTimeout) { this.logger.warn("Removing stale resource request", { resourceId, agentId: req.agentId, age, }); return false; } return true; }); if (filtered.length === 0) { this.waitQueue.delete(resourceId); } else { this.waitQueue.set(resourceId, filtered); } } // Clean up locks held too long for (const [resourceId, agentId] of this.locks) { const resource = this.resources.get(resourceId); if (resource?.lockedAt) { const lockAge = now - resource.lockedAt.getTime(); if (lockAge > this.config.resourceTimeout * 2) { this.logger.warn("Force releasing stale lock", { resourceId, agentId, lockAge, }); this.unlockResource(resourceId, agentId); } } } } } //# sourceMappingURL=resources.js.map