first commit
This commit is contained in:
76
docs/devtools/tooling/sandbox/src/pool/pool.md
Normal file
76
docs/devtools/tooling/sandbox/src/pool/pool.md
Normal 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.
|
||||
Reference in New Issue
Block a user