Skip to content

fix(runtime): release cb reference after __commonJS factory initialization#9067

Merged
sapphi-red merged 4 commits into
rolldown:mainfrom
hyfdev-agent:fix/commonjs-factory-gc-leak-v2
Apr 13, 2026
Merged

fix(runtime): release cb reference after __commonJS factory initialization#9067
sapphi-red merged 4 commits into
rolldown:mainfrom
hyfdev-agent:fix/commonjs-factory-gc-leak-v2

Conversation

@hyfdev-agent

Copy link
Copy Markdown
Contributor

Summary

After the first __require() call, mod is set and cb (the factory function) is never accessed again. Without an explicit release, cb is permanently retained in the closure and cannot be garbage collected.

This is a memory leak in long-lived Node.js processes (e.g. SSR servers loading bundles via vm.createContext): each factory can contain thousands of lines of compiled library code, and a typical bundle has hundreds of CJS modules — all kept in heap indefinitely after initialization.

Fix: set cb = null after the factory call via comma operator.

// Before
var __commonJS = (cb, mod) => function __require() {
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};

// After
var __commonJS = (cb, mod) => function __require() {
  return mod || ((0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), cb = null), mod.exports;
};

Also adds meta/design/runtime-helpers.md documenting the design decision.

Fixes #9063

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a runtime memory-retention issue in Rolldown’s CommonJS runtime helpers by releasing the module factory reference after first initialization, and documents the design decision.

Changes:

  • Update __commonJS and __commonJSMin to clear the cb (factory) reference after the first successful factory invocation.
  • Add a design note describing the runtime-helper behavior and rationale.
  • Refresh integration snapshot artifacts to reflect the updated helper output.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
meta/design/runtime-helpers.md Documents the rationale for releasing the CommonJS factory reference after initialization.
crates/rolldown/src/runtime/runtime-base.js Updates __commonJS / __commonJSMin to set cb = null after first initialization to allow GC.
crates/rolldown/tests/rolldown/tree_shaking/commonjs_inline_const_computed_key_write_object/artifacts.snap Updates snapshot for new __commonJSMin output.
crates/rolldown/tests/rolldown/topics/keep_names/if_stmt/artifacts.snap Updates snapshot for new __commonJSMin output.
crates/rolldown/tests/rolldown/misc/common_js_min/artifacts.snap Updates minified snapshot reflecting cb = null clearing.
crates/rolldown/tests/esbuild/importstar_ts/ts_import_star_common_js_no_capture/artifacts.snap Updates snapshot for new __commonJSMin output.
crates/rolldown/tests/esbuild/default/top_level_await_forbidden_require/artifacts.snap Updates snapshot for new __commonJSMin output.
crates/rolldown/tests/esbuild/default/top_level_await_forbidden_require_dead_branch/artifacts.snap Updates snapshot for new __commonJSMin output.

Comment thread crates/rolldown/src/runtime/runtime-base.js
Comment thread crates/rolldown/src/runtime/runtime-base.js
Comment thread meta/design/runtime-helpers.md Outdated
…alization

After the first call, `mod` is set and `cb` is never accessed again.
Without an explicit release, the factory function is permanently retained
in the closure and cannot be garbage collected.

This is a memory leak in long-lived processes (e.g. SSR servers loading
bundles via `vm.createContext`): each factory can contain thousands of
lines of compiled library code, and a typical bundle has hundreds of CJS
modules — all kept in heap indefinitely after initialization.

Fix: set `cb = null` after the factory call via comma operator, consistent
with the existing coding style.

Also add `meta/design/runtime-helpers.md` documenting the design decision.

Fixes: rolldown#9063
@hyfdev-agent
hyfdev-agent force-pushed the fix/commonjs-factory-gc-leak-v2 branch from 68b6d53 to 8d12563 Compare April 12, 2026 04:00
@pkg-pr-new

pkg-pr-new Bot commented Apr 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@rolldown/browser

npm i https://pkg.pr.new/@rolldown/browser@9067

@rolldown/debug

npm i https://pkg.pr.new/@rolldown/debug@9067

@rolldown/pluginutils

npm i https://pkg.pr.new/@rolldown/pluginutils@9067

rolldown

npm i https://pkg.pr.new/rolldown@9067

@rolldown/binding-android-arm64

npm i https://pkg.pr.new/@rolldown/binding-android-arm64@9067

@rolldown/binding-darwin-arm64

npm i https://pkg.pr.new/@rolldown/binding-darwin-arm64@9067

@rolldown/binding-darwin-x64

npm i https://pkg.pr.new/@rolldown/binding-darwin-x64@9067

@rolldown/binding-freebsd-x64

npm i https://pkg.pr.new/@rolldown/binding-freebsd-x64@9067

@rolldown/binding-linux-arm-gnueabihf

npm i https://pkg.pr.new/@rolldown/binding-linux-arm-gnueabihf@9067

