Files
skills/docs/devtools/tooling/sandbox/src/pool/pool.md
2026-05-29 08:20:56 +01:00

2.8 KiB

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

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:

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<SandboxResult>.
  • 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.