@samitouri / QOS-React / commits / 9938e248fe

[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();