@samitouri / QOS-React / commits / e0160d50c5

add transition tracing transitions stack (#24321)

Added a transitions stack for to keep track of which transitions are still happening for the current boundary. * On the root, we will get all transitions that have been initiated for the corresponding lanes. * Whenever we encounter a suspended boundary, we will add all transitions on the stack onto the boundary * Whenever we encounter a boundary that just unsuspended, we will add all transitions on the boundary onto the stack A transition will be considered complete when there are no boundaries that have the associated transition

Luna Ruan committed Apr 8, 2022 at 19:48 UTC e0160d50c5a492a925db6ab3f8478e118336c722
8 files changed +162 -88
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+10 -7
@@ -661,7 +661,7 @@ function updateOffscreenComponent(
661 // push the cache pool even though we're going to bail out
662 // because otherwise there'd be a context mismatch
663 if (current !== null) {
664 - pushTransition(workInProgress, null);
664 + pushTransition(workInProgress, null, null);
665 }
666 }
667 pushRenderLanes(workInProgress, renderLanes);
@@ -695,7 +695,7 @@ function updateOffscreenComponent(
695 // push the cache pool even though we're going to bail out
696 // because otherwise there'd be a context mismatch
697 if (current !== null) {
698 - pushTransition(workInProgress, null);
698 + pushTransition(workInProgress, null, null);
699 }
700 }
701
@@ -733,7 +733,9 @@ function updateOffscreenComponent(
733 // using the same cache. Unless the parent changed, since that means
734 // there was a refresh.
735 const prevCachePool = prevState !== null ? prevState.cachePool : null;
736 - pushTransition(workInProgress, prevCachePool);
736 + // TODO: Consider if and how Offscreen pre-rendering should
737 + // be attributed to the transition that spawned it
738 + pushTransition(workInProgress, prevCachePool, null);
739 }
740
741 pushRenderLanes(workInProgress, subtreeRenderLanes);
@@ -751,7 +753,7 @@ function updateOffscreenComponent(
753 // using the same cache. Unless the parent changed, since that means
754 // there was a refresh.
755 const prevCachePool = prevState.cachePool;
754 - pushTransition(workInProgress, prevCachePool);
756 + pushTransition(workInProgress, prevCachePool, null);
757 }
758
759 // Since we're not hidden anymore, reset the state
@@ -767,7 +769,7 @@ function updateOffscreenComponent(
769 // using the same cache. Unless the parent changed, since that means
770 // there was a refresh.
771 if (current !== null) {
770 - pushTransition(workInProgress, null);
772 + pushTransition(workInProgress, null, null);
773 }
774 }
775 }
@@ -1330,10 +1332,10 @@ function updateHostRoot(current, workInProgress, renderLanes) {
1332
1333 const nextState: RootState = workInProgress.memoizedState;
1334 const root: FiberRoot = workInProgress.stateNode;
1335 + pushRootTransition(workInProgress, root, renderLanes);
1336
1337 if (enableCache) {
1338 const nextCache: Cache = nextState.cache;
1336 - pushRootTransition(root);
1339 pushCacheProvider(workInProgress, nextCache);
1340 if (nextCache !== prevState.cache) {
1341 // The root cache refreshed.
@@ -3572,10 +3574,11 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3574 case HostRoot:
3575 pushHostRootContext(workInProgress);
3576 const root: FiberRoot = workInProgress.stateNode;
3577 + pushRootTransition(workInProgress, root, renderLanes);
3578 +
3579 if (enableCache) {
3580 const cache: Cache = current.memoizedState.cache;
3581 pushCacheProvider(workInProgress, cache);
3578 - pushRootTransition(root);
3582 }
3583 if (enableTransitionTracing) {
3584 workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+10 -7
@@ -661,7 +661,7 @@ function updateOffscreenComponent(
661 // push the cache pool even though we're going to bail out
662 // because otherwise there'd be a context mismatch
663 if (current !== null) {
664 - pushTransition(workInProgress, null);
664 + pushTransition(workInProgress, null, null);
665 }
666 }
667 pushRenderLanes(workInProgress, renderLanes);
@@ -695,7 +695,7 @@ function updateOffscreenComponent(
695 // push the cache pool even though we're going to bail out
696 // because otherwise there'd be a context mismatch
697 if (current !== null) {
698 - pushTransition(workInProgress, null);
698 + pushTransition(workInProgress, null, null);
699 }
700 }
701
@@ -733,7 +733,9 @@ function updateOffscreenComponent(
733 // using the same cache. Unless the parent changed, since that means
734 // there was a refresh.
735 const prevCachePool = prevState !== null ? prevState.cachePool : null;
736 - pushTransition(workInProgress, prevCachePool);
736 + // TODO: Consider if and how Offscreen pre-rendering should
737 + // be attributed to the transition that spawned it
738 + pushTransition(workInProgress, prevCachePool, null);
739 }
740
741 pushRenderLanes(workInProgress, subtreeRenderLanes);
@@ -751,7 +753,7 @@ function updateOffscreenComponent(
753 // using the same cache. Unless the parent changed, since that means
754 // there was a refresh.
755 const prevCachePool = prevState.cachePool;
754 - pushTransition(workInProgress, prevCachePool);
756 + pushTransition(workInProgress, prevCachePool, null);
757 }
758
759 // Since we're not hidden anymore, reset the state
@@ -767,7 +769,7 @@ function updateOffscreenComponent(
769 // using the same cache. Unless the parent changed, since that means
770 // there was a refresh.
771 if (current !== null) {
770 - pushTransition(workInProgress, null);
772 + pushTransition(workInProgress, null, null);
773 }
774 }
775 }
@@ -1330,10 +1332,10 @@ function updateHostRoot(current, workInProgress, renderLanes) {
1332
1333 const nextState: RootState = workInProgress.memoizedState;
1334 const root: FiberRoot = workInProgress.stateNode;
1335 + pushRootTransition(workInProgress, root, renderLanes);
1336
1337 if (enableCache) {
1338 const nextCache: Cache = nextState.cache;
1336 - pushRootTransition(root);
1339 pushCacheProvider(workInProgress, nextCache);
1340 if (nextCache !== prevState.cache) {
1341 // The root cache refreshed.
@@ -3572,10 +3574,11 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3574 case HostRoot:
3575 pushHostRootContext(workInProgress);
3576 const root: FiberRoot = workInProgress.stateNode;
3577 + pushRootTransition(workInProgress, root, renderLanes);
3578 +
3579 if (enableCache) {
3580 const cache: Cache = current.memoizedState.cache;
3581 pushCacheProvider(workInProgress, cache);
3578 - pushRootTransition(root);
3582 }
3583 if (enableTransitionTracing) {
3584 workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+3 -5
@@ -875,8 +875,6 @@ function completeWork(
875 }
876
877 if (enableCache) {
878 - popRootTransition(fiberRoot, renderLanes);
879 -
878 let previousCache: Cache | null = null;
879 if (current !== null) {
880 previousCache = current.memoizedState.cache;
@@ -888,6 +886,7 @@ function completeWork(
886 }
887 popCacheProvider(workInProgress, cache);
888 }
889 + popRootTransition(workInProgress, fiberRoot, renderLanes);
890 popHostContainer(workInProgress);
891 popTopLevelLegacyContextObject(workInProgress);
892 resetMutableSourceWorkInProgressVersions();
@@ -1593,11 +1592,10 @@ function completeWork(
1592 // Run passive effects to retain/release the cache.
1593 workInProgress.flags |= Passive;
1594 }
1596 - if (current !== null) {
1597 - popTransition(workInProgress);
1598 - }
1595 }
1596
1597 + popTransition(workInProgress, current);
1598 +
1599 return null;
1600 }
1601 case CacheComponent: {
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+3 -5
@@ -875,8 +875,6 @@ function completeWork(
875 }
876
877 if (enableCache) {
878 - popRootTransition(fiberRoot, renderLanes);
879 -
878 let previousCache: Cache | null = null;
879 if (current !== null) {
880 previousCache = current.memoizedState.cache;
@@ -888,6 +886,7 @@ function completeWork(
886 }
887 popCacheProvider(workInProgress, cache);
888 }
889 + popRootTransition(workInProgress, fiberRoot, renderLanes);
890 popHostContainer(workInProgress);
891 popTopLevelLegacyContextObject(workInProgress);
892 resetMutableSourceWorkInProgressVersions();
@@ -1593,11 +1592,10 @@ function completeWork(
1592 // Run passive effects to retain/release the cache.
1593 workInProgress.flags |= Passive;
1594 }
1596 - if (current !== null) {
1597 - popTransition(workInProgress);
1598 - }
1595 }
1596
1597 + popTransition(workInProgress, current);
1598 +
1599 return null;
1600 }
1601 case CacheComponent: {
packages/react-reconciler/src/ReactFiberTransition.new.js
+62 -15
@@ -10,11 +10,15 @@ import type {FiberRoot} from './ReactInternalTypes';
10 import type {Lanes} from './ReactFiberLane.new';
11 import type {StackCursor} from './ReactFiberStack.new';
12 import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new';
13 +import type {Transition} from './ReactFiberTracingMarkerComponent.new';
14
14 -import {enableCache} from 'shared/ReactFeatureFlags';
15 +import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags';
16 import {isPrimaryRenderer} from './ReactFiberHostConfig';
17 import {createCursor, push, pop} from './ReactFiberStack.new';
17 -import {getWorkInProgressRoot} from './ReactFiberWorkLoop.new';
18 +import {
19 + getWorkInProgressRoot,
20 + getWorkInProgressTransitions,
21 +} from './ReactFiberWorkLoop.new';
22 import {
23 createCache,
24 retainCache,
@@ -25,6 +29,15 @@ import {
29 // used during the previous render by placing it here, on the stack.
30 const resumedCache: StackCursor<Cache | null> = createCursor(null);
31
32 +// During the render/synchronous commit phase, we don't actually process the
33 +// transitions. Therefore, we want to lazily combine transitions. Instead of
34 +// comparing the arrays of transitions when we combine them and storing them
35 +// and filtering out the duplicates, we will instead store the unprocessed transitions
36 +// in an array and actually filter them in the passive phase.
37 +const transitionStack: StackCursor<Array<Transition> | null> = createCursor(
38 + null,
39 +);
40 +
41 function peekCacheFromPool(): Cache | null {
42 if (!enableCache) {
43 return (null: any);
@@ -75,25 +88,31 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache {
88 return freshCache;
89 }
90
78 -export function pushRootTransition(root: FiberRoot) {
79 - if (enableCache) {
80 - return;
91 +export function pushRootTransition(
92 + workInProgress: Fiber,
93 + root: FiberRoot,
94 + renderLanes: Lanes,
95 +) {
96 + if (enableTransitionTracing) {
97 + const rootTransitions = getWorkInProgressTransitions();
98 + push(transitionStack, rootTransitions, workInProgress);
99 }
82 - // Note: This function currently does nothing but I'll leave it here for
83 - // code organization purposes in case that changes.
100 }
101
86 -export function popRootTransition(root: FiberRoot, renderLanes: Lanes) {
87 - if (enableCache) {
88 - return;
102 +export function popRootTransition(
103 + workInProgress: Fiber,
104 + root: FiberRoot,
105 + renderLanes: Lanes,
106 +) {
107 + if (enableTransitionTracing) {
108 + pop(transitionStack, workInProgress);
109 }
90 - // Note: This function currently does nothing but I'll leave it here for
91 - // code organization purposes in case that changes.
110 }
111
112 export function pushTransition(
113 offscreenWorkInProgress: Fiber,
114 prevCachePool: SpawnedCachePool | null,
115 + newTransitions: Array<Transition> | null,
116 ): void {
117 if (enableCache) {
118 if (prevCachePool === null) {
@@ -102,12 +121,40 @@ export function pushTransition(
121 push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
122 }
123 }
124 +
125 + if (enableTransitionTracing) {
126 + if (transitionStack.current === null) {
127 + push(transitionStack, newTransitions, offscreenWorkInProgress);
128 + } else if (newTransitions === null) {
129 + push(transitionStack, transitionStack.current, offscreenWorkInProgress);
130 + } else {
131 + push(
132 + transitionStack,
133 + transitionStack.current.concat(newTransitions),
134 + offscreenWorkInProgress,
135 + );
136 + }
137 + }
138 }
139
107 -export function popTransition(workInProgress: Fiber) {
108 - if (enableCache) {
109 - pop(resumedCache, workInProgress);
140 +export function popTransition(workInProgress: Fiber, current: Fiber | null) {
141 + if (current !== null) {
142 + if (enableCache) {
143 + pop(resumedCache, workInProgress);
144 + }
145 +
146 + if (enableTransitionTracing) {
147 + pop(transitionStack, workInProgress);
148 + }
149 + }
150 +}
151 +
152 +export function getSuspendedTransitions(): Array<Transition> | null {
153 + if (!enableTransitionTracing) {
154 + return null;
155 }
156 +
157 + return transitionStack.current;
158 }
159
160 export function getSuspendedCache(): SpawnedCachePool | null {
packages/react-reconciler/src/ReactFiberTransition.old.js
+62 -15
@@ -10,11 +10,15 @@ import type {FiberRoot} from './ReactInternalTypes';
10 import type {Lanes} from './ReactFiberLane.old';
11 import type {StackCursor} from './ReactFiberStack.old';
12 import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old';
13 +import type {Transition} from './ReactFiberTracingMarkerComponent.old';
14
14 -import {enableCache} from 'shared/ReactFeatureFlags';
15 +import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags';
16 import {isPrimaryRenderer} from './ReactFiberHostConfig';
17 import {createCursor, push, pop} from './ReactFiberStack.old';
17 -import {getWorkInProgressRoot} from './ReactFiberWorkLoop.old';
18 +import {
19 + getWorkInProgressRoot,
20 + getWorkInProgressTransitions,
21 +} from './ReactFiberWorkLoop.old';
22 import {
23 createCache,
24 retainCache,
@@ -25,6 +29,15 @@ import {
29 // used during the previous render by placing it here, on the stack.
30 const resumedCache: StackCursor<Cache | null> = createCursor(null);
31
32 +// During the render/synchronous commit phase, we don't actually process the
33 +// transitions. Therefore, we want to lazily combine transitions. Instead of
34 +// comparing the arrays of transitions when we combine them and storing them
35 +// and filtering out the duplicates, we will instead store the unprocessed transitions
36 +// in an array and actually filter them in the passive phase.
37 +const transitionStack: StackCursor<Array<Transition> | null> = createCursor(
38 + null,
39 +);
40 +
41 function peekCacheFromPool(): Cache | null {
42 if (!enableCache) {
43 return (null: any);
@@ -75,25 +88,31 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache {
88 return freshCache;
89 }
90
78 -export function pushRootTransition(root: FiberRoot) {
79 - if (enableCache) {
80 - return;
91 +export function pushRootTransition(
92 + workInProgress: Fiber,
93 + root: FiberRoot,
94 + renderLanes: Lanes,
95 +) {
96 + if (enableTransitionTracing) {
97 + const rootTransitions = getWorkInProgressTransitions();
98 + push(transitionStack, rootTransitions, workInProgress);
99 }
82 - // Note: This function currently does nothing but I'll leave it here for
83 - // code organization purposes in case that changes.
100 }
101
86 -export function popRootTransition(root: FiberRoot, renderLanes: Lanes) {
87 - if (enableCache) {
88 - return;
102 +export function popRootTransition(
103 + workInProgress: Fiber,
104 + root: FiberRoot,
105 + renderLanes: Lanes,
106 +) {
107 + if (enableTransitionTracing) {
108 + pop(transitionStack, workInProgress);
109 }
90 - // Note: This function currently does nothing but I'll leave it here for
91 - // code organization purposes in case that changes.
110 }
111
112 export function pushTransition(
113 offscreenWorkInProgress: Fiber,
114 prevCachePool: SpawnedCachePool | null,
115 + newTransitions: Array<Transition> | null,
116 ): void {
117 if (enableCache) {
118 if (prevCachePool === null) {
@@ -102,12 +121,40 @@ export function pushTransition(
121 push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
122 }
123 }
124 +
125 + if (enableTransitionTracing) {
126 + if (transitionStack.current === null) {
127 + push(transitionStack, newTransitions, offscreenWorkInProgress);
128 + } else if (newTransitions === null) {
129 + push(transitionStack, transitionStack.current, offscreenWorkInProgress);
130 + } else {
131 + push(
132 + transitionStack,
133 + transitionStack.current.concat(newTransitions),
134 + offscreenWorkInProgress,
135 + );
136 + }
137 + }
138 }
139
107 -export function popTransition(workInProgress: Fiber) {
108 - if (enableCache) {
109 - pop(resumedCache, workInProgress);
140 +export function popTransition(workInProgress: Fiber, current: Fiber | null) {
141 + if (current !== null) {
142 + if (enableCache) {
143 + pop(resumedCache, workInProgress);
144 + }
145 +
146 + if (enableTransitionTracing) {
147 + pop(transitionStack, workInProgress);
148 + }
149 + }
150 +}
151 +
152 +export function getSuspendedTransitions(): Array<Transition> | null {
153 + if (!enableTransitionTracing) {
154 + return null;
155 }
156 +
157 + return transitionStack.current;
158 }
159
160 export function getSuspendedCache(): SpawnedCachePool | null {
packages/react-reconciler/src/ReactFiberUnwindWork.new.js
+6 -17
@@ -79,13 +79,12 @@ function unwindWork(
79 return null;
80 }
81 case HostRoot: {
82 + const root: FiberRoot = workInProgress.stateNode;
83 if (enableCache) {
83 - const root: FiberRoot = workInProgress.stateNode;
84 - popRootTransition(root, renderLanes);
85 -
84 const cache: Cache = workInProgress.memoizedState.cache;
85 popCacheProvider(workInProgress, cache);
86 }
87 + popRootTransition(workInProgress, root, renderLanes);
88 popHostContainer(workInProgress);
89 popTopLevelLegacyContextObject(workInProgress);
90 resetMutableSourceWorkInProgressVersions();
@@ -153,11 +152,7 @@ function unwindWork(
152 case OffscreenComponent:
153 case LegacyHiddenComponent:
154 popRenderLanes(workInProgress);
156 - if (enableCache) {
157 - if (current !== null) {
158 - popTransition(workInProgress);
159 - }
160 - }
155 + popTransition(workInProgress, current);
156 return null;
157 case CacheComponent:
158 if (enableCache) {
@@ -189,13 +184,12 @@ function unwindInterruptedWork(
184 break;
185 }
186 case HostRoot: {
187 + const root: FiberRoot = interruptedWork.stateNode;
188 if (enableCache) {
193 - const root: FiberRoot = interruptedWork.stateNode;
194 - popRootTransition(root, renderLanes);
195 -
189 const cache: Cache = interruptedWork.memoizedState.cache;
190 popCacheProvider(interruptedWork, cache);
191 }
192 + popRootTransition(interruptedWork, root, renderLanes);
193 popHostContainer(interruptedWork);
194 popTopLevelLegacyContextObject(interruptedWork);
195 resetMutableSourceWorkInProgressVersions();
@@ -221,12 +215,7 @@ function unwindInterruptedWork(
215 case OffscreenComponent:
216 case LegacyHiddenComponent:
217 popRenderLanes(interruptedWork);
224 - if (enableCache) {
225 - if (current !== null) {
226 - popTransition(interruptedWork);
227 - }
228 - }
229 -
218 + popTransition(interruptedWork, current);
219 break;
220 case CacheComponent:
221 if (enableCache) {
packages/react-reconciler/src/ReactFiberUnwindWork.old.js
+6 -17
@@ -79,13 +79,12 @@ function unwindWork(
79 return null;
80 }
81 case HostRoot: {
82 + const root: FiberRoot = workInProgress.stateNode;
83 if (enableCache) {
83 - const root: FiberRoot = workInProgress.stateNode;
84 - popRootTransition(root, renderLanes);
85 -
84 const cache: Cache = workInProgress.memoizedState.cache;
85 popCacheProvider(workInProgress, cache);
86 }
87 + popRootTransition(workInProgress, root, renderLanes);
88 popHostContainer(workInProgress);
89 popTopLevelLegacyContextObject(workInProgress);
90 resetMutableSourceWorkInProgressVersions();
@@ -153,11 +152,7 @@ function unwindWork(
152 case OffscreenComponent:
153 case LegacyHiddenComponent:
154 popRenderLanes(workInProgress);
156 - if (enableCache) {
157 - if (current !== null) {
158 - popTransition(workInProgress);
159 - }
160 - }
155 + popTransition(workInProgress, current);
156 return null;
157 case CacheComponent:
158 if (enableCache) {
@@ -189,13 +184,12 @@ function unwindInterruptedWork(
184 break;
185 }
186 case HostRoot: {
187 + const root: FiberRoot = interruptedWork.stateNode;
188 if (enableCache) {
193 - const root: FiberRoot = interruptedWork.stateNode;
194 - popRootTransition(root, renderLanes);
195 -
189 const cache: Cache = interruptedWork.memoizedState.cache;
190 popCacheProvider(interruptedWork, cache);
191 }
192 + popRootTransition(interruptedWork, root, renderLanes);
193 popHostContainer(interruptedWork);
194 popTopLevelLegacyContextObject(interruptedWork);
195 resetMutableSourceWorkInProgressVersions();
@@ -221,12 +215,7 @@ function unwindInterruptedWork(
215 case OffscreenComponent:
216 case LegacyHiddenComponent:
217 popRenderLanes(interruptedWork);
224 - if (enableCache) {
225 - if (current !== null) {
226 - popTransition(interruptedWork);
227 - }
228 - }
229 -
218 + popTransition(interruptedWork, current);
219 break;
220 case CacheComponent:
221 if (enableCache) {