Skip to content

feat: add memory leak detection script for react benchmark#2247

Open
hzy wants to merge 2 commits intolynx-family:mainfrom
hzy:feat/react-leak-detection
Open

feat: add memory leak detection script for react benchmark#2247
hzy wants to merge 2 commits intolynx-family:mainfrom
hzy:feat/react-leak-detection

Conversation

@hzy
Copy link
Collaborator

@hzy hzy commented Feb 13, 2026

This PR adds a memory leak detection script for React benchmarks.

Summary by CodeRabbit

  • New Features

    • Added a memory-leak detection tool for benchmark runs to scan and report possible leaks.
  • Chores

    • Added supporting tooling dependency for the new leak detector.
    • Updated an example project's build tooling to a newer prerelease version.
    • Added a metadata changeset file.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a leak-detection workflow to the React benchmark: a new script that analyzes .ptrace files, package.json script and dependency updates to run it, and a placeholder changeset file. Also updates an example's devDependency to a newer alpha release.

Changes

Cohort / File(s) Summary
Changeset
.changeset/wild-jeans-take.md
Adds an empty changeset placeholder file containing YAML document separators.
Benchmark package config
benchmark/react/package.json
Adds bench:detect-leak npm script and adds @lynx-js/trace-processor as a dependency and devDependency.
Leak detection script
benchmark/react/scripts/detectLeak.mjs
New ESM script that scans dist for .ptrace files, uses @lynx-js/trace-processor (WasmEngine, detectLeak target) to parse traces, runs SQL to find constructor/destructor events, reports leaks, and exits non‑zero on errors/leaks.
Example dependency update
examples/react-compiler/package.json
Bumps @rsbuild/plugin-babel from 1.0.6 to 2.0.0-alpha.3 in devDependencies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • upupming

Poem

🐰 I hop through traces, nibble at the clues,
Scanning .ptrace crumbs and chasing tiny cues.
Constructors and destructors — I match every pair,
Sniffing out the leaks with whimsical flair.
A little rabbit guard for memory care.

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add memory leak detection script for react benchmark' directly and clearly summarizes the primary change—adding a memory leak detection script for React benchmarks.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@changeset-bot
Copy link

changeset-bot bot commented Feb 13, 2026

🦋 Changeset detected

Latest commit: 1f915ce

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@hzy hzy marked this pull request as ready for review February 13, 2026 16:32
@hzy hzy requested a review from HuJean as a code owner February 13, 2026 16:32
Copilot AI review requested due to automatic review settings February 13, 2026 16:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a memory leak detection script for React benchmarks to help identify memory leaks by analyzing Perfetto trace files (.ptrace). The script queries trace data for FiberElement constructor/destructor events and reports any elements that were constructed but never destroyed.

Changes:

  • Added detectLeak.mjs script to analyze .ptrace files for memory leaks in React benchmarks
  • Added @lynx-js/trace-processor dependency (v0.0.1) to parse and query trace files
  • Added perfetto:detect-leak npm script to run the leak detection tool

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

File Description
benchmark/react/scripts/detectLeak.mjs New script that analyzes .ptrace files to detect memory leaks by comparing FiberElement constructors vs destructors
benchmark/react/package.json Adds @lynx-js/trace-processor dev dependency and perfetto:detect-leak script
pnpm-lock.yaml Lockfile updates for new @lynx-js/trace-processor dependency and its transitive dependency immer
.changeset/wild-jeans-take.md Changeset file for documenting the changes (currently empty)
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
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

🤖 Fix all issues with AI agents
In `@benchmark/react/package.json`:
- Line 23: The JSON in the package's "scripts" section has trailing commas
(e.g., after the "perfetto:detect-leak" entry) which violates our dprint rules;
remove any trailing commas after object entries and after the closing brace of
objects in benchmark/react/package.json so the file is valid JSON (ensure the
"scripts" object entries remain in the same order and that
"perfetto:detect-leak" is the last entry without a trailing comma).
🧹 Nitpick comments (1)
benchmark/react/scripts/detectLeak.mjs (1)

55-60: Early process.exit(1) prevents analysis of remaining files.

If the first .ptrace file lacks constructor events, the script terminates immediately without analyzing subsequent files. The rest of the script uses a hasLeak flag pattern (Lines 111, 117–118) for deferred exit. Consider aligning this case for consistency.

