Skip to content

Commit

Permalink
[Fizz][Static] use halt rather than error when aborting a Fizz preren…
Browse files Browse the repository at this point in the history
…der when enableHalt is on

enableHalt is a new feature flag to augment the abort behavior of prerenders. Initially landed for Flight this commit implements halting for Fizz. Halting is analagous to postponing in Fizz however it can only be done at the render level rather than marking individual trees as postponed from within.
  • Loading branch information
gnoff committed Aug 17, 2024
1 parent 00f030d commit a245cd3
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 7 deletions.
6 changes: 5 additions & 1 deletion packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7729,7 +7729,11 @@ describe('ReactDOMFizzServer', () => {

expect(prerendered.postponed).toBe(null);
expect(errors).toEqual([]);
expect(postpones).toEqual(['manufactured', 'manufactured']);
if (gate(flags => flags.enableHalt)) {
expect(postpones).toEqual([]);
} else {
expect(postpones).toEqual(['manufactured', 'manufactured']);
}

await act(() => {
prerendered.prelude.pipe(writable);
Expand Down
46 changes: 40 additions & 6 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ import {
enableSuspenseAvoidThisFallbackFizz,
enableCache,
enablePostpone,
enableHalt,
enableRenderableContext,
enableRefAsProp,
disableDefaultPropsExceptForClasses,
Expand Down Expand Up @@ -3789,14 +3790,23 @@ function abortTask(task: Task, request: Request, error: mixed): void {
if (replay === null) {
// We didn't complete the root so we have nothing to show. We can close
// the request;
const trackedPostpones = request.trackedPostpones;
if (enableHalt) {
if (trackedPostpones !== null && segment !== null) {
// We are prerendering. We don't want to fatal when the shell postpones
// or halts we just need to mark it as postponed.
trackPostpone(request, trackedPostpones, task, segment);
finishedTask(request, null, segment);
return;
}
}
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
const trackedPostpones = request.trackedPostpones;

if (trackedPostpones !== null && segment !== null) {
// We are prerendering. We don't want to fatal when the shell postpones
Expand All @@ -3821,6 +3831,8 @@ function abortTask(task: Task, request: Request, error: mixed): void {
// If the shell aborts during a replay, that's not a fatal error. Instead
// we should be able to recover by client rendering all the root boundaries in
// the ReplaySet.
// We don't have a branch for halting here because aborting during a render/resume
// will never be treated like a halt
replay.pendingTasks--;
if (replay.pendingTasks === 0 && replay.nodes.length > 0) {
let errorDigest;
Expand Down Expand Up @@ -3862,7 +3874,13 @@ function abortTask(task: Task, request: Request, error: mixed): void {
// boundary the message is referring to
const errorInfo = getThrownInfo(task.componentStack);
let errorDigest;
if (
if (enableHalt && request.trackedPostpones !== null) {
// We are halting a prerender. We need to mark the boundary as postponed
// but we do not log anything. It should be noted that with halt and postpone
// enabled halt semantics supercede and so if you abort with a postpone
// during prerender it will not show up in onPostpone
errorDigest = 'POSTPONE';
} else if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
Expand Down Expand Up @@ -4133,6 +4151,7 @@ function retryRenderTask(
? request.fatalError
: thrownValue;

const trackedPostpones = request.trackedPostpones;
if (typeof x === 'object' && x !== null) {
// $FlowFixMe[method-unbinding]
if (typeof x.then === 'function') {
Expand All @@ -4145,14 +4164,15 @@ function retryRenderTask(
return;
} else if (
enablePostpone &&
request.trackedPostpones !== null &&
trackedPostpones !== null &&
x.$$typeof === REACT_POSTPONE_TYPE
) {
// We don't need to check for halting here because the thrown value for halts
// is a symbol type so we won't ever enter this branch

// If we're tracking postpones, we mark this segment as postponed and finish
// the task without filling it in. If we're not tracking, we treat it more like
// an error.
const trackedPostpones = request.trackedPostpones;
task.abortSet.delete(task);
const postponeInstance: Postpone = (x: any);

const postponeInfo = getThrownInfo(task.componentStack);
Expand All @@ -4162,10 +4182,17 @@ function retryRenderTask(
postponeInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
task.abortSet.delete(task);
trackPostpone(request, trackedPostpones, task, segment);
finishedTask(request, task.blockedBoundary, segment);
return;
}
} else if (enableHalt && x === haltSymbol && trackedPostpones !== null) {
// We're aborting a prerender with halt enabled
task.abortSet.delete(task);
trackPostpone(request, trackedPostpones, task, segment);
finishedTask(request, task.blockedBoundary, segment);
return;
}

const errorInfo = getThrownInfo(task.componentStack);
Expand Down Expand Up @@ -4905,6 +4932,8 @@ export function stopFlowing(request: Request): void {
request.destination = null;
}

const haltSymbol = Symbol('halt');

// This is called to early terminate a request. It puts all pending boundaries in client rendered state.
export function abort(request: Request, reason: mixed): void {
if (request.status === OPEN) {
Expand All @@ -4923,7 +4952,12 @@ export function abort(request: Request, reason: mixed): void {
: reason;
// This error isn't necessarily fatal in this case but we need to stash it
// so we can use it to abort any pending work
request.fatalError = error;
if (enableHalt && request.trackedPostpones !== null) {
// When enabled, during prerenders, aborts are treated like halts which postpone without logging
request.fatalError = haltSymbol;
} else {
request.fatalError = error;
}
abortableTasks.forEach(task => abortTask(task, request, error));
abortableTasks.clear();
}
Expand Down

0 comments on commit a245cd3

Please sign in to comment.