Skip to content

Commit ab5b304

Browse files
committed
[Flight][Static] When prerendering serialize infinite promise when aborting with no reason
When prerendering if you abort the prerender without a reason instead of erroring each remaining task complete it with a promise that never resolve Unfortunately when you abort with an AbortSignal without a value the aborted reason is defaulted to an AbortError DOMexception. We test for this and basically just say that if you abort with an AbortError DOMException that is equivalent to aborting with nothing. Practically this should be fine because usually you abort with a specific reason.
1 parent fa6eab5 commit ab5b304

16 files changed

Lines changed: 285 additions & 23 deletions

packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ import type {Thenable} from 'shared/ReactTypes';
2020

2121
import {Readable} from 'stream';
2222

23+
import {enableHalt} from 'shared/ReactFeatureFlags';
24+
2325
import {
2426
createRequest,
2527
startWork,
2628
startFlowing,
2729
stopFlowing,
2830
abort,
31+
halt,
2932
} from 'react-server/src/ReactFlightServer';
3033

3134
import {
@@ -187,10 +190,20 @@ function prerenderToNodeStream(
187190
if (options && options.signal) {
188191
const signal = options.signal;
189192
if (signal.aborted) {
190-
abort(request, (signal: any).reason);
193+
const reason = (signal: any).reason;
194+
if (enableHalt) {
195+
halt(request, reason);
196+
} else {
197+
abort(request, reason);
198+
}
191199
} else {
192200
const listener = () => {
193-
abort(request, (signal: any).reason);
201+
const reason = (signal: any).reason;
202+
if (enableHalt) {
203+
halt(request, reason);
204+
} else {
205+
abort(request, reason);
206+
}
194207
signal.removeEventListener('abort', listener);
195208
};
196209
signal.addEventListener('abort', listener);

packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import type {Thenable} from 'shared/ReactTypes';
1212
import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
1313
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
1414

15+
import {enableHalt} from 'shared/ReactFeatureFlags';
16+
1517
import {
1618
createRequest,
1719
startWork,
1820
startFlowing,
1921
stopFlowing,
2022
abort,
23+
halt,
2124
} from 'react-server/src/ReactFlightServer';
2225

2326
import {
@@ -146,10 +149,20 @@ function prerender(
146149
if (options && options.signal) {
147150
const signal = options.signal;
148151
if (signal.aborted) {
149-
abort(request, (signal: any).reason);
152+
const reason = (signal: any).reason;
153+
if (enableHalt) {
154+
halt(request, reason);
155+
} else {
156+
abort(request, reason);
157+
}
150158
} else {
151159
const listener = () => {
152-
abort(request, (signal: any).reason);
160+
const reason = (signal: any).reason;
161+
if (enableHalt) {
162+
halt(request, reason);
163+
} else {
164+
abort(request, reason);
165+
}
153166
signal.removeEventListener('abort', listener);
154167
};
155168
signal.addEventListener('abort', listener);

packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import type {Thenable} from 'shared/ReactTypes';
1212
import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
1313
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
1414

15+
import {enableHalt} from 'shared/ReactFeatureFlags';
16+
1517
import {
1618
createRequest,
1719
startWork,
1820
startFlowing,
1921
stopFlowing,
2022
abort,
23+
halt,
2124
} from 'react-server/src/ReactFlightServer';
2225

2326
import {
@@ -146,10 +149,20 @@ function prerender(
146149
if (options && options.signal) {
147150
const signal = options.signal;
148151
if (signal.aborted) {
149-
abort(request, (signal: any).reason);
152+
const reason = (signal: any).reason;
153+
if (enableHalt) {
154+
halt(request, reason);
155+
} else {
156+
abort(request, reason);
157+
}
150158
} else {
151159
const listener = () => {
152-
abort(request, (signal: any).reason);
160+
const reason = (signal: any).reason;
161+
if (enableHalt) {
162+
halt(request, reason);
163+
} else {
164+
abort(request, reason);
165+
}
153166
signal.removeEventListener('abort', listener);
154167
};
155168
signal.addEventListener('abort', listener);

packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ import type {Thenable} from 'shared/ReactTypes';
2020

2121
import {Readable} from 'stream';
2222

23+
import {enableHalt} from 'shared/ReactFeatureFlags';
24+
2325
import {
2426
createRequest,
2527
startWork,
2628
startFlowing,
2729
stopFlowing,
2830
abort,
31+
halt,
2932
} from 'react-server/src/ReactFlightServer';
3033

3134
import {
@@ -189,10 +192,20 @@ function prerenderToNodeStream(
189192
if (options && options.signal) {
190193
const signal = options.signal;
191194
if (signal.aborted) {
192-
abort(request, (signal: any).reason);
195+
const reason = (signal: any).reason;
196+
if (enableHalt) {
197+
halt(request, reason);
198+
} else {
199+
abort(request, reason);
200+
}
193201
} else {
194202
const listener = () => {
195-
abort(request, (signal: any).reason);
203+
const reason = (signal: any).reason;
204+
if (enableHalt) {
205+
halt(request, reason);
206+
} else {
207+
abort(request, reason);
208+
}
196209
signal.removeEventListener('abort', listener);
197210
};
198211
signal.addEventListener('abort', listener);

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2722,4 +2722,83 @@ describe('ReactFlightDOM', () => {
27222722
await readInto(container, fizzReadable);
27232723
expect(getMeaningfulChildren(container)).toEqual(<div>hello world</div>);
27242724
});
2725+
2726+
// @gate experimental
2727+
it('serializes unfinished tasks with infinite promises when aborting a prerender without a reason', async () => {
2728+
let resolveGreeting;
2729+
const greetingPromise = new Promise(resolve => {
2730+
resolveGreeting = resolve;
2731+
});
2732+
2733+
function App() {
2734+
return (
2735+
<div>
2736+
<Suspense fallback="loading...">
2737+
<Greeting />
2738+
</Suspense>
2739+
</div>
2740+
);
2741+
}
2742+
2743+
async function Greeting() {
2744+
await greetingPromise;
2745+
return 'hello world';
2746+
}
2747+
2748+
const controller = new AbortController();
2749+
const {pendingResult} = await serverAct(async () => {
2750+
// destructure trick to avoid the act scope from awaiting the returned value
2751+
return {
2752+
pendingResult: ReactServerDOMStaticServer.prerenderToNodeStream(
2753+
<App />,
2754+
webpackMap,
2755+
{
2756+
signal: controller.signal,
2757+
},
2758+
),
2759+
};
2760+
});
2761+
2762+
controller.abort();
2763+
resolveGreeting();
2764+
const {prelude} = await pendingResult;
2765+
2766+
const preludeWeb = Readable.toWeb(prelude);
2767+
const response = ReactServerDOMClient.createFromReadableStream(preludeWeb);
2768+
2769+
const {writable: fizzWritable, readable: fizzReadable} = getTestStream();
2770+
2771+
function ClientApp() {
2772+
return use(response);
2773+
}
2774+
2775+
const shellErrors = [];
2776+
let abortFizz;
2777+
await serverAct(async () => {
2778+
const {pipe, abort} = ReactDOMFizzServer.renderToPipeableStream(
2779+
React.createElement(ClientApp),
2780+
{
2781+
onShellError(error) {
2782+
shellErrors.push(error.message);
2783+
},
2784+
},
2785+
);
2786+
pipe(fizzWritable);
2787+
abortFizz = abort;
2788+
});
2789+
2790+
await serverAct(() => {
2791+
try {
2792+
React.unstable_postpone('abort reason');
2793+
} catch (reason) {
2794+
abortFizz(reason);
2795+
}
2796+
});
2797+
2798+
expect(shellErrors).toEqual([]);
2799+
2800+
const container = document.createElement('div');
2801+
await readInto(container, fizzReadable);
2802+
expect(getMeaningfulChildren(container)).toEqual(<div>loading...</div>);
2803+
});
27252804
});

packages/react-server-dom-webpack/src/server/ReactFlightDOMServerBrowser.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import type {Thenable} from 'shared/ReactTypes';
1212
import type {ClientManifest} from './ReactFlightServerConfigWebpackBundler';
1313
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
1414

15+
import {enableHalt} from 'shared/ReactFeatureFlags';
16+
1517
import {
1618
createRequest,
1719
startWork,
1820
startFlowing,
1921
stopFlowing,
2022
abort,
23+
halt,
2124
} from 'react-server/src/ReactFlightServer';
2225

2326
import {
@@ -146,10 +149,20 @@ function prerender(
146149
if (options && options.signal) {
147150
const signal = options.signal;
148151
if (signal.aborted) {
149-
abort(request, (signal: any).reason);
152+
const reason = (signal: any).reason;
153+
if (enableHalt) {
154+
halt(request, reason);
155+
} else {
156+
abort(request, reason);
157+
}
150158
} else {
151159
const listener = () => {
152-
abort(request, (signal: any).reason);
160+
const reason = (signal: any).reason;
161+
if (enableHalt) {
162+
halt(request, reason);
163+
} else {
164+
abort(request, reason);
165+
}
153166
signal.removeEventListener('abort', listener);
154167
};
155168
signal.addEventListener('abort', listener);

packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import type {Thenable} from 'shared/ReactTypes';
1212
import type {ClientManifest} from './ReactFlightServerConfigWebpackBundler';
1313
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
1414

15+
import {enableHalt} from 'shared/ReactFeatureFlags';
16+
1517
import {
1618
createRequest,
1719
startWork,
1820
startFlowing,
1921
stopFlowing,
2022
abort,
23+
halt,
2124
} from 'react-server/src/ReactFlightServer';
2225

2326
import {
@@ -146,10 +149,20 @@ function prerender(
146149
if (options && options.signal) {
147150
const signal = options.signal;
148151
if (signal.aborted) {
149-
abort(request, (signal: any).reason);
152+
const reason = (signal: any).reason;
153+
if (enableHalt) {
154+
halt(request, reason);
155+
} else {
156+
abort(request, reason);
157+
}
150158
} else {
151159
const listener = () => {
152-
abort(request, (signal: any).reason);
160+
const reason = (signal: any).reason;
161+
if (enableHalt) {
162+
halt(request, reason);
163+
} else {
164+
abort(request, reason);
165+
}
153166
signal.removeEventListener('abort', listener);
154167
};
155168
signal.addEventListener('abort', listener);

packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ import type {Thenable} from 'shared/ReactTypes';
2020

2121
import {Readable} from 'stream';
2222

23+
import {enableHalt} from 'shared/ReactFeatureFlags';
24+
2325
import {
2426
createRequest,
2527
startWork,
2628
startFlowing,
2729
stopFlowing,
2830
abort,
31+
halt,
2932
} from 'react-server/src/ReactFlightServer';
3033

3134
import {
@@ -189,10 +192,20 @@ function prerenderToNodeStream(
189192
if (options && options.signal) {
190193
const signal = options.signal;
191194
if (signal.aborted) {
192-
abort(request, (signal: any).reason);
195+
const reason = (signal: any).reason;
196+
if (enableHalt) {
197+
halt(request, reason);
198+
} else {
199+
abort(request, reason);
200+
}
193201
} else {
194202
const listener = () => {
195-
abort(request, (signal: any).reason);
203+
const reason = (signal: any).reason;
204+
if (enableHalt) {
205+
halt(request, reason);
206+
} else {
207+
abort(request, reason);
208+
}
196209
signal.removeEventListener('abort', listener);
197210
};
198211
signal.addEventListener('abort', listener);

0 commit comments

Comments
 (0)