Skip to content

fix: stabilize chunk assignment across parallel file reads#6362

Merged
lukastaegert merged 8 commits into
rollup:masterfrom
sonukapoor:fix/issue-5902
May 14, 2026
Merged

fix: stabilize chunk assignment across parallel file reads#6362
lukastaegert merged 8 commits into
rollup:masterfrom
sonukapoor:fix/issue-5902

Conversation

@sonukapoor
Copy link
Copy Markdown
Contributor

@sonukapoor sonukapoor commented Apr 20, 2026

This PR contains:

  • bugfix
  • feature
  • refactor
  • documentation
  • other

Are tests included?

  • yes (bugfixes and features will not be merged without tests)
  • no

Breaking Changes?

  • yes (breaking changes will not be merged unless absolutely necessary)
  • no

List any relevant issue numbers:

Description

Rollup stores parsed modules in a Map<string, Module> (modulesById). With parallel file reads, modules are inserted in whichever order their resolveId hooks complete, making the Map's iteration order non-deterministic across builds. Two places depended on this order:

  1. Bundle.assignManualChunks iterated modulesById.values() directly, so a stateful manualChunks function (one whose return value depends on how many times it has been called) would produce different chunk assignments on different runs. Fixed by sorting the modules alphabetically by ID before iterating.

  2. assignExportsToMangledNames / assignExportsToNames in src/utils/exportNames.ts iterated the chunk's exports Set in insertion order. Because the Set is populated during module graph traversal, its order could also vary. Fixed by sorting the exports before assigning aliases, using a stable comparator: module ID → source declaration position → base name → export key → constructor name. Synthetic variables such as NamespaceVariable have no source declaration, so they receive Number.MAX_SAFE_INTEGER as their position and always sort after regular variables within the same module — preventing them from stealing natural export names in CJS/AMD/UMD formats.

The regression test builds the same graph twice with different resolveId delay patterns (so modules resolve in different orders) and asserts that chunk file names, hashes, and generated code are identical. Five chunking-form snapshot fixtures are updated to reflect the new deterministic sort order for ES and System output formats.

Copilot AI review requested due to automatic review settings April 20, 2026 14:35
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 20, 2026

Someone is attempting to deploy a commit to the rollup-js Team on Vercel.

A member of the Team first needs to authorize it.

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

