Skip to content

Commit 8d7161e

Browse files
committedMay 25, 2016
Don't count the time inside flushes towards lifecycle hooks (facebook#6860)
* Don't count the time inside flushes towards lifecycle hooks Fixes facebook#6842. We keep the existing behavior of testing for matching `onBeginLifeCycleTimer`/`onEndLifeCycleTimer` calls, but we push the current timer onto the stack if we enter a flush. This solves an issue with portals which cause updates while a lifecycle timer is already running. I chose to subtract the time spent in the flush from the time counted towards the lifecycle method because it would artificially inflate the “total” time of the component due to all the components inside the portal, so it would skew the exclusive table. * Fix up the comment
1 parent de1de9e commit 8d7161e

File tree

2 files changed

+128
-33
lines changed

2 files changed

+128
-33
lines changed
 

‎src/renderers/shared/ReactDebugTool.js

+72-33
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ function emitEvent(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) {
4141

4242
var isProfiling = false;
4343
var flushHistory = [];
44+
var lifeCycleTimerStack = [];
4445
var currentFlushNesting = 0;
4546
var currentFlushMeasurements = null;
4647
var currentFlushStartTime = null;
4748
var currentTimerDebugID = null;
4849
var currentTimerStartTime = null;
50+
var currentTimerNestedFlushDuration = null;
4951
var currentTimerType = null;
5052

5153
function clearHistory() {
@@ -103,6 +105,72 @@ function checkDebugID(debugID) {
103105
warning(debugID, 'ReactDebugTool: debugID may not be empty.');
104106
}
105107

108+
function beginLifeCycleTimer(debugID, timerType) {
109+
if (!isProfiling || currentFlushNesting === 0) {
110+
return;
111+
}
112+
warning(
113+
!currentTimerType,
114+
'There is an internal error in the React performance measurement code. ' +
115+
'Did not expect %s timer to start while %s timer is still in ' +
116+
'progress for %s instance.',
117+
timerType,
118+
currentTimerType || 'no',
119+
(debugID === currentTimerDebugID) ? 'the same' : 'another'
120+
);
121+
currentTimerStartTime = performanceNow();
122+
currentTimerNestedFlushDuration = 0;
123+
currentTimerDebugID = debugID;
124+
currentTimerType = timerType;
125+
}
126+
127+
function endLifeCycleTimer(debugID, timerType) {
128+
if (!isProfiling || currentFlushNesting === 0) {
129+
return;
130+
}
131+
warning(
132+
currentTimerType === timerType,
133+
'There is an internal error in the React performance measurement code. ' +
134+
'We did not expect %s timer to stop while %s timer is still in ' +
135+
'progress for %s instance. Please report this as a bug in React.',
136+
timerType,
137+
currentTimerType || 'no',
138+
(debugID === currentTimerDebugID) ? 'the same' : 'another'
139+
);
140+
currentFlushMeasurements.push({
141+
timerType,
142+
instanceID: debugID,
143+
duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration,
144+
});
145+
currentTimerStartTime = null;
146+
currentTimerNestedFlushDuration = null;
147+
currentTimerDebugID = null;
148+
currentTimerType = null;
149+
}
150+
151+
function pauseCurrentLifeCycleTimer() {
152+
var currentTimer = {
153+
startTime: currentTimerStartTime,
154+
nestedFlushStartTime: performanceNow(),
155+
debugID: currentTimerDebugID,
156+
timerType: currentTimerType,
157+
};
158+
lifeCycleTimerStack.push(currentTimer);
159+
currentTimerStartTime = null;
160+
currentTimerNestedFlushDuration = null;
161+
currentTimerDebugID = null;
162+
currentTimerType = null;
163+
}
164+
165+
function resumeCurrentLifeCycleTimer() {
166+
var {startTime, nestedFlushStartTime, debugID, timerType} = lifeCycleTimerStack.pop();
167+
var nestedFlushDuration = performanceNow() - nestedFlushStartTime;
168+
currentTimerStartTime = startTime;
169+
currentTimerNestedFlushDuration += nestedFlushDuration;
170+
currentTimerDebugID = debugID;
171+
currentTimerType = timerType;
172+
}
173+
106174
var ReactDebugTool = {
107175
addDevtool(devtool) {
108176
eventHandlers.push(devtool);
@@ -148,58 +216,29 @@ var ReactDebugTool = {
148216
if (__DEV__) {
149217
currentFlushNesting++;
150218
resetMeasurements();
219+
pauseCurrentLifeCycleTimer();
151220
}
152221
emitEvent('onBeginFlush');
153222
},
154223
onEndFlush() {
155224
if (__DEV__) {
156225
resetMeasurements();
157226
currentFlushNesting--;
227+
resumeCurrentLifeCycleTimer();
158228
}
159229
emitEvent('onEndFlush');
160230
},
161231
onBeginLifeCycleTimer(debugID, timerType) {
162232
checkDebugID(debugID);
163233
emitEvent('onBeginLifeCycleTimer', debugID, timerType);
164234
if (__DEV__) {
165-
if (isProfiling && currentFlushNesting > 0) {
166-
warning(
167-
!currentTimerType,
168-
'There is an internal error in the React performance measurement code. ' +
169-
'Did not expect %s timer to start while %s timer is still in ' +
170-
'progress for %s instance.',
171-
timerType,
172-
currentTimerType || 'no',
173-
(debugID === currentTimerDebugID) ? 'the same' : 'another'
174-
);
175-
currentTimerStartTime = performanceNow();
176-
currentTimerDebugID = debugID;
177-
currentTimerType = timerType;
178-
}
235+
beginLifeCycleTimer(debugID, timerType);
179236
}
180237
},
181238
onEndLifeCycleTimer(debugID, timerType) {
182239
checkDebugID(debugID);
183240
if (__DEV__) {
184-
if (isProfiling && currentFlushNesting > 0) {
185-
warning(
186-
currentTimerType === timerType,
187-
'There is an internal error in the React performance measurement code. ' +
188-
'We did not expect %s timer to stop while %s timer is still in ' +
189-
'progress for %s instance. Please report this as a bug in React.',
190-
timerType,
191-
currentTimerType || 'no',
192-
(debugID === currentTimerDebugID) ? 'the same' : 'another'
193-
);
194-
currentFlushMeasurements.push({
195-
timerType,
196-
instanceID: debugID,
197-
duration: performanceNow() - currentTimerStartTime,
198-
});
199-
currentTimerStartTime = null;
200-
currentTimerDebugID = null;
201-
currentTimerType = null;
202-
}
241+
endLifeCycleTimer(debugID, timerType);
203242
}
204243
emitEvent('onEndLifeCycleTimer', debugID, timerType);
205244
},

‎src/renderers/shared/__tests__/ReactPerf-test.js

+56
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,62 @@ describe('ReactPerf', function() {
329329
}]);
330330
});
331331

332+
it('should not count time in a portal towards lifecycle method', function() {
333+
function Foo() {
334+
return null;
335+
}
336+
337+
var portalContainer = document.createElement('div');
338+
class Portal extends React.Component {
339+
componentDidMount() {
340+
ReactDOM.render(<Foo />, portalContainer);
341+
}
342+
render() {
343+
return null;
344+
}
345+
}
346+
347+
var container = document.createElement('div');
348+
var measurements = measure(() => {
349+
ReactDOM.render(<Portal />, container);
350+
});
351+
352+
expect(ReactPerf.getExclusive(measurements)).toEqual([{
353+
key: 'Portal',
354+
instanceCount: 1,
355+
totalDuration: 6,
356+
counts: {
357+
ctor: 1,
358+
componentDidMount: 1,
359+
render: 1,
360+
},
361+
durations: {
362+
ctor: 1,
363+
// We want to exclude nested imperative ReactDOM.render() from lifecycle hook's own time.
364+
// Otherwise it would artificially float to the top even though its exclusive time is small.
365+
// This is how we get 4 as a number with the performanceNow() mock:
366+
// - we capture the time we enter componentDidMount (n = 0)
367+
// - we capture the time when we enter a nested flush (n = 1)
368+
// - in the nested flush, we call it twice: before and after <Foo /> rendering. (n = 3)
369+
// - we capture the time when we exit a nested flush (n = 4)
370+
// - we capture the time we exit componentDidMount (n = 5)
371+
// Time spent in componentDidMount = (5 - 0 - (4 - 3)) = 4.
372+
componentDidMount: 4,
373+
render: 1,
374+
},
375+
}, {
376+
key: 'Foo',
377+
instanceCount: 1,
378+
totalDuration: 1,
379+
counts: {
380+
render: 1,
381+
},
382+
durations: {
383+
render: 1,
384+
},
385+
}]);
386+
});
387+
332388
it('warns once when using getMeasurementsSummaryMap', function() {
333389
var measurements = measure(() => {});
334390
spyOn(console, 'error');

0 commit comments

Comments
 (0)
Please sign in to comment.