@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
269 lines • 10 kB
JavaScript
/**
* 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