♻️ Suggested refactor
     if (countIt.valid()) {
       const count = countIt.get('count');
       if (count === 0) {
         console.error(
           `Error: No FiberElement::Constructor events found in ${file}.`,
         );
-        process.exit(1); // eslint-disable-line n/no-process-exit
+        hasLeak = true;
+        continue;
       }
     }

Copy link
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: 2

🤖 Fix all issues with AI agents
In `@benchmark/react/package.json`:
- Line 38: The devDependency "@lynx-js/trace-processor" referenced in
package.json does not exist and will break installs; either remove the
"@lynx-js/trace-processor" entry from devDependencies or add/restore the package
as a workspace package or published npm package and update package.json to point
to the correct version or workspace reference (e.g., workspace: or valid
version) so the dependency resolves during installation.

In `@examples/react-compiler/package.json`:
- Line 19: The package.json currently pins the dependency
"@rsbuild/plugin-babel" to an alpha pre-release "2.0.0-alpha.3"; replace this
unstable version with a stable release (either "1.1.0" — latest stable — or
revert to "1.0.6" if the PR isn't intended to change babel plugin behavior).
Update the version string for the "@rsbuild/plugin-babel" entry in package.json
accordingly and run your package manager (npm/yarn/pnpm) to reinstall and update
lockfile.

@hzy hzy closed this Feb 13, 2026
@hzy hzy force-pushed the feat/react-leak-detection branch from 804271b to b89b3e3 Compare February 13, 2026 18:34
@hzy hzy reopened this Feb 13, 2026
Copy link
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

🤖 Fix all issues with AI agents
In `@benchmark/react/package.json`:
- Line 14: The "bench:detect-leak" script will be picked up by the "bench"
aggregate (the "bench" npm script matches /^bench:.*/), causing detect-leak to
run when traces don't exist; rename the script key to "perfetto:detect-leak" (or
another "perfetto:*" name) so it runs after perfetto traces are produced, or
alternatively update the "bench" aggregate pattern to exclude
"bench:detect-leak"; locate the "bench:detect-leak" entry in package.json and
either change its key to "perfetto:detect-leak" or modify the aggregate script
pattern to prevent matching it.
🧹 Nitpick comments (1)
benchmark/react/scripts/detectLeak.mjs (1)

44-61: Early process.exit(1) skips remaining .ptrace files.

If one file has zero FiberElement::Constructor events, the script exits immediately without analyzing the rest. If this is intentional (treat missing constructors as a fatal trace error), consider adding a brief comment explaining the rationale. Otherwise, you could accumulate errors and exit after processing all files, consistent with how leak detection is handled below.

@hzy hzy force-pushed the feat/react-leak-detection branch from 54a8c86 to 980caec Compare February 14, 2026 01:42
Copy link
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: 2

🤖 Fix all issues with AI agents
In `@benchmark/react/scripts/detectLeak.mjs`:
- Around line 40-61: The abrupt process.exit(1) inside the scope where "using
engine = new WasmEngine('detectLeak')" prevents the engine's Symbol.dispose from
running; replace the direct process.exit(1) in the block that handles count ===
0 with throwing a descriptive Error (e.g., throw new Error(`No
FiberElement::Constructor events found in ${file}`)) or set a failure flag and
break so the using block can unwind and dispose the WasmEngine; ensure the
thrown error is caught or allowed to propagate to the existing top-level handler
(the call site that currently exits near line 118/122) so the program still
terminates non-zero but after proper disposal.
- Around line 40-42: The file uses the `using` resource-management syntax with
`using engine = new WasmEngine('detectLeak')` which Node.js 22 (V8 v12.4) does
not support; replace that pattern with explicit construction and manual cleanup:
instantiate WasmEngine (new WasmEngine('detectLeak')), run await
engine.parse(fileContent) and await engine.notifyEof() inside a try block, and
in a finally block call the engine's cleanup method (e.g., engine.dispose(),
engine.close(), or engine.free() — whichever `WasmEngine` exposes) to release
resources, or alternatively add transpilation/polyfills for TC39 Explicit
Resource Management if you choose to keep `using`. Ensure you reference the
WasmEngine constructor and its cleanup method when making the change.
🧹 Nitpick comments (1)
benchmark/react/scripts/detectLeak.mjs (1)

