Skip to content

[Perf Tracks] Don't enumerate typed array props in dev - #36913

Merged
eps1lon merged 5 commits into
react:mainfrom
UditDewan:perf-track-typed-array-hang
Jul 2, 2026
Merged

[Perf Tracks] Don't enumerate typed array props in dev#36913
eps1lon merged 5 commits into
react:mainfrom
UditDewan:perf-track-typed-array-hang

Conversation

@UditDewan

Copy link
Copy Markdown
Contributor

Summary

Fixes #36200.

Passing a large TypedArray (e.g. new Float32Array(25000000)) as a prop
freezes rendering in development mode since 19.2 (production is unaffected).

The Performance Tracks property logger in ReactPerformanceTrackProperties.js
recurses into object prop values via addObjectToProperties, which enumerates
them with for...in. For a typed array this forces the engine to materialize a
string key for every index up front before the loop body runs, so the
existing OBJECT_WIDTH_LIMIT early-break (at 100 entries) never helps. A
25M-element array takes ~2s+ just to set up the enumeration, and the render
appears to hang.

Quick repro of the underlying cost:

const big = new Uint8Array(25000000);
let n = 0;
console.time('for-in');
for (const key in big) { n++; if (n >= 100) break; } // breaks after 100
console.timeEnd('for-in'); // ~2260ms

This change skips enumeration for typed arrays and instead shows the type and
length (e.g. Float32Array(25000000)), which is fast and more useful than a
truncated list of numeric indices. ArrayBuffer and DataView have no
enumerable indexed properties, so they were never affected and keep their
existing behavior.

The guard is added in two places:

  • addValueToProperties — the path used for component props (the reported
    case), producing the Type(length) descriptor.
  • addObjectToProperties — a defensive early return so the direct callers in
    Flight (e.g. resolved async values) also can't hit the slow enumeration.

How did you test this change?

  • Added a regression test (shows the type and length of typed arrays instead of enumerating them) asserting the compact Type(length) output.
  • Updated the existing does not show all properties of wide objects test to
    use a plain wide object so it keeps exercising the OBJECT_WIDTH_LIMIT
    truncation (typed arrays no longer reach that path).
  • yarn test ReactPerformanceTrack — all pass.
  • yarn flow dom-node — clean.
  • ESLint/Prettier — clean.

Passing a large TypedArray (e.g. `new Float32Array(25000000)`) as a prop
froze rendering in development since 19.2. The performance track property
logger recursed into the value and enumerated it with `for...in`, which
forces the engine to materialize a key for every index up front (~2s+ for
25M elements) even though the loop breaks after 100 entries.

Skip enumeration for typed arrays and show the type and length instead
(e.g. `Float32Array(25000000)`), which is both fast and more useful.
`ArrayBuffer` and `DataView` don't have enumerable indexed properties, so
they were never affected.

Fixes react#36200

Co-Authored-By: Baradhan-Madhu <26barum@gmail.com>
@meta-cla meta-cla Bot added the CLA Signed label Jul 1, 2026
@react-sizebot

react-sizebot commented Jul 1, 2026

Copy link
Copy Markdown

Comparing: 3508aee...05eae48

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 7.19 kB 7.19 kB = 1.91 kB 1.91 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 614.43 kB 614.43 kB = 108.59 kB 108.59 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 7.19 kB 7.19 kB +0.05% 1.91 kB 1.91 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 685.61 kB 685.61 kB = 120.08 kB 120.08 kB
facebook-www/ReactDOM-prod.classic.js = 705.96 kB 705.96 kB = 123.67 kB 123.67 kB
facebook-www/ReactDOM-prod.modern.js = 696.28 kB 696.28 kB = 122.05 kB 122.05 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.37% 239.90 kB 240.78 kB +0.57% 53.33 kB 53.63 kB
oss-stable/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.37% 239.92 kB 240.80 kB +0.57% 53.35 kB 53.65 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.37% 239.93 kB 240.81 kB +0.57% 53.35 kB 53.66 kB

Generated by 🚫 dangerJS against 05eae48


Scheduler.unstable_advanceTime(10);

const bigData = new Uint8Array(1000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why change this? The assertions should be updated instead to show the new behavior.

indent: number,
prefix: string,
): void {
if (ArrayBuffer.isView(object) && typeof object.length === 'number') {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need the object.length check?

@UditDewan
UditDewan requested a review from eps1lon July 1, 2026 21:46
@eps1lon

eps1lon commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Can you check why we don't have a similar issue when sending Server Component props with Flight for debugging in React DevTools?

@UditDewan

Copy link
Copy Markdown
Contributor Author

Flight never has this problem because it type-checks typed arrays before any generic property enumeration and ships them as raw binary, so it never really walks their indices.
In renderDebugModel (the path DevTools uses for Server Component props/debug info), every typed array flavor is intercepted explicitly at packages/react-server/src/ReactFlightServer.js:5156-5206: ArrayBuffer, Int8Array … BigUint64Array, DataView, and routed to serializeDebugTypedArray, which calls emitTypedArrayChunk (line 3326). That emits the underlying bytes as an out-of-band binary row in the stream (essentially a memcpy of the buffer), and the Flight client reconstructs a real typed array from those bytes. The generic for...in enumeration at line 5221 is only reached by objects that fall through all those checks, so a Uint8Array(10_000_000) costs O(byteLength) of I/O, never millions of materialized index keys. The regular (non-debug) prop path in emitChunk (lines 5826-5888) does the same thing before falling back to JSON.
The debug path also has belt-and-suspenders limits the perf track lacks: an objectLimit counter that turns excess objects into deferred placeholders, and strings over 1MB are replaced with a placeholder (line 5250).
The performance-track code hit the hang because it's the opposite kind of serializer: a generic pretty-printer that took whatever object it was handed and enumerated it with for...in to produce human-readable rows for the Performance panel, synchronously on the main thread. Typed arrays are exotic objects whose every index is an enumerable own property, so for...in forced the engine to materialize a string key per element. It had no type-specific fast path until this PR added the ArrayBuffer.isView guard, which mirrors, in miniature, what Flight has always done structurally.

@eps1lon
eps1lon merged commit e71a639 into react:main Jul 2, 2026
238 checks passed
github-actions Bot pushed a commit that referenced this pull request Jul 2, 2026
github-actions Bot pushed a commit that referenced this pull request Jul 2, 2026
github-actions Bot pushed a commit to HaroldHuanrongLIU/react that referenced this pull request Jul 5, 2026
github-actions Bot pushed a commit to HaroldHuanrongLIU/react that referenced this pull request Jul 5, 2026
@UditDewan
UditDewan deleted the perf-track-typed-array-hang branch July 12, 2026 02:53
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.

Bug: Large TypedArray props hang browser in dev mode in 19.2.X

3 participants