Skip to content

fix: prevent assertions from returning wrong values during large iteration runs#7692

Open
gopu-bruno wants to merge 2 commits intousebruno:mainfrom
gopu-bruno:fix/assert-large-iterations-quickjs
Open

fix: prevent assertions from returning wrong values during large iteration runs#7692
gopu-bruno wants to merge 2 commits intousebruno:mainfrom
gopu-bruno:fix/assert-large-iterations-quickjs

Conversation

@gopu-bruno
Copy link
Copy Markdown
Collaborator

@gopu-bruno gopu-bruno commented Apr 7, 2026

Description

When running collections with --iteration-count=350, assertions using res("...") queries would start returning wrong values (e.g., "bearer", "POST", "none") around iteration ~310. Once corrupted, all subsequent res() evaluations returned the same incorrect value.

Root cause

executeQuickJsVm reused a single QuickJSSyncContext for every evaluation. Shims (addBruShimToContext, addBrunoResponseShimToContext, etc.) set properties on vm.global each call, and handles accumulated on the shared context over hundreds of iterations, eventually causing QuickJS to return stale/corrupted data.

Changes

  • Create a fresh QuickJS context per executeQuickJsVm call instead of reusing a single shared context across all evaluations
  • Reuse the cached WASM module in executeQuickJsVmAsync via loader() instead of loading a new module every call

Fixes: #7167
Tracked: JIRA

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • Refactor
    • Updated sandbox initialization to lazy-load and cache the runtime module, reducing startup work.
    • Each script execution now uses a fresh execution context, improving isolation and reliability while allowing the shared module to be reused across runs.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

Walkthrough

Switched QuickJS initialization from eagerly creating and caching a reusable context to lazy-loading and caching the QuickJS WASM module. The module is loaded once and reused; each VM execution now creates a new QuickJS context via QuickJSModule.newContext() instead of reusing a shared context.

Changes

Cohort / File(s) Summary
QuickJS Context & Module Loading
packages/bruno-js/src/sandbox/quickjs/index.js
Replaced eager cached QuickJSSyncContext with a memoized WASM module (QuickJSModule). loader() is awaited and the module cached; every execution creates a fresh QuickJS context (newContext()), avoiding shared context reuse that could leak state.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

size/L

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • naman-bruno
  • bijin-bruno

Poem

QuickJS wakes from lazy sleep,
A module cached for tasks to keep,
Each call a newborn, tidy and bright,
No stale state sneaking into the night,
Scripts run clean — a hopeful sight! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the primary fix: preventing assertions from returning wrong values during large iteration runs by switching to fresh QuickJS contexts per execution.
Linked Issues check ✅ Passed The code changes directly address issue #7167 by creating fresh QuickJS contexts per executeQuickJsVm call instead of reusing a shared context, which was the root cause of assertion value corruption.
Out of Scope Changes check ✅ Passed All changes are scoped to QuickJS context initialization and caching strategy, directly targeting the identified root cause of context handle accumulation and data corruption.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bruno-js/src/sandbox/quickjs/index.js (1)

59-89: ⚠️ Potential issue | 🟠 Major

Dispose every fresh QuickJS context to prevent WASM memory leaks.

Lines 59 and 100 allocate a new VM per evaluation but never release it. Each newContext() must be explicitly disposed with vm.dispose(), or the underlying QuickJS/WASM resources will leak. Long-running workloads will accumulate contexts and shim handles until garbage collection.

Handles are already disposed correctly (result.error.dispose(), result.value.dispose()), so wrap the context lifecycle in a finally block:

