|
| 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. |
0 commit comments