UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

205 lines 7.8 kB
import { TerminalError } from '../utils/errors.js'; import { delay } from '../utils/helpers.js'; /** * Terminal pool for efficient resource management */ export class TerminalPool { maxSize; recycleAfter; adapter; logger; terminals = new Map(); availableQueue = []; initializationPromise; constructor(maxSize, recycleAfter, adapter, logger) { this.maxSize = maxSize; this.recycleAfter = recycleAfter; this.adapter = adapter; this.logger = logger; } async initialize() { if (this.initializationPromise) { return this.initializationPromise; } this.initializationPromise = this.doInitialize(); return this.initializationPromise; } async doInitialize() { this.logger.info('Initializing terminal pool', { maxSize: this.maxSize, recycleAfter: this.recycleAfter, }); // Pre-create some terminals const preCreateCount = Math.min(2, this.maxSize); const promises = []; for (let i = 0; i < preCreateCount; i++) { promises.push(this.createPooledTerminal()); } await Promise.all(promises); this.logger.info('Terminal pool initialized', { created: preCreateCount, }); } async shutdown() { this.logger.info('Shutting down terminal pool'); // Destroy all terminals const terminals = Array.from(this.terminals.values()); await Promise.all(terminals.map(({ terminal }) => this.adapter.destroyTerminal(terminal))); this.terminals.clear(); this.availableQueue = []; } async acquire() { // Try to get an available terminal while (this.availableQueue.length > 0) { const terminalId = this.availableQueue.shift(); const pooled = this.terminals.get(terminalId); if (pooled && pooled.terminal.isAlive()) { pooled.inUse = true; pooled.lastUsed = new Date(); this.logger.debug('Terminal acquired from pool', { terminalId, useCount: pooled.useCount, }); return pooled.terminal; } // Terminal is dead, remove it if (pooled) { this.terminals.delete(terminalId); } } // No available terminals, create new one if under limit if (this.terminals.size < this.maxSize) { await this.createPooledTerminal(); return this.acquire(); // Recursive call to get the newly created terminal } // Pool is full, wait for a terminal to become available this.logger.info('Terminal pool full, waiting for available terminal'); const startTime = Date.now(); const timeout = 30000; // 30 seconds while (Date.now() - startTime < timeout) { await delay(100); // Check if any terminal became available const available = Array.from(this.terminals.values()).find((pooled) => !pooled.inUse && pooled.terminal.isAlive()); if (available) { available.inUse = true; available.lastUsed = new Date(); return available.terminal; } } throw new TerminalError('No terminal available in pool (timeout)'); } async release(terminal) { const pooled = this.terminals.get(terminal.id); if (!pooled) { this.logger.warn('Attempted to release unknown terminal', { terminalId: terminal.id, }); return; } pooled.useCount++; pooled.inUse = false; // Check if terminal should be recycled if (pooled.useCount >= this.recycleAfter || !terminal.isAlive()) { this.logger.info('Recycling terminal', { terminalId: terminal.id, useCount: pooled.useCount, }); // Destroy old terminal this.terminals.delete(terminal.id); await this.adapter.destroyTerminal(terminal); // Create replacement if under limit if (this.terminals.size < this.maxSize) { await this.createPooledTerminal(); } } else { // Return to available queue this.availableQueue.push(terminal.id); this.logger.debug('Terminal returned to pool', { terminalId: terminal.id, useCount: pooled.useCount, }); } } async getHealthStatus() { const aliveTerminals = Array.from(this.terminals.values()).filter((pooled) => pooled.terminal.isAlive()); const available = aliveTerminals.filter((pooled) => !pooled.inUse).length; const recycled = Array.from(this.terminals.values()).filter((pooled) => pooled.useCount >= this.recycleAfter).length; return { healthy: aliveTerminals.length > 0, size: this.terminals.size, available, recycled, }; } async performMaintenance() { this.logger.debug('Performing terminal pool maintenance'); // Remove dead terminals const deadTerminals = []; for (const [id, pooled] of this.terminals.entries()) { if (!pooled.terminal.isAlive()) { deadTerminals.push(id); } } // Clean up dead terminals for (const id of deadTerminals) { this.logger.warn('Removing dead terminal from pool', { terminalId: id }); this.terminals.delete(id); const index = this.availableQueue.indexOf(id); if (index !== -1) { this.availableQueue.splice(index, 1); } } // Ensure minimum pool size const currentSize = this.terminals.size; const minSize = Math.min(2, this.maxSize); if (currentSize < minSize) { const toCreate = minSize - currentSize; this.logger.info('Replenishing terminal pool', { currentSize, minSize, creating: toCreate, }); const promises = []; for (let i = 0; i < toCreate; i++) { promises.push(this.createPooledTerminal()); } await Promise.all(promises); } // Check for stale terminals that should be recycled const now = Date.now(); const staleTimeout = 300000; // 5 minutes for (const [id, pooled] of this.terminals.entries()) { if (!pooled.inUse && pooled.terminal.isAlive()) { const idleTime = now - pooled.lastUsed.getTime(); if (idleTime > staleTimeout) { this.logger.info('Recycling stale terminal', { terminalId: id, idleTime, }); // Mark for recycling pooled.useCount = this.recycleAfter; } } } } async createPooledTerminal() { try { const terminal = await this.adapter.createTerminal(); const pooled = { terminal, useCount: 0, lastUsed: new Date(), inUse: false, }; this.terminals.set(terminal.id, pooled); this.availableQueue.push(terminal.id); this.logger.debug('Created pooled terminal', { terminalId: terminal.id }); } catch (error) { this.logger.error('Failed to create pooled terminal', error); throw error; } } } //# sourceMappingURL=pool.js.map