first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
# SandboxPool
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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.