Skip to content

Commit 046b550

Browse files
committed
Push a stalled await from debug info to the ownerStack/debugTask
1 parent 19defd1 commit 046b550

File tree

2 files changed

+94
-13
lines changed

2 files changed

+94
-13
lines changed

packages/react-server/src/ReactFizzComponentStack.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99

10-
import type {ReactComponentInfo} from 'shared/ReactTypes';
10+
import type {ReactComponentInfo, ReactAsyncInfo} from 'shared/ReactTypes';
1111
import type {LazyComponent} from 'react/src/ReactLazy';
1212

1313
import {
@@ -37,7 +37,8 @@ export type ComponentStackNode = {
3737
| string
3838
| Function
3939
| LazyComponent<any, any>
40-
| ReactComponentInfo,
40+
| ReactComponentInfo
41+
| ReactAsyncInfo,
4142
owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
4243
stack?: null | string | Error, // DEV only
4344
};

packages/react-server/src/ReactFizzServer.js

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
ReactFormState,
2222
ReactComponentInfo,
2323
ReactDebugInfo,
24+
ReactAsyncInfo,
2425
ViewTransitionProps,
2526
ActivityProps,
2627
SuspenseProps,
@@ -181,6 +182,7 @@ import {
181182
enableAsyncIterableChildren,
182183
enableViewTransition,
183184
enableFizzBlockingRender,
185+
enableAsyncDebugInfo,
184186
} from 'shared/ReactFeatureFlags';
185187

186188
import assign from 'shared/assign';
@@ -985,6 +987,45 @@ function getStackFromNode(stackNode: ComponentStackNode): string {
985987
return getStackByComponentStackNode(stackNode);
986988
}
987989

990+
function pushHaltedAwaitOnComponentStack(
991+
task: Task,
992+
debugInfo: void | null | ReactDebugInfo,
993+
): void {
994+
if (!__DEV__) {
995+
// eslint-disable-next-line react-internal/prod-error-codes
996+
throw new Error(
997+
'pushHaltedAwaitOnComponentStack should never be called in production. This is a bug in React.',
998+
);
999+
}
1000+
if (debugInfo != null) {
1001+
for (let i = debugInfo.length - 1; i >= 0; i--) {
1002+
const info = debugInfo[i];
1003+
if (typeof info.name === 'string') {
1004+
// This is a Server Component. Any awaits in previous Server Components already resolved.
1005+
break;
1006+
}
1007+
if (typeof info.time === 'number') {
1008+
// This had an end time. Any awaits before this must have already resolved.
1009+
break;
1010+
}
1011+
if (info.awaited != null) {
1012+
const asyncInfo: ReactAsyncInfo = (info: any);
1013+
const bestStack =
1014+
asyncInfo.debugStack == null ? asyncInfo.awaited : asyncInfo;
1015+
if (bestStack.debugStack !== undefined) {
1016+
task.componentStack = {
1017+
parent: task.componentStack,
1018+
type: asyncInfo,
1019+
owner: bestStack.owner,
1020+
stack: bestStack.debugStack,
1021+
};
1022+
task.debugTask = (bestStack.debugTask: any);
1023+
}
1024+
}
1025+
}
1026+
}
1027+
}
1028+
9881029
function pushServerComponentStack(
9891030
task: Task,
9901031
debugInfo: void | null | ReactDebugInfo,
@@ -4612,6 +4653,20 @@ function abortTask(task: Task, request: Request, error: mixed): void {
46124653
}
46134654

46144655
const errorInfo = getThrownInfo(task.componentStack);
4656+
if (__DEV__ && enableAsyncDebugInfo) {
4657+
// If the task is not rendering, then this is an async abort. Conceptually it's as if
4658+
// the abort happened inside the async gap. The abort reason's stack frame won't have that
4659+
// on the stack so instead we use the owner stack and debug task of any halted async debug info.
4660+
const node: any = task.node;
4661+
if (node !== null && typeof node === 'object') {
4662+
// Push a fake component stack frame that represents the await.
4663+
pushHaltedAwaitOnComponentStack(task, node._debugInfo);
4664+
if (task.thenableState !== null) {
4665+
// TODO: If we were stalled inside use() of a Client Component then we should
4666+
// rerender to get the stack trace from the use() call.
4667+
}
4668+
}
4669+
}
46154670

46164671
if (boundary === null) {
46174672
if (request.status !== CLOSING && request.status !== CLOSED) {
@@ -4631,16 +4686,21 @@ function abortTask(task: Task, request: Request, error: mixed): void {
46314686
if (trackedPostpones !== null && segment !== null) {
46324687
// We are prerendering. We don't want to fatal when the shell postpones
46334688
// we just need to mark it as postponed.
4634-
logPostpone(request, postponeInstance.message, errorInfo, null);
4689+
logPostpone(
4690+
request,
4691+
postponeInstance.message,
4692+
errorInfo,
4693+
task.debugTask,
4694+
);
46354695
trackPostpone(request, trackedPostpones, task, segment);
46364696
finishedTask(request, null, task.row, segment);
46374697
} else {
46384698
const fatal = new Error(
46394699
'The render was aborted with postpone when the shell is incomplete. Reason: ' +
46404700
postponeInstance.message,
46414701
);
4642-
logRecoverableError(request, fatal, errorInfo, null);
4643-
fatalError(request, fatal, errorInfo, null);
4702+
logRecoverableError(request, fatal, errorInfo, task.debugTask);
4703+
fatalError(request, fatal, errorInfo, task.debugTask);
46444704
}
46454705
} else if (
46464706
enableHalt &&
@@ -4650,12 +4710,12 @@ function abortTask(task: Task, request: Request, error: mixed): void {
46504710
const trackedPostpones = request.trackedPostpones;
46514711
// We are aborting a prerender and must treat the shell as halted
46524712
// We log the error but we still resolve the prerender
4653-
logRecoverableError(request, error, errorInfo, null);
4713+
logRecoverableError(request, error, errorInfo, task.debugTask);
46544714
trackPostpone(request, trackedPostpones, task, segment);
46554715
finishedTask(request, null, task.row, segment);
46564716
} else {
4657-
logRecoverableError(request, error, errorInfo, null);
4658-
fatalError(request, error, errorInfo, null);
4717+
logRecoverableError(request, error, errorInfo, task.debugTask);
4718+
fatalError(request, error, errorInfo, task.debugTask);
46594719
}
46604720
return;
46614721
} else {
@@ -4672,7 +4732,12 @@ function abortTask(task: Task, request: Request, error: mixed): void {
46724732
error.$$typeof === REACT_POSTPONE_TYPE
46734733
) {
46744734
const postponeInstance: Postpone = (error: any);
4675-
logPostpone(request, postponeInstance.message, errorInfo, null);
4735+
logPostpone(
4736+
request,
4737+
postponeInstance.message,
4738+
errorInfo,
4739+
task.debugTask,
4740+
);
46764741
// TODO: Figure out a better signal than a magic digest value.
46774742
errorDigest = 'POSTPONE';
46784743
} else {
@@ -4710,11 +4775,16 @@ function abortTask(task: Task, request: Request, error: mixed): void {
47104775
error.$$typeof === REACT_POSTPONE_TYPE
47114776
) {
47124777
const postponeInstance: Postpone = (error: any);
4713-
logPostpone(request, postponeInstance.message, errorInfo, null);
4778+
logPostpone(
4779+
request,
4780+
postponeInstance.message,
4781+
errorInfo,
4782+
task.debugTask,
4783+
);
47144784
} else {
47154785
// We are aborting a prerender and must halt this boundary.
47164786
// We treat this like other postpones during prerendering
4717-
logRecoverableError(request, error, errorInfo, null);
4787+
logRecoverableError(request, error, errorInfo, task.debugTask);
47184788
}
47194789
trackPostpone(request, trackedPostpones, task, segment);
47204790
// If this boundary was still pending then we haven't already cancelled its fallbacks.
@@ -4737,7 +4807,12 @@ function abortTask(task: Task, request: Request, error: mixed): void {
47374807
error.$$typeof === REACT_POSTPONE_TYPE
47384808
) {
47394809
const postponeInstance: Postpone = (error: any);
4740-
logPostpone(request, postponeInstance.message, errorInfo, null);
4810+
logPostpone(
4811+
request,
4812+
postponeInstance.message,
4813+
errorInfo,
4814+
task.debugTask,
4815+
);
47414816
if (request.trackedPostpones !== null && segment !== null) {
47424817
trackPostpone(request, request.trackedPostpones, task, segment);
47434818
finishedTask(request, task.blockedBoundary, task.row, segment);
@@ -4753,7 +4828,12 @@ function abortTask(task: Task, request: Request, error: mixed): void {
47534828
// TODO: Figure out a better signal than a magic digest value.
47544829
errorDigest = 'POSTPONE';
47554830
} else {
4756-
errorDigest = logRecoverableError(request, error, errorInfo, null);
4831+
errorDigest = logRecoverableError(
4832+
request,
4833+
error,
4834+
errorInfo,
4835+
task.debugTask,
4836+
);
47574837
}
47584838
boundary.status = CLIENT_RENDERED;
47594839
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, true);

0 commit comments

Comments
 (0)