Skip to content

fix(v8-runtime): cap each isolate's ArrayBuffer memory - #2079

Merged
eersnington merged 2 commits into
mainfrom
fix/v8-array-buffer-budget
Oct 27, 2026
Merged

eersnington merged 2 commits into
mainfrom
fix/v8-array-buffer-budget

Conversation

@eersnington

@eersnington eersnington commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Refs #2078.

heap_limits caps only the V8 heap. ArrayBuffer backing stores used V8's default allocator, which has no limit. One guest could hold gigabytes, and the host then OOM-killed the shared sidecar and every VM on it.

  • Each guest isolate gets a counting ArrayBuffer allocator (guest_isolate_params in crates/v8-runtime/src/isolate.rs). Fresh and snapshot-restored isolates both use it.
  • The budget is the isolate's existing heap limit, so no new option or protocol field. Past the budget the allocator returns null. V8 collects garbage, retries, and then throws RangeError: Array buffer allocation failed in the guest, the same error Node gives on a failed allocation.
  • The sidecar emits a v8_array_buffer_bytes limit warning once per isolate.
  • Host bytes given to the guest (raw_bytes_to_uint8array, CBOR bytes) now use a Rust-owned backing store. v8::ArrayBuffer::New calls FatalProcessOutOfMemory when the allocator refuses, so a guest with a spent budget could otherwise abort the sidecar with one readFileSync.
// Guest under the default 128 MiB limit
const held = [];
for (let i = 0; i < 16; i++) held.push(new Uint8Array(64 * 1024 * 1024));
// main:    holds 1 GiB in the shared sidecar
// this PR: RangeError: Array buffer allocation failed
Guest allocation past the limit main This PR
Uint8Array, Buffer, SharedArrayBuffer Allocation succeeds RangeError in the guest
readFileSync after the budget is spent Allocation succeeds RangeError in the guest, sidecar keeps running
WebAssembly.Memory, module memory Allocation succeeds Unchanged, fixed in #2080
Shared WASM memory, resizable ArrayBuffer, growable SharedArrayBuffer Allocation succeeds Unchanged. V8 allocates these with its page allocator, which has no embedder hook.

Security

  • Closes a guest-to-sidecar denial of service: one VM can no longer exhaust host memory through ArrayBuffer allocations and take down co-tenant VMs.

This is part 1 of 2 in a stack:

@railway-app

railway-app Bot commented Oct 27, 2026

Copy link
Copy Markdown

This PR was not deployed automatically as @eersnington does not have access to the Railway project.

In order to get automatic PR deploys, please add @eersnington to your workspace on Railway.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

📟 Pip-Boy · b83845e

📦 Packages · fresh npm install resolve, no lockfile · this PR vs base

No notable install size, dependency, or entrypoint changes across 6 packages.

All 6 packages · linux-x64-gnu
package (linux-x64-gnu) @latest base this PR Δ vs base packages
@rivet-dev/agentos-core 326.0 MB 326.0 MB 326.0 MB 0 10 (0)
@rivet-dev/agentos-node-pty 7.6 KB 18.0 KB 18.0 KB 0 1 (0)
@rivet-dev/agentos-posix 253.8 MB 253.8 MB 253.8 MB 0 7 (0)
@rivet-dev/agentos-runtime-core 253.8 MB 253.8 MB 253.8 MB 0 6 (0)
@rivet-dev/agentos-sandbox 363.7 MB 363.7 MB 363.7 MB 0 85 (0)
@rivet-dev/agentos-toolchain 130.8 KB 141.2 KB 141.2 KB 0 2 (0)

🧹 Rust dependencies · cargo deny + cargo shear

check new in this PR fixed existing on base
advisories ✅ 0 0 28
licenses ✅ 0 0 0
sources ✅ 0 0 0
bans ✅ 0 0 0
unused ✅ 0 0 16

🦀 Binaries · publish Dockerfiles, --release

Base: main@ae5e5cd, the newest main commit with native builds before the merge base. Size changes merged to main since then show up here too.

artifact npm @latest base this PR Δ vs base
agentos-native-sidecar linux-x64-gnu 143.8 MB 143.8 MB 143.8 MB +4.8 KB (+0.0%)

⏱ Timing

job wall time slowest step
changes 27s checkout · 22s
zizmor 8s checkout · 3s
footprint 1m58s Pack the base · 32s
native · linux-x64-gnu 13m21s Build with the publish Dockerfile · 13m00s
hygiene 52s checkout · 25s
⏭ self-test — skipped
report 17s Write the comment and run summary · 5s
🔭 Full CI trace for this commit

One OpenTelemetry trace of CI and Pip-Boy for b83845e: every job and step, with the sizes above attached. Run after CI finishes.

pnpm install --frozen-lockfile --filter 'pipboy...'
pnpm --filter pipboy pipboy trace --sha b83845e0ca913ec55b8946f76f519b89a9399aa0

Add --otlp <url> to send it to any OTLP/HTTP collector.

Agent instruction: From a rivet-dev/agentos checkout, run the commands above, then read the trace file the last command prints. List the slowest jobs and steps and explain where CI time went.

@eersnington
eersnington merged commit 3c60c9a into main Oct 27, 2026
12 of 15 checks passed
@eersnington
eersnington deleted the fix/v8-array-buffer-budget branch October 27, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant