UNPKG

n8n

Version:

n8n Workflow Automation Tool

246 lines 8.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentIsolatePool = exports.AgentIsolateSlot = exports.PoolExhaustedError = exports.PoolDisposedError = void 0; const sandbox_polyfills_1 = require("./sandbox-polyfills"); class PoolDisposedError extends Error { constructor() { super('Agent isolate pool is disposed'); this.name = 'PoolDisposedError'; } } exports.PoolDisposedError = PoolDisposedError; class PoolExhaustedError extends Error { constructor() { super('Agent isolate pool is exhausted — too many concurrent requests. Try again later.'); this.name = 'PoolExhaustedError'; } } exports.PoolExhaustedError = PoolExhaustedError; class AgentIsolateSlot { constructor(ivmModule, memoryLimit, libraryBundle) { this.isolate = new ivmModule.Isolate({ memoryLimit }); this.bundleScript = this.isolate.compileScriptSync(libraryBundle); } get isHealthy() { return !this.isolate.isDisposed; } createContext() { const context = this.isolate.createContextSync(); try { const jail = context.global; jail.setSync('global', jail.derefInto()); context.evalSync(sandbox_polyfills_1.SANDBOX_POLYFILLS, { timeout: 5000 }); context.evalSync(` globalThis.__modules = {}; globalThis.require = function(id) { if (globalThis.__modules[id]) { return globalThis.__modules[id]; } // Return empty stub for unknown modules (Node built-ins, etc.) return new Proxy({}, { get: function(target, prop) { if (prop === '__esModule') return false; if (prop === 'default') return {}; return function() { return {}; }; } }); }; `, { timeout: 5000 }); this.bundleScript.runSync(context, { timeout: 10000 }); return context; } catch (e) { context.release(); throw e; } } dispose() { if (this.isHealthy) { this.bundleScript.release(); this.isolate.dispose(); } } } exports.AgentIsolateSlot = AgentIsolateSlot; class AgentIsolatePool { constructor(ivmModule, libraryBundle, options = {}) { this.ivmModule = ivmModule; this.libraryBundle = libraryBundle; this.slots = []; this.waitQueue = []; this.disposed = false; this.warming = 0; this.replenishPromises = new Set(); this.size = options.size ?? 2; this.memoryLimit = options.memoryLimit ?? 32; this.highWaterMarkRatio = options.highWaterMarkRatio ?? 0.8; this.maxQueueDepth = options.maxQueueDepth ?? 10; this.logger = options.logger; } async initialize() { const results = await Promise.allSettled(Array.from({ length: this.size }, async () => await Promise.resolve(this.createSlot()))); for (const result of results) { if (result.status === 'fulfilled') { this.slots.push(result.value); } else { this.logger?.warn('[AgentIsolatePool] Failed to create slot during init', { error: result.reason instanceof Error ? result.reason.message : String(result.reason), }); } } if (this.slots.length === 0) { const firstRejection = results.find((r) => r.status === 'rejected'); const cause = firstRejection?.reason instanceof Error ? firstRejection.reason : new Error(String(firstRejection?.reason)); throw new Error('AgentIsolatePool: failed to create any isolate slots during initialization', { cause }); } const missing = this.size - this.slots.length; for (let i = 0; i < missing; i++) { void this.replenish(); } } async acquire() { if (this.disposed) throw new PoolDisposedError(); const slot = this.slots.shift(); if (slot) { void this.replenish(); return slot; } if (this.waitQueue.length >= this.maxQueueDepth) { this.logger?.warn('[AgentIsolatePool] Pool exhausted — request rejected', { queueDepth: this.waitQueue.length, maxQueueDepth: this.maxQueueDepth, }); throw new PoolExhaustedError(); } return await new Promise((resolve, reject) => { this.waitQueue.push({ resolve, reject }); }); } release(slot) { if (this.disposed) { slot.dispose(); return; } if (!slot.isHealthy) { this.logger?.warn('[AgentIsolatePool] Slot OOM — discarding and replenishing'); slot.dispose(); void this.replenish(); return; } if (this.isOverHighWaterMark(slot)) { this.logger?.debug('[AgentIsolatePool] Slot over high-water mark — proactively recycling'); slot.dispose(); void this.replenish(); return; } const waiter = this.waitQueue.shift(); if (waiter) { waiter.resolve(slot); return; } this.slots.push(slot); } tryAcquireSync() { if (this.disposed) return null; const slot = this.slots.shift(); if (slot) { void this.replenish(); return slot; } return null; } async dispose() { this.disposed = true; const error = new PoolDisposedError(); for (const { reject } of this.waitQueue) { reject(error); } this.waitQueue = []; await Promise.all([...this.replenishPromises]); for (const slot of this.slots) { slot.dispose(); } this.slots = []; } isOverHighWaterMark(slot) { try { const stats = slot.isolate.getHeapStatisticsSync(); const limitBytes = this.memoryLimit * 1024 * 1024; return stats.used_heap_size > limitBytes * this.highWaterMarkRatio; } catch (error) { this.logger?.warn('[AgentIsolatePool] Failed to get heap statistics — assuming over high-water mark', { error: error instanceof Error ? error.message : String(error), }); return true; } } createSlot() { return new AgentIsolateSlot(this.ivmModule, this.memoryLimit, this.libraryBundle); } replenish(attempt = 0) { if (this.disposed) return; if (this.slots.length + this.warming >= this.size) return; this.warming++; let promise; promise = Promise.resolve() .then(() => { const slot = this.createSlot(); this.warming--; this.replenishPromises.delete(promise); if (this.disposed) { slot.dispose(); return; } const waiter = this.waitQueue.shift(); if (waiter) { waiter.resolve(slot); void this.replenish(); } else { this.slots.push(slot); } }) .catch((error) => { this.warming--; this.replenishPromises.delete(promise); if (attempt < AgentIsolatePool.MAX_REPLENISH_RETRIES) { const delay = AgentIsolatePool.REPLENISH_RETRY_BASE_MS * 2 ** attempt; this.logger?.debug('[AgentIsolatePool] Replenishment failed, retrying', { attempt, delayMs: delay, error: error instanceof Error ? error.message : String(error), }); let retryPromise; retryPromise = new Promise((resolve) => { setTimeout(resolve, delay).unref(); }).then(() => { this.replenishPromises.delete(retryPromise); this.replenish(attempt + 1); }); this.replenishPromises.add(retryPromise); } else { this.logger?.warn('[AgentIsolatePool] Replenishment failed after max retries', { error: error instanceof Error ? error.message : String(error), }); const waitError = new Error('Isolate slot creation permanently failed — no slots available'); for (const { reject } of this.waitQueue.splice(0)) { reject(waitError); } } }); this.replenishPromises.add(promise); } } exports.AgentIsolatePool = AgentIsolatePool; AgentIsolatePool.MAX_REPLENISH_RETRIES = 3; AgentIsolatePool.REPLENISH_RETRY_BASE_MS = 500; //# sourceMappingURL=agent-isolate-pool.js.map