Skip to content

spike: QuickJS-WASM sandbox marshaling cost harness#10072

Draft
jackkav wants to merge 1 commit into
developfrom
claude/determined-vaughan-e432f9
Draft

spike: QuickJS-WASM sandbox marshaling cost harness#10072
jackkav wants to merge 1 commit into
developfrom
claude/determined-vaughan-e432f9

Conversation

@jackkav

@jackkav jackkav commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Overview

A throwaway, runnable spike that measures the marshaling cost of moving pre/post-request scripts from the current same-realm AsyncFunction model (run-script.ts / sandbox.ts) into a separate QuickJS-WASM engine.

Motivation: QuickJS-WASM is the most portable real-isolation option for sandboxing scripts/plugins across both the Electron environment (renderer / main / UtilityProcess) and the inso Node CLI — one .wasm, no native rebuild, no vm2. The open question was how expensive the WASM boundary is once you can no longer pass the live InsomniaObject by reference. This spike answers it with numbers.

Not wired into the app. It lives under packages/insomnia/src/scripting/quickjs-spike/ and runs standalone so the results don't depend on the monorepo.

What it does

Runs the same representative pre-request script (env get/set loops, header mutation, console.log, and an awaited insomnia.sendRequest doing real host async I/O) through two architectures and counts/times every boundary crossing:

  • A — proxy the live host object (the natural port of today's pass-by-reference): every environment.get/set, header add, log, sendRequest crosses the boundary.
  • B — bulk-copy state in, rebuild the pm/insomnia API inside the sandbox, bridge only what must escape (console + async sendRequest), copy mutated state out.

Results (M-series, QuickJS 0.32, non-asyncify variant)

env vars: 50    per-crossing ≈ 1473 ns
A  proxy live object   crossings= 2005  run=13.07 ms
B  bulk-copy + bridge  crossings=    4  run= 1.92 ms   → 501× fewer crossings, 6.8× faster

env vars: 500   per-crossing ≈ 1229 ns
A  proxy live object   crossings= 2005  run= 5.70 ms
B  bulk-copy + bridge  crossings=    4  run= 1.97 ms   → 501× fewer crossings, 2.9× faster

Fidelity passes both ways (awaited sendRequest writes lastStatus=200 back; header added).

Key findings

  1. A crossing is cheap (~1.2–1.5 µs); cost is dominated by crossing count, which is an architecture choice, not a QuickJS limit.
  2. Don't proxy the live InsomniaObject method-by-method. Serialize the toObject() surface in once, rebuild the API in-sandbox over plain state, copy out once. Bulk JSON of a 7 KB payload is ~0.1 ms in / ~0.5 ms out.
  3. Async is the real work, and asyncify is a trap: newAsyncifiedFunction only drives host calls on the synchronous eval path; a host call reached from a user await chain (every pm script) crashes the job pump. The robust pattern is VM-native promises (ctx.newPromise()) + a host driver loop, which also works with the smaller non-asyncify WASM.
  4. That driver loop is the QuickJS analogue of the existing __bridgeReset__/__bridgeSettle__ async-task monitor.

Reviewer notes

  • Spike code only — no production wiring, no dependency added to any workspace package.json. To run: npm i quickjs-emscripten in a scratch dir and node harness.mjs (see the README).
  • The README documents implications for an eventual real port (reuse toObject() as the serialize/merge contract; only sendRequest/console/vault need host bridges).

Standalone, runnable spike measuring the boundary-crossing cost of running
pre/post-request scripts inside a QuickJS-WASM engine instead of the current
same-realm AsyncFunction model.

Compares two architectures on the same representative script: proxying the
live host object (today's pass-by-reference) vs. bulk-copying state in and
rebuilding the pm/insomnia API inside the sandbox. Documents the async bridge
finding (asyncify collides with user await chains; VM-native promises + a
driver loop is the robust pattern).

Not wired into the app; see README for results and implications.
@github-actions

Copy link
Copy Markdown

✅ Circular References Report

Generated at: 2026-06-12T09:33:52.409Z
Status: ✅ NO CHANGE

Summary

Metric Base (develop) PR Change
Total Circular References 9 9 0 (0.00%)
Click to view all circular references in PR (9)
insomnia-inso/src/db/models/types.ts -> insomnia-inso/src/db/types.ts
insomnia/src/main/prompt-bridge.ts -> insomnia/src/main/window-utils.ts -> insomnia/src/main/plugin-window.ts
insomnia/src/main/window-utils.ts -> insomnia/src/main/plugin-window.ts
insomnia/src/network/network.ts -> insomnia-scripting-environment/src/objects/index.ts -> insomnia-scripting-environment/src/objects/collection.ts -> insomnia-scripting-environment/src/objects/response.ts
insomnia/src/network/network.ts -> insomnia/src/common/render.ts
insomnia/src/ui/components/settings/import-export.tsx -> insomnia/src/ui/components/modals/export-requests-modal.tsx
insomnia/src/ui/components/tabs/tab-list.tsx -> insomnia/src/ui/components/tabs/tab.tsx
insomnia/src/ui/components/templating/tag-editor-arg-sub-form.tsx -> insomnia/src/ui/components/templating/external-vault/external-vault-form.tsx
insomnia/src/ui/components/viewers/response-viewer.tsx -> insomnia/src/ui/components/viewers/response-multipart-viewer.tsx
Click to view all circular references in base branch (9)
insomnia-inso/src/db/models/types.ts -> insomnia-inso/src/db/types.ts
insomnia/src/main/prompt-bridge.ts -> insomnia/src/main/window-utils.ts -> insomnia/src/main/plugin-window.ts
insomnia/src/main/window-utils.ts -> insomnia/src/main/plugin-window.ts
insomnia/src/network/network.ts -> insomnia-scripting-environment/src/objects/index.ts -> insomnia-scripting-environment/src/objects/collection.ts -> insomnia-scripting-environment/src/objects/response.ts
insomnia/src/network/network.ts -> insomnia/src/common/render.ts
insomnia/src/ui/components/settings/import-export.tsx -> insomnia/src/ui/components/modals/export-requests-modal.tsx
insomnia/src/ui/components/tabs/tab-list.tsx -> insomnia/src/ui/components/tabs/tab.tsx
insomnia/src/ui/components/templating/tag-editor-arg-sub-form.tsx -> insomnia/src/ui/components/templating/external-vault/external-vault-form.tsx
insomnia/src/ui/components/viewers/response-viewer.tsx -> insomnia/src/ui/components/viewers/response-multipart-viewer.tsx

Analysis

No Change: This PR does not introduce or remove any circular references.


This report was generated automatically by comparing against the develop branch.

@ihexxa

ihexxa commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

The approach 2 seems better to me, there could be some objects that cannot be proxied. And tackling require could be considered at some point. Really cool as it looks very compact and also covers benchmarks.

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.

2 participants