Uh oh!
There was an error while loading. Please reload this page.
[Flight] Clear chunk reason after successful module initialization - #36024
Conversation
Comparing: 7b5b561...b43ee2a Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: (No significant changes) |
When `requireModule` triggers a reentrant `readChunk` on the same module chunk, the reentrant call can fail and set `chunk.reason` to an error. After the outer `requireModule` succeeds, the chunk transitions to initialized but retains the stale error as `reason`. When the Flight response stream later closes, it iterates all chunks and expects `reason` on initialized chunks to be a `FlightStreamController`. Since the stale `reason` is an `Error` object instead, calling `chunk.reason.error()` crashes with `TypeError: chunk.reason.error is not a function`. The reentrancy can occur when module evaluation synchronously triggers `readChunk` on the same chunk — for example, when code called during evaluation tries to resolve the client reference for the module that is currently being initialized. In Fizz SSR, `captureOwnerStack()` can trigger this because it constructs component stacks that resolve lazy client references via `readChunk`. The reentrant `requireModule` call returns the module's namespace object, but since the module is still being evaluated, accessing the export binding throws a TDZ (Temporal Dead Zone) `ReferenceError`. This sets the chunk to the errored state, and the `ReferenceError` becomes the stale `chunk.reason` after the outer call succeeds. This scenario is triggered in Next.js when a client module calls an instrumented API like `Math.random()` in module scope, which synchronously invokes `captureOwnerStack()`.
523741a to
bc64684Compare
eps1lon
left a comment
There was a problem hiding this comment.
There's only one place left where we don't set the reason field after changing status: https://github.com/facebook/react/blob/3bc2d414287e62a7b74731c6c7b837270353a339/packages/react-client/src/ReactFlightClient.js#L1075-L1079
What's the reason we're not doing it there? Worth adding a comment since it's unique now.
On the Reply side, https://github.com/facebook/react/blob/3bc2d414287e62a7b74731c6c7b837270353a339/packages/react-server/src/ReactFlightReplyServer.js#L476-L481 is left.
Uh oh!
There was an error while loading. Please reload this page.
…eact#36024) When `requireModule` triggers a reentrant `readChunk` on the same module chunk, the reentrant call can fail and set `chunk.reason` to an error. After the outer `requireModule` succeeds, the chunk transitions to initialized but retains the stale error as `reason`. When the Flight response stream later closes, it iterates all chunks and expects `reason` on initialized chunks to be a `FlightStreamController`. Since the stale `reason` is an `Error` object instead, calling `chunk.reason.error()` crashes with `TypeError: chunk.reason.error is not a function`. The reentrancy can occur when module evaluation synchronously triggers `readChunk` on the same chunk — for example, when code called during evaluation tries to resolve the client reference for the module that is currently being initialized. In Fizz SSR, `captureOwnerStack()` can trigger this because it constructs component stacks that resolve lazy client references via `readChunk`. The reentrant `requireModule` call returns the module's namespace object, but since the module is still being evaluated, accessing the export binding throws a TDZ (Temporal Dead Zone) `ReferenceError`. This sets the chunk to the errored state, and the `ReferenceError` becomes the stale `chunk.reason` after the outer call succeeds. This scenario is triggered in Next.js when a client module calls an instrumented API like `Math.random()` in module scope, which synchronously invokes `captureOwnerStack()`. DiffTrain build for [5e9eedb](react@5e9eedb)
When
requireModuletriggers a reentrantreadChunkon the same module chunk, the reentrant call can fail and setchunk.reasonto an error. After the outerrequireModulesucceeds, the chunk transitions to initialized but retains the stale error asreason.When the Flight response stream later closes, it iterates all chunks and expects
reasonon initialized chunks to be aFlightStreamController. Since the stalereasonis anErrorobject instead, callingchunk.reason.error()crashes withTypeError: chunk.reason.error is not a function.The reentrancy can occur when module evaluation synchronously triggers
readChunkon the same chunk — for example, when code called during evaluation tries to resolve the client reference for the module that is currently being initialized. In Fizz SSR,captureOwnerStack()can trigger this because it constructs component stacks that resolve lazy client references viareadChunk. The reentrantrequireModulecall returns the module's namespace object, but since the module is still being evaluated, accessing the export binding throws a TDZ (Temporal Dead Zone)ReferenceError. This sets the chunk to the errored state, and theReferenceErrorbecomes the stalechunk.reasonafter the outer call succeeds.This scenario is triggered in Next.js when a client module calls an instrumented API like
Math.random()in module scope, which synchronously invokescaptureOwnerStack().