Skip to content

Commit a960b92

Browse files
authored
[Flight] model halting as never delivered chunks (#30740)
stacked on: #30731 We've refined the model of halting a prerender. Now when you abort during a prerender we simply omit the rows that would complete the flight render. This is analagous to prerendering in Fizz where you must resume the prerender to actually result in errors propagating in the postponed holes. We don't have a resume yet for flight and it's not entirely clear how that will work however the key insight here is that deciding whether the never resolving rows are an error or not should really be done on the consuming side rather than in the producer. This PR also reintroduces the logs for the abort error/postpone when prerendering which will give you some indication that something wasn't finished when the prerender was aborted.
1 parent 0fa9476 commit a960b92

File tree

13 files changed

+253
-283
lines changed

13 files changed

+253
-283
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import {
4646
enableRefAsProp,
4747
enableFlightReadableStream,
4848
enableOwnerStacks,
49-
enableHalt,
5049
} from 'shared/ReactFeatureFlags';
5150

5251
import {
@@ -1997,20 +1996,6 @@ function resolvePostponeDev(
19971996
}
19981997
}
19991998

2000-
function resolveBlocked(response: Response, id: number): void {
2001-
const chunks = response._chunks;
2002-
const chunk = chunks.get(id);
2003-
if (!chunk) {
2004-
chunks.set(id, createBlockedChunk(response));
2005-
} else if (chunk.status === PENDING) {
2006-
// This chunk as contructed via other means but it is actually a blocked chunk
2007-
// so we update it here. We check the status because it might have been aborted
2008-
// before we attempted to resolve it.
2009-
const blockedChunk: BlockedChunk<mixed> = (chunk: any);
2010-
blockedChunk.status = BLOCKED;
2011-
}
2012-
}
2013-
20141999
function resolveHint<Code: HintCode>(
20152000
response: Response,
20162001
code: Code,
@@ -2637,13 +2622,6 @@ function processFullStringRow(
26372622
}
26382623
}
26392624
// Fallthrough
2640-
case 35 /* "#" */: {
2641-
if (enableHalt) {
2642-
resolveBlocked(response, id);
2643-
return;
2644-
}
2645-
}
2646-
// Fallthrough
26472625
default: /* """ "{" "[" "t" "f" "n" "0" - "9" */ {
26482626
// We assume anything else is JSON.
26492627
resolveModel(response, id, row);

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

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

2121
import {Readable} from 'stream';
2222

23-
import {enableHalt} from 'shared/ReactFeatureFlags';
24-
2523
import {
2624
createRequest,
25+
createPrerenderRequest,
2726
startWork,
2827
startFlowing,
2928
stopFlowing,
3029
abort,
31-
halt,
3230
} from 'react-server/src/ReactFlightServer';
3331

3432
import {
@@ -175,35 +173,27 @@ function prerenderToNodeStream(
175173
resolve({prelude: readable});
176174
}
177175

178-
const request = createRequest(
176+
const request = createPrerenderRequest(
179177
model,
180178
moduleBasePath,
179+
onAllReady,
180+
onFatalError,
181181
options ? options.onError : undefined,
182182
options ? options.identifierPrefix : undefined,
183183
options ? options.onPostpone : undefined,
184184
options ? options.temporaryReferences : undefined,
185185
__DEV__ && options ? options.environmentName : undefined,
186186
__DEV__ && options ? options.filterStackFrame : undefined,
187-
onAllReady,
188-
onFatalError,
189187
);
190188
if (options && options.signal) {
191189
const signal = options.signal;
192190
if (signal.aborted) {
193191
const reason = (signal: any).reason;
194-
if (enableHalt) {
195-
halt(request, reason);
196-
} else {
197-
abort(request, reason);
198-
}
192+
abort(request, reason);
199193
} else {
200194
const listener = () => {
201195
const reason = (signal: any).reason;
202-
if (enableHalt) {
203-
halt(request, reason);
204-
} else {
205-
abort(request, reason);
206-
}
196+
abort(request, reason);
207197
signal.removeEventListener('abort', listener);
208198
};
209199
signal.addEventListener('abort', listener);

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

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ 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-
1715
import {
1816
createRequest,
17+
createPrerenderRequest,
1918
startWork,
2019
startFlowing,
2120
stopFlowing,
2221
abort,
23-
halt,
2422
} from 'react-server/src/ReactFlightServer';
2523

2624
import {
@@ -134,35 +132,27 @@ function prerender(
134132
);
135133
resolve({prelude: stream});
136134
}
137-
const request = createRequest(
135+
const request = createPrerenderRequest(
138136
model,
139137
turbopackMap,
138+
onAllReady,
139+
onFatalError,
140140
options ? options.onError : undefined,
141141
options ? options.identifierPrefix : undefined,
142142
options ? options.onPostpone : undefined,
143143
options ? options.temporaryReferences : undefined,
144144
__DEV__ && options ? options.environmentName : undefined,
145145
__DEV__ && options ? options.filterStackFrame : undefined,
146-
onAllReady,
147-
onFatalError,
148146
);
149147
if (options && options.signal) {
150148
const signal = options.signal;
151149
if (signal.aborted) {
152150
const reason = (signal: any).reason;
153-
if (enableHalt) {
154-
halt(request, reason);
155-
} else {
156-
abort(request, reason);
157-
}
151+
abort(request, reason);
158152
} else {
159153
const listener = () => {
160154
const reason = (signal: any).reason;
161-
if (enableHalt) {
162-
halt(request, reason);
163-
} else {
164-
abort(request, reason);
165-
}
155+
abort(request, reason);
166156
signal.removeEventListener('abort', listener);
167157
};
168158
signal.addEventListener('abort', listener);

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

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ 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-
1715
import {
1816
createRequest,
17+
createPrerenderRequest,
1918
startWork,
2019
startFlowing,
2120
stopFlowing,
2221
abort,
23-
halt,
2422
} from 'react-server/src/ReactFlightServer';
2523

2624
import {
@@ -134,35 +132,27 @@ function prerender(
134132
);
135133
resolve({prelude: stream});
136134
}
137-
const request = createRequest(
135+
const request = createPrerenderRequest(
138136
model,
139137
turbopackMap,
138+
onAllReady,
139+
onFatalError,
140140
options ? options.onError : undefined,
141141
options ? options.identifierPrefix : undefined,
142142
options ? options.onPostpone : undefined,
143143
options ? options.temporaryReferences : undefined,
144144
__DEV__ && options ? options.environmentName : undefined,
145145
__DEV__ && options ? options.filterStackFrame : undefined,
146-
onAllReady,
147-
onFatalError,
148146
);
149147
if (options && options.signal) {
150148
const signal = options.signal;
151149
if (signal.aborted) {
152150
const reason = (signal: any).reason;
153-
if (enableHalt) {
154-
halt(request, reason);
155-
} else {
156-
abort(request, reason);
157-
}
151+
abort(request, reason);
158152
} else {
159153
const listener = () => {
160154
const reason = (signal: any).reason;
161-
if (enableHalt) {
162-
halt(request, reason);
163-
} else {
164-
abort(request, reason);
165-
}
155+
abort(request, reason);
166156
signal.removeEventListener('abort', listener);
167157
};
168158
signal.addEventListener('abort', listener);

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

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

2121
import {Readable} from 'stream';
2222

23-
import {enableHalt} from 'shared/ReactFeatureFlags';
24-
2523
import {
2624
createRequest,
25+
createPrerenderRequest,
2726
startWork,
2827
startFlowing,
2928
stopFlowing,
3029
abort,
31-
halt,
3230
} from 'react-server/src/ReactFlightServer';
3331

3432
import {
@@ -177,35 +175,27 @@ function prerenderToNodeStream(
177175
resolve({prelude: readable});
178176
}
179177

180-
const request = createRequest(
178+
const request = createPrerenderRequest(
181179
model,
182180
turbopackMap,
181+
onAllReady,
182+
onFatalError,
183183
options ? options.onError : undefined,
184184
options ? options.identifierPrefix : undefined,
185185
options ? options.onPostpone : undefined,
186186
options ? options.temporaryReferences : undefined,
187187
__DEV__ && options ? options.environmentName : undefined,
188188
__DEV__ && options ? options.filterStackFrame : undefined,
189-
onAllReady,
190-
onFatalError,
191189
);
192190
if (options && options.signal) {
193191
const signal = options.signal;
194192
if (signal.aborted) {
195193
const reason = (signal: any).reason;
196-
if (enableHalt) {
197-
halt(request, reason);
198-
} else {
199-
abort(request, reason);
200-
}
194+
abort(request, reason);
201195
} else {
202196
const listener = () => {
203197
const reason = (signal: any).reason;
204-
if (enableHalt) {
205-
halt(request, reason);
206-
} else {
207-
abort(request, reason);
208-
}
198+
abort(request, reason);
209199
signal.removeEventListener('abort', listener);
210200
};
211201
signal.addEventListener('abort', listener);

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2724,7 +2724,7 @@ describe('ReactFlightDOM', () => {
27242724
});
27252725

27262726
// @gate enableHalt
2727-
it('serializes unfinished tasks with infinite promises when aborting a prerender', async () => {
2727+
it('does not propagate abort reasons errors when aborting a prerender', async () => {
27282728
let resolveGreeting;
27292729
const greetingPromise = new Promise(resolve => {
27302730
resolveGreeting = resolve;
@@ -2746,6 +2746,7 @@ describe('ReactFlightDOM', () => {
27462746
}
27472747

27482748
const controller = new AbortController();
2749+
const errors = [];
27492750
const {pendingResult} = await serverAct(async () => {
27502751
// destructure trick to avoid the act scope from awaiting the returned value
27512752
return {
@@ -2754,15 +2755,20 @@ describe('ReactFlightDOM', () => {
27542755
webpackMap,
27552756
{
27562757
signal: controller.signal,
2758+
onError(err) {
2759+
errors.push(err);
2760+
},
27572761
},
27582762
),
27592763
};
27602764
});
27612765

2762-
controller.abort();
2766+
controller.abort('boom');
27632767
resolveGreeting();
27642768
const {prelude} = await pendingResult;
27652769

2770+
expect(errors).toEqual(['boom']);
2771+
27662772
const preludeWeb = Readable.toWeb(prelude);
27672773
const response = ReactServerDOMClient.createFromReadableStream(preludeWeb);
27682774

@@ -2772,7 +2778,7 @@ describe('ReactFlightDOM', () => {
27722778
return use(response);
27732779
}
27742780

2775-
const errors = [];
2781+
errors.length = 0;
27762782
let abortFizz;
27772783
await serverAct(async () => {
27782784
const {pipe, abort} = ReactDOMFizzServer.renderToPipeableStream(
@@ -2788,10 +2794,10 @@ describe('ReactFlightDOM', () => {
27882794
});
27892795

27902796
await serverAct(() => {
2791-
abortFizz('boom');
2797+
abortFizz('bam');
27922798
});
27932799

2794-
expect(errors).toEqual(['boom']);
2800+
expect(errors).toEqual(['bam']);
27952801

27962802
const container = document.createElement('div');
27972803
await readInto(container, fizzReadable);
@@ -2861,7 +2867,7 @@ describe('ReactFlightDOM', () => {
28612867
it('will halt unfinished chunks inside Suspense when aborting a prerender', async () => {
28622868
const controller = new AbortController();
28632869
function ComponentThatAborts() {
2864-
controller.abort();
2870+
controller.abort('boom');
28652871
return null;
28662872
}
28672873

@@ -2912,11 +2918,8 @@ describe('ReactFlightDOM', () => {
29122918
};
29132919
});
29142920

2915-
controller.abort();
2916-
29172921
const {prelude} = await pendingResult;
2918-
expect(errors).toEqual([]);
2919-
2922+
expect(errors).toEqual(['boom']);
29202923
const response = ReactServerDOMClient.createFromReadableStream(
29212924
Readable.toWeb(prelude),
29222925
);
@@ -2926,6 +2929,7 @@ describe('ReactFlightDOM', () => {
29262929
function ClientApp() {
29272930
return use(response);
29282931
}
2932+
errors.length = 0;
29292933
let abortFizz;
29302934
await serverAct(async () => {
29312935
const {pipe, abort} = ReactDOMFizzServer.renderToPipeableStream(

0 commit comments

Comments
 (0)