Skip to content

Commit f65055f

Browse files
NathanFlurryclaude
andcommitted
feat: US-051 - Add process isolation documentation page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2df62f0 commit f65055f

3 files changed

Lines changed: 193 additions & 1 deletion

File tree

docs/docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
"features/module-loading",
6666
"features/output-capture",
6767
"features/resource-limits",
68-
"features/child-processes"
68+
"features/child-processes",
69+
"process-isolation"
6970
]
7071
},
7172
{

docs/process-isolation.mdx

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
title: Process Isolation
3+
description: Configure V8 process topology to control crash blast radius and resource partitioning.
4+
---
5+
6+
By default, all `NodeRuntime` instances share a single V8 child process. Every `exec()` or `run()` call creates a session (separate V8 isolate on a separate OS thread), but all sessions live in the same OS process.
7+
8+
This is efficient — one process, one UDS connection, shared startup cost — but it means a V8 OOM or crash in **any** session kills **all** sessions across **all** runtimes.
9+
10+
Process isolation lets you control the blast radius by placing runtimes in separate V8 processes.
11+
12+
## What process isolation means
13+
14+
Each V8 process has its own:
15+
16+
- **Memory space** — a crash or heap corruption in one process cannot affect another.
17+
- **File descriptor table** — no FD leakage between processes.
18+
- **Signal handlers**`SIGSEGV` in one process doesn't reach another.
19+
- **Crash domain** — if the process dies, only its sessions are affected.
20+
21+
## When to use it
22+
23+
- **Multi-tenant hosting** — isolate Tenant A's executions from Tenant B's at the process level so a crash from one tenant cannot disrupt another.
24+
- **Untrusted code from different security domains** — code from different sources should not share crash fate.
25+
- **High OOM risk workloads** — workloads that frequently hit memory limits benefit from a smaller blast radius.
26+
27+
For single-tenant or low-risk workloads, the default shared process is simpler and more memory-efficient.
28+
29+
## Topology options
30+
31+
### Shared (default)
32+
33+
All runtimes use the global shared V8 process. No configuration needed.
34+
35+
```
36+
NodeRuntime A ─┐
37+
NodeRuntime B ─┤── Global V8 Process (sessions: A1, B1, C1)
38+
NodeRuntime C ─┘
39+
```
40+
41+
```ts
42+
import {
43+
NodeRuntime,
44+
createNodeDriver,
45+
createNodeRuntimeDriverFactory,
46+
} from "@secure-exec/node";
47+
48+
const rt = new NodeRuntime({
49+
systemDriver: createNodeDriver(),
50+
runtimeDriverFactory: createNodeRuntimeDriverFactory(),
51+
});
52+
```
53+
54+
### Per-tenant
55+
56+
Multiple runtimes share a dedicated V8 process. Crash in this process affects only the runtimes attached to it.
57+
58+
```
59+
NodeRuntime A ─┐── Tenant 1 Process (sessions: A1, B1)
60+
NodeRuntime B ─┘
61+
NodeRuntime C ──── Tenant 2 Process (sessions: C1)
62+
```
63+
64+
```ts
65+
import { createV8Runtime } from "@secure-exec/v8";
66+
import {
67+
NodeRuntime,
68+
createNodeDriver,
69+
createNodeRuntimeDriverFactory,
70+
} from "@secure-exec/node";
71+
72+
// Create a dedicated process for this tenant
73+
const tenantProcess = await createV8Runtime({ maxSessions: 10 });
74+
75+
const rt1 = new NodeRuntime({
76+
systemDriver: createNodeDriver(),
77+
runtimeDriverFactory: createNodeRuntimeDriverFactory({ v8Runtime: tenantProcess }),
78+
});
79+
80+
const rt2 = new NodeRuntime({
81+
systemDriver: createNodeDriver(),
82+
runtimeDriverFactory: createNodeRuntimeDriverFactory({ v8Runtime: tenantProcess }),
83+
});
84+
85+
// rt1 and rt2 share tenantProcess, isolated from the global process
86+
```
87+
88+
### Per-runtime
89+
90+
Each runtime gets its own V8 process. Maximum isolation — a crash affects only one runtime.
91+
92+
```
93+
NodeRuntime A ──── Process A (session: A1)
94+
NodeRuntime B ──── Process B (session: B1)
95+
```
96+
97+
```ts
98+
import { createV8Runtime } from "@secure-exec/v8";
99+
import {
100+
NodeRuntime,
101+
createNodeDriver,
102+
createNodeRuntimeDriverFactory,
103+
} from "@secure-exec/node";
104+
105+
const processA = await createV8Runtime();
106+
const processB = await createV8Runtime();
107+
108+
const rtA = new NodeRuntime({
109+
systemDriver: createNodeDriver(),
110+
runtimeDriverFactory: createNodeRuntimeDriverFactory({ v8Runtime: processA }),
111+
});
112+
113+
const rtB = new NodeRuntime({
114+
systemDriver: createNodeDriver(),
115+
runtimeDriverFactory: createNodeRuntimeDriverFactory({ v8Runtime: processB }),
116+
});
117+
```
118+
119+
## Trade-offs
120+
121+
| Topology | Memory cost | Crash blast radius | Startup cost |
122+
|---|---|---|---|
123+
| Shared (default) | ~30-50 MB total | All sessions | One-time |
124+
| Per-tenant | ~30-50 MB per tenant | One tenant's sessions | Per tenant |
125+
| Per-runtime | ~30-50 MB per runtime | One runtime's sessions | Per runtime |
126+
127+
Choose based on your isolation requirements. The shared topology is the most memory-efficient. Per-tenant balances isolation with resource usage. Per-runtime provides the strongest isolation at the highest cost.
128+
129+
## Resource limits
130+
131+
`maxSessions` controls the maximum number of concurrent sessions (isolates) within a single V8 process. It applies per-process, not globally.
132+
133+
```ts
134+
// This process allows up to 5 concurrent sessions
135+
const process = await createV8Runtime({ maxSessions: 5 });
136+
137+
// Both runtimes share the 5-session budget
138+
const rt1 = new NodeRuntime({
139+
systemDriver: createNodeDriver(),
140+
runtimeDriverFactory: createNodeRuntimeDriverFactory({ v8Runtime: process }),
141+
});
142+
143+
const rt2 = new NodeRuntime({
144+
systemDriver: createNodeDriver(),
145+
runtimeDriverFactory: createNodeRuntimeDriverFactory({ v8Runtime: process }),
146+
});
147+
```
148+
149+
Per-execution resource limits (`memoryLimit`, `cpuTimeLimitMs`) still apply per-session regardless of topology. See [Resource Limits](/features/resource-limits) for details.
150+
151+
## Crash behavior
152+
153+
When a V8 process crashes (OOM, segfault, panic):
154+
155+
1. **Only sessions in that process are affected.** Sessions in other processes continue running.
156+
2. **Affected sessions receive an `ERR_V8_PROCESS_CRASH` error.** The host process remains alive.
157+
3. **New sessions cannot be created on the crashed process.** Create a new `V8Runtime` to recover.
158+
159+
```ts
160+
const process = await createV8Runtime();
161+
const factory = createNodeRuntimeDriverFactory({ v8Runtime: process });
162+
163+
const rt = new NodeRuntime({
164+
systemDriver: createNodeDriver(),
165+
runtimeDriverFactory: factory,
166+
memoryLimit: 8, // small heap to trigger OOM
167+
});
168+
169+
const result = await rt.exec("const a = []; while(true) a.push(new Array(1e6))");
170+
// result.errorMessage includes ERR_V8_PROCESS_CRASH
171+
// Host process is still alive
172+
```
173+
174+
Runtimes using the default shared process share crash fate — if the global process dies, all runtimes are affected. Use explicit `createV8Runtime()` handles to control which runtimes share a crash domain.
175+
176+
## Lifecycle
177+
178+
The caller owns the `V8Runtime` handle and is responsible for disposing it when done.
179+
180+
```ts
181+
const process = await createV8Runtime();
182+
183+
// ... use process ...
184+
185+
// Clean up — sends SIGTERM to the child process
186+
await process.dispose();
187+
```
188+
189+
The global shared runtime is disposed automatically on process exit. Disposing a `V8Runtime` while sessions are active causes those sessions to receive `ERR_V8_PROCESS_CRASH` errors.

docs/runtimes/node.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const runtime = new NodeRuntime({
2929
These exports are also available from `"secure-exec"` for backward compatibility.
3030
</Note>
3131

32+
By default, all runtimes share a single V8 child process. You can pass a dedicated `V8Runtime` handle via `createNodeRuntimeDriverFactory({ v8Runtime })` to control crash blast radius and resource partitioning. See [Process Isolation](/process-isolation) for topology options and trade-offs.
33+
3234
## exec vs run
3335

3436
Use `exec()` when you care about side effects (logging, file writes) but don't need a return value.

0 commit comments

Comments
 (0)