Fixes nondeterministic chunk assignment/hashing when parallel file reads complete in different orders (resolves #5902) by making chunk assignment inputs and merge ordering deterministic.

Changes:

  • Derive entry indices from a stable sorted entry list and convert per-entry tracking to be module-keyed before indexing.
  • Add deterministic ordering/tie-breakers for chunk grouping, partitioning, merging, and final output ordering.
  • Add a regression test that runs equivalent builds with different file-read completion orders and asserts identical output.

Reviewed changes

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

File Description
src/utils/chunkAssignment.ts Makes entry indexing and multiple chunk ordering/merge steps deterministic to stabilize chunk relationships and hashes.
test/misc/misc.js Adds a memfs-based regression test that varies read completion order and asserts stable hashed outputs.

Comment thread src/utils/chunkAssignment.ts Outdated
Comment thread src/utils/chunkAssignment.ts Outdated
@sonukapoor
Copy link
Copy Markdown
Contributor Author

Good catch. I consolidated the bitmask signature logic into a single helper to avoid drift. I also moved the deterministic sort key computation out of the sort comparator so signatures and module-id keys are computed once per sort pass.

Comment thread test/misc/misc.js
@sonukapoor sonukapoor marked this pull request as draft April 21, 2026 16:42
@sonukapoor sonukapoor marked this pull request as ready for review April 21, 2026 17:31
@sonukapoor sonukapoor requested a review from TrickyPi April 21, 2026 17:31
@sonukapoor sonukapoor force-pushed the fix/issue-5902 branch 2 times, most recently from 04bfe7e to f40903b Compare April 23, 2026 22:54
Rollup stores parsed modules in a Map<string, Module> (modulesById).
With parallel file reads, modules are inserted in whichever order their
resolveId hooks complete, making iteration order non-deterministic across
builds. Two code paths depended on this order:

- Bundle.assignManualChunks iterated modulesById.values() directly, so
  a stateful manualChunks function produced different chunk assignments
  on different runs. Fix: sort modules alphabetically by ID before
  iterating.

- assignExportsToMangledNames and assignExportsToNames iterated the
  chunk exports Set in insertion order. Fix: sort exports before
  assigning aliases using a stable comparator: module ID → source
  declaration position → base name → export key → constructor name.
  Synthetic variables (NamespaceVariable etc.) have no source
  declaration and receive Number.MAX_SAFE_INTEGER as their position so
  they always sort after regular variables, preventing them from
  stealing natural export names in CJS/AMD/UMD formats.

Adds a regression test that builds the same graph twice with different
resolveId delay patterns and asserts identical chunk names, hashes, and
code. Updates five chunking-form snapshots whose previous expected
output reflected arbitrary insertion order rather than the new
deterministic sort (ES and System formats only).
@TrickyPi
Copy link
Copy Markdown
Member

I carefully debugged the repro today and found that the inconsistent hash names come from the linked repro calling this.emitFile to emit chunks in the transform hook.

Since the file read order can affect the order in which transform is called for each module, the call order of this.emitFile can also become inconsistent. This means that, during chunk generation, the resulting chunk order can be inconsistent as well. After all chunk.link() calls have finished, the order of chunk.exports may then become inconsistent, which eventually leads to inconsistent final hash names.

I think sorting the exports is a reasonable approach, but it is only needed when generating mangled names, because exportsByName is sorted later when the final exports are rendered.

I have updated your PR directly with the description above and added a regression test.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.78%. Comparing base (82a0fe7) to head (2a2f46d).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #6362   +/-   ##
=======================================
  Coverage   98.78%   98.78%           
=======================================
  Files         274      274           
  Lines       10793    10795    +2     
  Branches     2882     2883    +1     
=======================================
+ Hits        10662    10664    +2     
  Misses         89       89           
  Partials       42       42           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonukapoor
Copy link
Copy Markdown
Contributor Author

@TrickyPi The makes sense, and the root cause analysis is more precise - emitFile call order from resolveId/transform is indeed the non-determinism source, and sorting by name in assignExportsToMangledNames is the right place to fix it. The simplified comparator and the focused regression test are cleaner.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rollup Ready Ready Preview, Comment May 14, 2026 8:17am

Request Review

@sonukapoor
Copy link
Copy Markdown
Contributor Author

@TrickyPi is there anything pending on this PR before it can be merged?

@TrickyPi
Copy link
Copy Markdown
Member

I don’t think there’s anything pending from my side. It should be ready to merge whenever @lukastaegert has time.

@lukastaegert
Copy link
Copy Markdown
Member

Hi, thanks for the PR and sorry for the wait. I added another test and a small change to make the sorting more stable. Will merge this once I sorted out CI...

@lukastaegert lukastaegert added this pull request to the merge queue May 14, 2026
Merged via the queue into rollup:master with commit 6aa3248 May 14, 2026
47 checks passed
@wmertens
Copy link
Copy Markdown
Contributor

I'm soooo happy this is fixed; is this also a problem in rolldown?

@github-actions
Copy link
Copy Markdown

This PR has been released as part of [email protected]. You can test it via npm install rollup.

graphite-app Bot pushed a commit to rolldown/rolldown that referenced this pull request May 19, 2026
`codeSplitting.groups[].name` was called in the order of `ModuleIdx` which is assigned in module-load completion order. This is a problem if `codeSplitting.groups[].name`behaves differently depending on the call order. While a stateful `codeSplitting.groups[].name`is not a good idea, it is also difficult to detect that and I think it's better to handle this on Rolldown side.

This PR makes `codeSplitting.groups[].name` to be called in the `stable_id` order so that the order is deterministic.

This PR is similar to the first part of rollup/rollup#6362. (The second part was already handled)
V1OL3TF0X pushed a commit to V1OL3TF0X/rolldown that referenced this pull request May 25, 2026
…own#9457)

`codeSplitting.groups[].name` was called in the order of `ModuleIdx` which is assigned in module-load completion order. This is a problem if `codeSplitting.groups[].name`behaves differently depending on the call order. While a stateful `codeSplitting.groups[].name`is not a good idea, it is also difficult to detect that and I think it's better to handle this on Rolldown side.

This PR makes `codeSplitting.groups[].name` to be called in the `stable_id` order so that the order is deterministic.

This PR is similar to the first part of rollup/rollup#6362. (The second part was already handled)
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.

Chunk hashes are unstable when maxParallelFileOps != 1

5 participants