@samitouri / QOS-React-1 / commits / 4a1f29079c

[Fizz] Add Owner Stacks when render is aborted (#32735)

Sebastian "Sebbie" Silbermann committed Jun 2, 2025 at 19:27 UTC 4a1f29079ccc61659e026bbcf205bc8d53780927
3 files changed +81 -1
packages/react-noop-renderer/src/ReactNoopServer.js
+1
@@ -364,6 +364,7 @@ function render(children: React$Element<any>, options?: Options): Destination {
364 children,
365 null,
366 null,
367 + null,
368 options ? options.progressiveChunkSize : undefined,
369 options ? options.onError : undefined,
370 options ? options.onAllReady : undefined,
packages/react-server/src/ReactFizzServer.js
+26 -1
@@ -4762,6 +4762,27 @@ function abortTask(task: Task, request: Request, error: mixed): void {
4762 }
4763 }
4764
4765 +function abortTaskDEV(task: Task, request: Request, error: mixed): void {
4766 + if (__DEV__) {
4767 + const prevTaskInDEV = currentTaskInDEV;
4768 + const prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack;
4769 + setCurrentTaskInDEV(task);
4770 + ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;
4771 + try {
4772 + abortTask(task, request, error);
4773 + } finally {
4774 + setCurrentTaskInDEV(prevTaskInDEV);
4775 + ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl;
4776 + }
4777 + } else {
4778 + // These errors should never make it into a build so we don't need to encode them in codes.json
4779 + // eslint-disable-next-line react-internal/prod-error-codes
4780 + throw new Error(
4781 + 'abortTaskDEV should never be called in production mode. This is a bug in React.',
4782 + );
4783 + }
4784 +}
4785 +
4786 function safelyEmitEarlyPreloads(
4787 request: Request,
4788 shellComplete: boolean,
@@ -6111,7 +6132,11 @@ export function abort(request: Request, reason: mixed): void {
6132 // This error isn't necessarily fatal in this case but we need to stash it
6133 // so we can use it to abort any pending work
6134 request.fatalError = error;
6114 - abortableTasks.forEach(task => abortTask(task, request, error));
6135 + if (__DEV__) {
6136 + abortableTasks.forEach(task => abortTaskDEV(task, request, error));
6137 + } else {
6138 + abortableTasks.forEach(task => abortTask(task, request, error));
6139 + }
6140 abortableTasks.clear();
6141 }
6142 if (request.destination !== null) {
packages/react-server/src/__tests__/ReactServer-test.js
+54
@@ -10,13 +10,28 @@
10
11 'use strict';
12
13 +let act;
14 let React;
15 let ReactNoopServer;
16
17 +function normalizeCodeLocInfo(str) {
18 + return (
19 + str &&
20 + str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
21 + const dot = name.lastIndexOf('.');
22 + if (dot !== -1) {
23 + name = name.slice(dot + 1);
24 + }
25 + return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
26 + })
27 + );
28 +}
29 +
30 describe('ReactServer', () => {
31 beforeEach(() => {
32 jest.resetModules();
33
34 + act = require('internal-test-utils').act;
35 React = require('react');
36 ReactNoopServer = require('react-noop-renderer/server');
37 });
@@ -32,4 +47,43 @@ describe('ReactServer', () => {
47 const result = ReactNoopServer.render(<div>hello world</div>);
48 expect(result.root).toEqual(div('hello world'));
49 });
50 +
51 + it('has Owner Stacks in DEV when aborted', async () => {
52 + function Component({promise}) {
53 + React.use(promise);
54 + return <div>Hello, Dave!</div>;
55 + }
56 + function App({promise}) {
57 + return <Component promise={promise} />;
58 + }
59 +
60 + let caughtError;
61 + let componentStack;
62 + let ownerStack;
63 + const result = ReactNoopServer.render(
64 + <App promise={new Promise(() => {})} />,
65 + {
66 + onError: (error, errorInfo) => {
67 + caughtError = error;
68 + componentStack = errorInfo.componentStack;
69 + ownerStack = __DEV__ ? React.captureOwnerStack() : null;
70 + },
71 + },
72 + );
73 +
74 + await act(async () => {
75 + result.abort();
76 + });
77 + expect(caughtError).toEqual(
78 + expect.objectContaining({
79 + message: 'The render was aborted by the server without a reason.',
80 + }),
81 + );
82 + expect(normalizeCodeLocInfo(componentStack)).toEqual(
83 + '\n in Component (at **)' + '\n in App (at **)',
84 + );
85 + expect(normalizeCodeLocInfo(ownerStack)).toEqual(
86 + __DEV__ ? '\n in App (at **)' : null,
87 + );
88 + });
89 });