@@ -21,6 +21,7 @@ import type {
21
21
ReactFormState ,
22
22
ReactComponentInfo ,
23
23
ReactDebugInfo ,
24
+ ReactAsyncInfo ,
24
25
ViewTransitionProps ,
25
26
ActivityProps ,
26
27
SuspenseProps ,
@@ -181,6 +182,7 @@ import {
181
182
enableAsyncIterableChildren ,
182
183
enableViewTransition ,
183
184
enableFizzBlockingRender ,
185
+ enableAsyncDebugInfo ,
184
186
} from 'shared/ReactFeatureFlags' ;
185
187
186
188
import assign from 'shared/assign' ;
@@ -985,6 +987,45 @@ function getStackFromNode(stackNode: ComponentStackNode): string {
985
987
return getStackByComponentStackNode ( stackNode ) ;
986
988
}
987
989
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
+
988
1029
function pushServerComponentStack (
989
1030
task : Task ,
990
1031
debugInfo : void | null | ReactDebugInfo ,
@@ -4612,6 +4653,20 @@ function abortTask(task: Task, request: Request, error: mixed): void {
4612
4653
}
4613
4654
4614
4655
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
+ }
4615
4670
4616
4671
if ( boundary === null ) {
4617
4672
if ( request . status !== CLOSING && request . status !== CLOSED ) {
@@ -4631,16 +4686,21 @@ function abortTask(task: Task, request: Request, error: mixed): void {
4631
4686
if ( trackedPostpones !== null && segment !== null ) {
4632
4687
// We are prerendering. We don't want to fatal when the shell postpones
4633
4688
// 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
+ ) ;
4635
4695
trackPostpone ( request , trackedPostpones , task , segment ) ;
4636
4696
finishedTask ( request , null , task . row , segment ) ;
4637
4697
} else {
4638
4698
const fatal = new Error (
4639
4699
'The render was aborted with postpone when the shell is incomplete. Reason: ' +
4640
4700
postponeInstance . message ,
4641
4701
) ;
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 ) ;
4644
4704
}
4645
4705
} else if (
4646
4706
enableHalt &&
@@ -4650,12 +4710,12 @@ function abortTask(task: Task, request: Request, error: mixed): void {
4650
4710
const trackedPostpones = request . trackedPostpones ;
4651
4711
// We are aborting a prerender and must treat the shell as halted
4652
4712
// We log the error but we still resolve the prerender
4653
- logRecoverableError ( request , error , errorInfo , null ) ;
4713
+ logRecoverableError ( request , error , errorInfo , task . debugTask ) ;
4654
4714
trackPostpone ( request , trackedPostpones , task , segment ) ;
4655
4715
finishedTask ( request , null , task . row , segment ) ;
4656
4716
} 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 ) ;
4659
4719
}
4660
4720
return ;
4661
4721
} else {
@@ -4672,7 +4732,12 @@ function abortTask(task: Task, request: Request, error: mixed): void {
4672
4732
error . $$typeof === REACT_POSTPONE_TYPE
4673
4733
) {
4674
4734
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
+ ) ;
4676
4741
// TODO: Figure out a better signal than a magic digest value.
4677
4742
errorDigest = 'POSTPONE ';
4678
4743
} else {
@@ -4710,11 +4775,16 @@ function abortTask(task: Task, request: Request, error: mixed): void {
4710
4775
error . $$typeof === REACT_POSTPONE_TYPE
4711
4776
) {
4712
4777
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
+ ) ;
4714
4784
} else {
4715
4785
// We are aborting a prerender and must halt this boundary.
4716
4786
// We treat this like other postpones during prerendering
4717
- logRecoverableError ( request , error , errorInfo , null ) ;
4787
+ logRecoverableError ( request , error , errorInfo , task . debugTask ) ;
4718
4788
}
4719
4789
trackPostpone ( request , trackedPostpones , task , segment ) ;
4720
4790
// 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 {
4737
4807
error . $$typeof === REACT_POSTPONE_TYPE
4738
4808
) {
4739
4809
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
+ ) ;
4741
4816
if ( request . trackedPostpones !== null && segment !== null ) {
4742
4817
trackPostpone ( request , request . trackedPostpones , task , segment ) ;
4743
4818
finishedTask ( request , task . blockedBoundary , task . row , segment ) ;
@@ -4753,7 +4828,12 @@ function abortTask(task: Task, request: Request, error: mixed): void {
4753
4828
// TODO: Figure out a better signal than a magic digest value.
4754
4829
errorDigest = 'POSTPONE ';
4755
4830
} else {
4756
- errorDigest = logRecoverableError ( request , error , errorInfo , null ) ;
4831
+ errorDigest = logRecoverableError (
4832
+ request ,
4833
+ error ,
4834
+ errorInfo ,
4835
+ task . debugTask ,
4836
+ ) ;
4757
4837
}
4758
4838
boundary . status = CLIENT_RENDERED ;
4759
4839
encodeErrorForBoundary ( boundary , errorDigest , error , errorInfo , true ) ;
0 commit comments