[Fizz] Don't perform work when closing (#30497)
When a Fizz render is closing but not yet closed it's possible that pinged tasks can spawn more work. The point of the closing state is to allow time to start piping/reading the underlying stream but semantically the render is finished at that point so work should no longer happen.
Josh Story committed
Jul 29, 2024 at 11:09 UTC
9938e248feecc6410738cc905744836d6f78c7f8
2 files changed
+62
-1
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+61
@@ -3994,6 +3994,67 @@ describe('ReactDOMFizzServer', () => {
3994
expect(headers.Link.length).toBe(306);
3995
});
3996
3997
+ it('does not perform any additional work after fatally erroring', async () => {
3998
+ let resolve: () => void;
3999
+ const promise = new Promise(r => {
4000
+ resolve = r;
4001
+ });
4002
+ function AsyncComp() {
4003
+ React.use(promise);
4004
+ return <DidRender>Async</DidRender>;
4005
+ }
4006
+
4007
+ let didRender = false;
4008
+ function DidRender({children}) {
4009
+ didRender = true;
4010
+ return children;
4011
+ }
4012
+
4013
+ function ErrorComp() {
4014
+ throw new Error('boom');
4015
+ }
4016
+
4017
+ function App() {
4018
+ return (
4019
+ <div>
4020
+ <Suspense fallback="loading...">
4021
+ <AsyncComp />
4022
+ </Suspense>
4023
+ <ErrorComp />
4024
+ </div>
4025
+ );
4026
+ }
4027
+
4028
+ let pipe;
4029
+ const errors = [];
4030
+ let didFatal = true;
4031
+ await act(() => {
4032
+ pipe = renderToPipeableStream(<App />, {
4033
+ onError(error) {
4034
+ errors.push(error.message);
4035
+ },
4036
+ onShellError(error) {
4037
+ didFatal = true;
4038
+ },
4039
+ }).pipe;
4040
+ });
4041
+
4042
+ expect(didRender).toBe(false);
4043
+ await act(() => {
4044
+ resolve();
4045
+ });
4046
+ expect(didRender).toBe(false);
4047
+
4048
+ const testWritable = new Stream.Writable();
4049
+ await act(() => pipe(testWritable));
4050
+ expect(didRender).toBe(false);
4051
+ expect(didFatal).toBe(didFatal);
4052
+ expect(errors).toEqual([
4053
+ 'boom',
4054
+ 'The destination stream errored while writing data.',
4055
+ ]);
4056
+ });
4057
+
4058
describe('error escaping', () => {
4059
it('escapes error hash, message, and component stack values in directly flushed errors (html escaping)', async () => {
4060
window.__outlet = {};
packages/react-server/src/ReactFizzServer.js
+1
-1
@@ -4215,7 +4215,7 @@ function retryReplayTask(request: Request, task: ReplayTask): void {
4215
}
4216
4217
export function performWork(request: Request): void {
4218
- if (request.status === CLOSED) {
4218
+ if (request.status === CLOSED || request.status === CLOSING) {
4219
return;
4220
}
4221
const prevContext = getActiveContext();