122-122: Top-level await main() will produce an unhandled rejection on failure.

If main() throws (e.g., from a fs.readFile error or the WASM engine), the rejection is unhandled at the top level. Consider adding a .catch() to log the error and set a non-zero exit code explicitly.

Proposed fix
-await main();
+await main().catch((err) => {
+  console.error(err);
+  process.exit(1); // eslint-disable-line n/no-process-exit
+});

Comment on lines 40 to 61
using engine = new WasmEngine('detectLeak');
await engine.parse(fileContent);
await engine.notifyEof();

const countQuery = `
SELECT count(*) as count
FROM slice s
JOIN args a1 ON s.arg_set_id = a1.arg_set_id
JOIN args a2 ON s.arg_set_id = a2.arg_set_id
WHERE s.name = 'FiberElement::Constructor' AND a1.key = 'debug.id' AND a2.key = 'debug.tag'
`;
const countResult = await engine.query(countQuery);
const countIt = countResult.iter({ count: 0 });
if (countIt.valid()) {
const count = countIt.get('count');
if (count === 0) {
console.error(
`Error: No FiberElement::Constructor events found in ${file}.`,
);
process.exit(1); // eslint-disable-line n/no-process-exit
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

process.exit(1) bypasses using disposal — WASM engine may not be cleaned up.

process.exit() at line 59 forces an abrupt termination that does not unwind the scope, so the Symbol.dispose registered by using engine on line 40 will never fire. While the OS reclaims memory on exit, the WASM engine may need graceful shutdown (e.g., flushing internal state).

Replace process.exit(1) with a thrown error (or set a flag and break), so the using block disposes the engine normally before the outer process.exit(1) on line 118.

Proposed fix
     if (countIt.valid()) {
       const count = countIt.get('count');
       if (count === 0) {
         console.error(
           `Error: No FiberElement::Constructor events found in ${file}.`,
         );
-        process.exit(1); // eslint-disable-line n/no-process-exit
+        throw new Error(
+          `No FiberElement::Constructor events found in ${file}.`,
+        );
       }
     }

Then catch the error at the call site (line 122) or let it propagate as a non-zero exit.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
using engine = new WasmEngine('detectLeak');
await engine.parse(fileContent);
await engine.notifyEof();
const countQuery = `
SELECT count(*) as count
FROM slice s
JOIN args a1 ON s.arg_set_id = a1.arg_set_id
JOIN args a2 ON s.arg_set_id = a2.arg_set_id
WHERE s.name = 'FiberElement::Constructor' AND a1.key = 'debug.id' AND a2.key = 'debug.tag'
`;
const countResult = await engine.query(countQuery);
const countIt = countResult.iter({ count: 0 });
if (countIt.valid()) {
const count = countIt.get('count');
if (count === 0) {
console.error(
`Error: No FiberElement::Constructor events found in ${file}.`,
);
process.exit(1); // eslint-disable-line n/no-process-exit
}
}
using engine = new WasmEngine('detectLeak');
await engine.parse(fileContent);
await engine.notifyEof();
const countQuery = `
SELECT count(*) as count
FROM slice s
JOIN args a1 ON s.arg_set_id = a1.arg_set_id
JOIN args a2 ON s.arg_set_id = a2.arg_set_id
WHERE s.name = 'FiberElement::Constructor' AND a1.key = 'debug.id' AND a2.key = 'debug.tag'
`;
const countResult = await engine.query(countQuery);
const countIt = countResult.iter({ count: 0 });
if (countIt.valid()) {
const count = countIt.get('count');
if (count === 0) {
console.error(
`Error: No FiberElement::Constructor events found in ${file}.`,
);
throw new Error(
`No FiberElement::Constructor events found in ${file}.`,
);
}
}
🤖 Prompt for AI Agents
In `@benchmark/react/scripts/detectLeak.mjs` around lines 40 - 61, The abrupt
process.exit(1) inside the scope where "using engine = new
WasmEngine('detectLeak')" prevents the engine's Symbol.dispose from running;
replace the direct process.exit(1) in the block that handles count === 0 with
throwing a descriptive Error (e.g., throw new Error(`No
FiberElement::Constructor events found in ${file}`)) or set a failure flag and
break so the using block can unwind and dispose the WasmEngine; ensure the
thrown error is caught or allowed to propagate to the existing top-level handler
(the call site that currently exits near line 118/122) so the program still
terminates non-zero but after proper disposal.

Comment on lines 40 to 42
using engine = new WasmEngine('detectLeak');
await engine.parse(fileContent);
await engine.notifyEof();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Node.js 22 explicit resource management using keyword support runtime

💡 Result:

Node.js v22 does not support JavaScript’s Explicit Resource Management syntax (using / await using) at runtime.

  • The feature (“Explicit Resource Management”) ships in V8 v13.8 (Chrome 134). (v8.dev)
  • Node.js v22.x uses V8 v12.4 (example: Node v22.4.0 ships with V8 v12.4.254.21), which is earlier than the required V8 version—so using will be a syntax error in Node 22. (nodejs.org)
  • A quick compatibility summary: Node 22 → V8 12.4 (no using), while Node 24 → V8 13.6 (adds using). (samthor.au)

What to do on Node 22

  • Transpile using / await using (e.g., TypeScript/Babel), and polyfill the runtime pieces (Symbol.dispose, Symbol.asyncDispose, DisposableStack, AsyncDisposableStack, SuppressedError). (typescriptlang.org)
  • Or avoid the syntax and just use a polyfill/shim for DisposableStack-style APIs (syntax still won’t work without transpilation). (core-js.io)

Citations:


Replace using syntax or add transpilation for Node.js 22 compatibility.

The using keyword (TC39 Explicit Resource Management) requires V8 v13.8+. Node.js 22 ships with V8 v12.4 and does not support this syntax—it will fail at runtime. Node.js 24 (V8 v13.6+) supports it. Since this repo targets Node.js ^22 || ^24, either:

  • Transpile this file with TypeScript/Babel and polyfill the runtime (DisposableStack, Symbol.dispose, etc.), or
  • Use a try-finally pattern or manual resource cleanup instead of using, or
  • Drop Node.js 22 support
🤖 Prompt for AI Agents
In `@benchmark/react/scripts/detectLeak.mjs` around lines 40 - 42, The file uses
the `using` resource-management syntax with `using engine = new
WasmEngine('detectLeak')` which Node.js 22 (V8 v12.4) does not support; replace
that pattern with explicit construction and manual cleanup: instantiate
WasmEngine (new WasmEngine('detectLeak')), run await engine.parse(fileContent)
and await engine.notifyEof() inside a try block, and in a finally block call the
engine's cleanup method (e.g., engine.dispose(), engine.close(), or
engine.free() — whichever `WasmEngine` exposes) to release resources, or
alternatively add transpilation/polyfills for TC39 Explicit Resource Management
if you choose to keep `using`. Ensure you reference the WasmEngine constructor
and its cleanup method when making the change.

@hzy hzy force-pushed the feat/react-leak-detection branch 4 times, most recently from 1c2fe84 to 59924eb Compare February 14, 2026 02:37
@hzy hzy force-pushed the feat/react-leak-detection branch from 59924eb to fb84b6d Compare February 14, 2026 14:32
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 14, 2026

Merging this PR will not alter performance

✅ 63 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing hzy:feat/react-leak-detection (1f915ce) with main (df4bc6e)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@relativeci
Copy link

relativeci bot commented Feb 14, 2026

Web Explorer

#7686 Bundle Size — 383.74KiB (0%).

1f915ce(current) vs df4bc6e main#7677(baseline)

Bundle metrics  no changes
                 Current
#7686
     Baseline
#7677
No change  Initial JS 154.88KiB 154.88KiB
No change  Initial CSS 35.06KiB 35.06KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 8 8
No change  Assets 8 8
No change  Modules 239 239
No change  Duplicate Modules 16 16
No change  Duplicate Code 2.99% 2.99%
No change  Packages 4 4
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7686
     Baseline
#7677
No change  JS 252.83KiB 252.83KiB
No change  Other 95.85KiB 95.85KiB
No change  CSS 35.06KiB 35.06KiB

Bundle analysis reportBranch hzy:feat/react-leak-detectionProject dashboard


Generated by RelativeCIDocumentationReport issue

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant