# SandboxPool _(no submodules)_ ## Purpose `SandboxPool` amortises engine startup cost across many short executions by maintaining a warm pool of `SandboxEngine` instances. The pool pre-warms to `minSize` on construction, lazily grows up to `maxSize` under load, recycles engines that exceed `maxExecutionsPerEngine` (so leak-prone engines like long-lived workers don't accumulate state), and reclaims idle engines above `minSize` whose `lastUsed` exceeds `idleTimeoutMs` via the explicit `cleanupTick()` call. The pool's clock is injectable — defaulting to `Date.now` in production, replaced by a `FakeClock` in tests so the idle-reclaim behaviour can be asserted without flake. The cleanup is invoked explicitly rather than on an internal timer so the consumer controls scheduling and the pool stays single-threaded. ## Features - Pre-warmed up to `minSize` on construction. - Lazy growth up to `maxSize` on acquisition. - `POOL_EXHAUSTED` surfaced in a `SandboxResult` when the pool cannot grow. - Per-engine execution count, recycle on threshold. - `cleanupTick()` reclaims idle engines down to `minSize`. - Injectable `Clock` for deterministic tests. - OTel `recordPoolAcquisition` for each acquisition (acquired / created / exhausted). - Aggregate `stats()` surface. ## Usage: Basic ```ts import { VmEngine } from "@suaveplan/sandbox/vm-engine"; import { SandboxPool } from "@suaveplan/sandbox/pool"; const pool = new SandboxPool({ minSize: 2, maxSize: 8, engineConfig: { timeout: 5000, maxOutputSize: 1024, memoryLimit: 5e7 }, factory: (cfg) => new VmEngine(cfg), }); const r = await pool.execute("1 + 1"); pool.destroy(); ``` ## Usage: Advanced Drive cleanup against a `FakeClock` so idle reclamation can be asserted: ```ts let now = 0; const pool = new SandboxPool({ engineConfig, factory, minSize: 1, maxSize: 4, idleTimeoutMs: 1000, clock: () => now, }); // run a few executions, then advance time: now = 5000; pool.cleanupTick(); ``` ## API Reference - `new SandboxPool(config)`. - `execute(code, context?, options?): Promise`. - `size()`, `available()`, `inUse()`, `stats()`. - `warmUp()`, `cleanupTick()`, `destroy()`. - `DEFAULT_POOL_CONFIG` — exported defaults for downstream composition. ## Implementation Notes `acquire` prefers idle engines over creating new ones — this keeps warmed engines reusable rather than stranding them behind a fresh allocation when an old engine is right there. Recycling is reactive (on release, after the threshold is crossed) rather than proactive (at acquisition time) so a recycle never delays the path that needs the engine. The `clock` lookup happens once per `lastUsed`/`createdAt` write so callers can swap the clock implementation without re-engineering the pool's call graph.