claude-flow-tbowman01
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
254 lines • 9.35 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;
}
async initialize() {
this.logger.info('Initializing resource manager');
// Set up periodic cleanup
setInterval(() => this.cleanup(), 30000); // Every 30 seconds
}
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);
// 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);
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();
// 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;
}
async 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 {
healthy: true,
metrics: {
totalResources,
lockedResources,
freeResources: totalResources - lockedResources,
waitingAgents: waitingAgents.size,
totalWaitingRequests: totalWaiting,
},
};
}
async lockResource(resourceId, agentId) {
const resource = this.resources.get(resourceId);
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());
}
this.agentResources.get(agentId).add(resourceId);
this.logger.info('Resource locked', { resourceId, agentId });
// 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 });
}
async performMaintenance() {
this.logger.debug('Performing resource manager maintenance');
this.cleanup();
}
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