@rolldown/binding-linux-arm64-gnu

npm i https://pkg.pr.new/@rolldown/binding-linux-arm64-gnu@9067

@rolldown/binding-linux-arm64-musl

npm i https://pkg.pr.new/@rolldown/binding-linux-arm64-musl@9067

@rolldown/binding-linux-ppc64-gnu

npm i https://pkg.pr.new/@rolldown/binding-linux-ppc64-gnu@9067

@rolldown/binding-linux-s390x-gnu

npm i https://pkg.pr.new/@rolldown/binding-linux-s390x-gnu@9067

@rolldown/binding-linux-x64-gnu

npm i https://pkg.pr.new/@rolldown/binding-linux-x64-gnu@9067

@rolldown/binding-linux-x64-musl

npm i https://pkg.pr.new/@rolldown/binding-linux-x64-musl@9067

@rolldown/binding-openharmony-arm64

npm i https://pkg.pr.new/@rolldown/binding-openharmony-arm64@9067

@rolldown/binding-wasm32-wasi

npm i https://pkg.pr.new/@rolldown/binding-wasm32-wasi@9067

@rolldown/binding-win32-arm64-msvc

npm i https://pkg.pr.new/@rolldown/binding-win32-arm64-msvc@9067

@rolldown/binding-win32-x64-msvc

npm i https://pkg.pr.new/@rolldown/binding-win32-x64-msvc@9067

commit: cf19877

@graphite-app

graphite-app Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • Apr 13, 5:57 AM UTC: @hyf0-agent we removed the merge queue label because we could not find a Graphite account associated with your GitHub profile.

You must have a Graphite account in order to use the merge queue. Create an account and try again using this link

  • Apr 13, 5:57 AM UTC: @hyf0-agent we removed the merge queue label because we could not find a Graphite account associated with your GitHub profile.

You must have a Graphite account in order to use the merge queue. Create an account and try again using this link

@sapphi-red
sapphi-red enabled auto-merge (squash) April 13, 2026 06:00
@sapphi-red
sapphi-red merged commit d06c805 into rolldown:main Apr 13, 2026
26 checks passed
This was referenced Apr 15, 2026
shulaoda added a commit that referenced this pull request Apr 16, 2026
## [1.0.0-rc.16] - 2026-04-16

### 🚀 Features

