@@ -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
186188import 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+
9881029function 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