Fix for lines 59–89 (sync path)
-  const vm = QuickJSModule.newContext();
-
-  try {
+  let vm;
+  try {
+    vm = QuickJSModule.newContext();
   } catch (error) {
     console.error('Error executing the script!', error);
+  } finally {
+    vm?.dispose();
   }
Fix for lines 99–169 (async path)
-  try {
+  let vm;
+  try {
     const module = await loader();
-    const vm = module.newContext();
+    vm = module.newContext();
   } catch (error) {
     error.__isQuickJS = true;
     throw error;
+  } finally {
+    vm?.dispose();
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-js/src/sandbox/quickjs/index.js` around lines 59 - 89, The
QuickJS context created by QuickJSModule.newContext() isn't disposed, causing
WASM memory leaks; update the sync path in the function that uses vm to ensure
vm.dispose() is always called in a finally block after any handle disposals
(result.error.dispose() / result.value.dispose()), so: create vm via
QuickJSModule.newContext(), run the try block exactly as-is, then in finally
call vm.dispose(); apply the same pattern to the async path that also creates a
vm; keep existing handle disposal (result.error/result.value) before disposing
the vm and ensure any shims added by
addBruShimToContext/addBrunoRequestShimToContext/addBrunoResponseShimToContext
and values from marshallToVm remain valid until after those disposals.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-js/src/sandbox/quickjs/index.js`:
- Around line 17-19: QuickJSModule is set asynchronously via loader(), but
executeQuickJsVm() dereferences QuickJSModule/newContext before the try block;
move the creation of the QuickJS context (calls to QuickJSModule.newContext() or
newContext()) inside the try within executeQuickJsVm() and explicitly check for
QuickJSModule presence (or await loader() / throw a clear "QuickJS not loaded"
error) so the not-yet-loaded case is handled and falls into the catch path
instead of raising an uncaught exception.

---

Outside diff comments:
In `@packages/bruno-js/src/sandbox/quickjs/index.js`:
- Around line 59-89: The QuickJS context created by QuickJSModule.newContext()
isn't disposed, causing WASM memory leaks; update the sync path in the function
that uses vm to ensure vm.dispose() is always called in a finally block after
any handle disposals (result.error.dispose() / result.value.dispose()), so:
create vm via QuickJSModule.newContext(), run the try block exactly as-is, then
in finally call vm.dispose(); apply the same pattern to the async path that also
creates a vm; keep existing handle disposal (result.error/result.value) before
disposing the vm and ensure any shims added by
addBruShimToContext/addBrunoRequestShimToContext/addBrunoResponseShimToContext
and values from marshallToVm remain valid until after those disposals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1fb60a04-a5eb-477d-99bc-e5960bda56b7

📥 Commits

Reviewing files that changed from the base of the PR and between 4d6032b and 64244e2.

📒 Files selected for processing (1)
  • packages/bruno-js/src/sandbox/quickjs/index.js

@pull-request-size pull-request-size bot added size/S and removed size/XS labels Apr 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/bruno-js/src/sandbox/quickjs/index.js (2)

59-88: ⚠️ Potential issue | 🟠 Major

Missing vm.dispose() — memory leak in long-running iterations.

The context is created on line 60 but never disposed. Each invocation allocates WASM memory that won't be reclaimed until the process exits. Over 350+ iterations (the exact scenario this PR targets), this could accumulate significant memory.

Ensure disposal happens in all exit paths (success, error, and the outer catch).

🛠️ Proposed fix
   try {
     const vm = QuickJSModule.newContext();
     const { bru, req, res, ...variables } = externalContext;

     bru && addBruShimToContext(vm, bru);
     req && addBrunoRequestShimToContext(vm, req);
     res && addBrunoResponseShimToContext(vm, res);

     Object.entries(variables)?.forEach(([key, value]) => {
       vm.setProp(vm.global, key, marshallToVm(value, vm));
     });

     const templateLiteralText = `\`${externalScript}\``;
     const jsExpressionText = `${externalScript}`;

     let scriptText = scriptType === 'template-literal' ? templateLiteralText : jsExpressionText;

     const result = vm.evalCode(scriptText);
     if (result.error) {
       let e = vm.dump(result.error);
       result.error.dispose();
+      vm.dispose();
       return e;
     } else {
       let v = vm.dump(result.value);
       result.value.dispose();
+      vm.dispose();
       return v;
     }
   } catch (error) {
     console.error('Error executing the script!', error);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-js/src/sandbox/quickjs/index.js` around lines 59 - 88, The
QuickJS context created via QuickJSModule.newContext() is never disposed causing
a WASM memory leak; ensure vm.dispose() is called on every exit path (after
handling result from vm.evalCode(scriptText) and inside the catch and error
branches). Update the block around QuickJSModule.newContext(), vm.evalCode(),
and the result handling (inspect result.error / result.value) to always call
vm.dispose() after dumping and disposing result.error/result.value, and also
call vm.dispose() in the outer catch before rethrowing or returning to guarantee
the context is freed.

167-167: ⚠️ Potential issue | 🟠 Major

Clarify why vm.dispose() is commented out.

Same concern as the sync version — without disposal, each async invocation leaks the context's WASM memory. If there's a specific reason (e.g., disposal causing issues with promise resolution), please document it. Otherwise, consider uncommenting or wrapping in a finally block to ensure cleanup.

-    // vm.dispose();
+    vm.dispose();
     return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-js/src/sandbox/quickjs/index.js` at line 167, The
commented-out vm.dispose() in the async QuickJS path leaves WASM memory leaking
across async invocations; either restore cleanup by calling vm.dispose() in a
finally block after the async run (so disposal always occurs even on errors) or
add a clear code comment above the commented line explaining the exact reason it
must remain disabled (including the specific promise-resolution issue and a
ticket/issue link). Locate the async invocation flow where vm.dispose() is
currently commented and implement the finally-based disposal (or documented
exception) to prevent WASM memory leaks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/bruno-js/src/sandbox/quickjs/index.js`:
- Around line 59-88: The QuickJS context created via QuickJSModule.newContext()
is never disposed causing a WASM memory leak; ensure vm.dispose() is called on
every exit path (after handling result from vm.evalCode(scriptText) and inside
the catch and error branches). Update the block around
QuickJSModule.newContext(), vm.evalCode(), and the result handling (inspect
result.error / result.value) to always call vm.dispose() after dumping and
disposing result.error/result.value, and also call vm.dispose() in the outer
catch before rethrowing or returning to guarantee the context is freed.
- Line 167: The commented-out vm.dispose() in the async QuickJS path leaves WASM
memory leaking across async invocations; either restore cleanup by calling
vm.dispose() in a finally block after the async run (so disposal always occurs
even on errors) or add a clear code comment above the commented line explaining
the exact reason it must remain disabled (including the specific
promise-resolution issue and a ticket/issue link). Locate the async invocation
flow where vm.dispose() is currently commented and implement the finally-based
disposal (or documented exception) to prevent WASM memory leaks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32e41c60-9d8c-4f98-8cd2-bb621e209e4f

📥 Commits

Reviewing files that changed from the base of the PR and between 64244e2 and ea2cbe9.

📒 Files selected for processing (1)
  • packages/bruno-js/src/sandbox/quickjs/index.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Asserts randomly start failing with wrong response query result

1 participant