- const enum cross-module inlining support (#8796) by @Dunqing
- implement module tagging system for code splitting (#9045) by @hyf0

### 🐛 Bug Fixes

- rolldown_plugin_vite_manifest: handle duplicate chunk names for CSS entries (#9059) by @sapphi-red
- improve error message for invalid return values in function options (#9125) by @shulaoda
- await async export-star init wrappers (#9101) by @thezzisu
- never panic during diagnostic emission (#9091) by @IWANABETHATGUY
- include array rest pattern in binding_identifiers (#9112) by @IWANABETHATGUY
- rolldown: set worker thread count with ROLLDOWN_WORKER_THREADS (#9086) by @fpotter
- rolldown_plugin_lazy_compilation: escape request ID in proxy modules (#9102) by @h-a-n-a
- treat namespace member access as side-effect-free (#9099) by @IWANABETHATGUY
- relax overly conservative side-effect leak check in chunk optimizer (#9085) by @IWANABETHATGUY
- runtime: release `cb` reference after `__commonJS` factory initialization (#9067) by @hyf0-agent
- `@__NO_SIDE_EFFECTS__` wrapper should not remove dynamic imports (#9075) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: use POSIX path join/normalize for glob resolution (#9077) by @shulaoda
- emit REQUIRE_TLA error when require() loads a module with top-level await (#9071) by @jaehafe
- emit namespace declaration for empty modules in manual chunks (#8993) by @privatenumber
- rolldown_plugin_vite_import_glob: keep common base on path segment boundary (#9070) by @shulaoda
- prevent circular runtime helper imports during facade elimination (#8989) (#9057) by @IWANABETHATGUY
- correct circular dependency check in facade elimination (#9047) by @h-a-n-a
- docs: correct dead link in CodeSplittingGroup.tags JSDoc (#9051) by @hyf0
- emit DUPLICATE_SHEBANG warning when banner contains shebang (#9026) by @IWANABETHATGUY

### 🚜 Refactor

- use semantic reference flags for member write detection (#9060) by @Dunqing
- extract UsedSymbolRefs newtype wrapper (#9130) by @IWANABETHATGUY
- dedupe await wrapping in export-star init emit (#9119) by @IWANABETHATGUY
- calculate side-effect-free function symbols on demand (#9120) by @IWANABETHATGUY
- extract duplicated top-level await handling into shared helper (#9087) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: use split_first for get_common_base (#9069) by @shulaoda
- simplify ESM init deduplication with idiomatic insert check (#9044) by @IWANABETHATGUY

### 📚 Documentation

- document runtime module placement strategy in code-splitting design (#9062) by @IWANABETHATGUY
- clarify `options` hook behavior difference with Rollup in watch mode (#9053) by @sapphi-red
- meta/design: introduce module tags (#9017) by @hyf0

### ⚡ Performance

- convert `generate_transitive_esm_init` to iterative (#9046) by @IWANABETHATGUY

### 🧪 Testing

- merge strict/non_strict test variants using configVariants (#9089) by @IWANABETHATGUY

### ⚙️ Miscellaneous Tasks

- disable Renovate auto-updates for oxc packages (#9129) by @IWANABETHATGUY
- upgrade [email protected] (#9127) by @Dunqing
- deps: update napi to v3.8.5 (#9126) by @renovate[bot]
- deps: update dependency @napi-rs/cli to v3.6.2 (#9123) by @renovate[bot]
- move lazy-compilation design doc (#9117) by @h-a-n-a
- deps: update dependency vite-plus to v0.1.18 (#9118) by @renovate[bot]
- deps: update dependency vite-plus to v0.1.17 (#9113) by @renovate[bot]
- deps: update oxc to v0.125.0 (#9094) by @renovate[bot]
- deps: update dependency follow-redirects to v1.16.0 [security] (#9103) by @renovate[bot]
- deps: update test262 submodule for tests (#9097) by @sapphi-red
- deps: update crate-ci/typos action to v1.45.1 (#9096) by @renovate[bot]
- deps: update rust crates (#9081) by @renovate[bot]
- deps: update npm packages (#9080) by @renovate[bot]
- remove outdated TODO in determine_module_exports_kind (#9072) by @jaehafe
- rust/test: support `extendedTests: false` shorthand in test config (#9050) by @hyf0
- ci: extract shared infra-changes anchor in path filters (#9054) by @hyf0
- add docs build check to catch dead links in PRs (#9052) by @hyf0

### ❤️ New Contributors

* @thezzisu made their first contribution in [#9101](#9101)
* @fpotter made their first contribution in [#9086](#9086)
* @jaehafe made their first contribution in [#9071](#9071)
* @privatenumber made their first contribution in [#8993](#8993)

Co-authored-by: shulaoda <[email protected]>
graphite-app Bot pushed a commit that referenced this pull request Jul 15, 2026
### Description

`crates/rolldown/src/runtime/index.js` is not part of the build, and hasn''t been for a long time. This PR deletes it.

The runtime module is assembled in [`runtime_module_task.rs`](https://github.com/rolldown/rolldown/blob/main/crates/rolldown/src/module_loader/runtime_module_task.rs#L33-L36) from exactly four `include_str!`ed files:

```rust
const RUNTIME_BASE_JS: &str = include_str!("../runtime/runtime-base.js");
const RUNTIME_TAIL_JS: &str = include_str!("../runtime/runtime-tail.js");
const RUNTIME_HEAD_NODE_JS: &str = include_str!("../runtime/runtime-head-node.js");
const RUNTIME_TAIL_NODE_JS: &str = include_str!("../runtime/runtime-tail-node.js");
```

`index.js` is not among them. Nothing else in the repo references it either — there''s no `build.rs` in the crate, and no path reference to it in Rust, JS, or `knip.jsonc`. Its only remaining coverage is incidental: the sibling `tsconfig.json` globs `./**/*`, so it gets type-checked and formatted alongside the files that actually ship.

### How it got here

It''s the original esbuild runtime port from #37. #375 added `runtime-without-comments.js` as the copy actually fed to the bundler; that file was renamed to `runtime-base.js` in #2919 and later split with `runtime-tail.js`. `index.js` was never wired back up — it survived as a copy that edits were hand-mirrored into.

### Why delete rather than leave it

The mirroring has fallen behind, so the file now actively misinforms:

| | `runtime-base.js` (ships) | `index.js` (dead) |
|---|---|---|
| `cb = null` release in `__commonJS` (#9067) | present | **missing** |
| `__exportAll` + `Symbol.toStringTag` | present | still the old `__export` |
| `__require` error text | rewritten to link the rolldown docs (in `runtime-tail.js`) | still esbuild''s "Dynamic require of …" |

Someone grepping for a runtime helper can land here and draw the wrong conclusion about what ships — the missing #9067 fix reads as a live memory leak. It isn''t, because the file is inert. Deleting it removes the trap and the ongoing cost of keeping a second copy in sync.

### Test Plan

Deletion only; no shipped output changes, so no snapshots move. `include_str!` resolves at compile time, so a stale reference would break the build:

- `cargo check -p rolldown --all-targets` — passes
- `tsc --noEmit -p crates/rolldown/src/runtime/tsconfig.json` — passes
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.

__commonJS runtime helper retains factory reference after initialization, preventing GC in long-lived processes

3 participants