Skip to content

Commit

Permalink
Make ReactPerf.start() work during reconciliation (#7208)
Browse files Browse the repository at this point in the history
* Add failing test demonstrating a ReactPerf warning

* Make the failing ReactPerf test more precise

* Make ReactPerf.start() work during reconciliation

* Reorder lifecycle methods for greater clarity

* Fix memory leak

* Error boundaries should not break ReactPerf

* Put onBeginFlush/onEndFlush into transaction wrappers

This looks cleaner even though it is not strictly necessary.
We still call them manually for unmounting because it doesn't have a transaction.

(cherry picked from commit 1a0e3a3)
  • Loading branch information
gaearon authored and zpao committed Jul 8, 2016
1 parent ccd781d commit 9fb8989
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 38 deletions.
5 changes: 0 additions & 5 deletions src/renderers/dom/client/ReactMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,6 @@ var ReactMount = {
shouldReuseMarkup,
context
) {
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}

// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case.
Expand Down Expand Up @@ -364,7 +360,6 @@ var ReactMount = {
ReactInstrumentation.debugTool.onMountRootComponent(
componentInstance._renderedComponent._debugID
);
ReactInstrumentation.debugTool.onEndFlush();
}

return componentInstance;
Expand Down
8 changes: 8 additions & 0 deletions src/renderers/dom/client/ReactReconcileTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var CallbackQueue = require('CallbackQueue');
var PooledClass = require('PooledClass');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactInputSelection = require('ReactInputSelection');
var ReactInstrumentation = require('ReactInstrumentation');
var Transaction = require('Transaction');
var ReactUpdateQueue = require('ReactUpdateQueue');

Expand Down Expand Up @@ -91,6 +92,13 @@ var TRANSACTION_WRAPPERS = [
ON_DOM_READY_QUEUEING,
];

if (__DEV__) {
TRANSACTION_WRAPPERS.push({
initialize: ReactInstrumentation.debugTool.onBeginFlush,
close: ReactInstrumentation.debugTool.onEndFlush,
});
}

/**
* Currently:
* - The order that these are listed in the transaction is critical:
Expand Down
4 changes: 0 additions & 4 deletions src/renderers/dom/server/ReactServerRendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ function renderToStringImpl(element, makeStaticMarkup) {
transaction = ReactServerRenderingTransaction.getPooled(makeStaticMarkup);

return transaction.perform(function() {
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}
var componentInstance = instantiateReactComponent(element, true);
var markup = ReactReconciler.mountComponent(
componentInstance,
Expand All @@ -52,7 +49,6 @@ function renderToStringImpl(element, makeStaticMarkup) {
ReactInstrumentation.debugTool.onUnmountComponent(
componentInstance._debugID
);
ReactInstrumentation.debugTool.onEndFlush();
}
if (!makeStaticMarkup) {
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
Expand Down
8 changes: 8 additions & 0 deletions src/renderers/dom/server/ReactServerRenderingTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

var PooledClass = require('PooledClass');
var Transaction = require('Transaction');
var ReactInstrumentation = require('ReactInstrumentation');
var ReactServerUpdateQueue = require('ReactServerUpdateQueue');


Expand All @@ -23,6 +24,13 @@ var ReactServerUpdateQueue = require('ReactServerUpdateQueue');
*/
var TRANSACTION_WRAPPERS = [];

if (__DEV__) {
TRANSACTION_WRAPPERS.push({
initialize: ReactInstrumentation.debugTool.onBeginFlush,
close: ReactInstrumentation.debugTool.onEndFlush,
});
}

var noopCallbackQueue = {
enqueue: function() {},
};
Expand Down
5 changes: 0 additions & 5 deletions src/renderers/native/ReactNativeMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,6 @@ var ReactNativeMount = {
var instance = instantiateReactComponent(nextWrappedElement, false);
ReactNativeMount._instancesByContainerID[containerTag] = instance;

if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}

// The initial render is synchronous but any updates that happen during
// rendering, in componentWillMount or componentDidMount, will be batched
// according to the current batching strategy.
Expand All @@ -156,7 +152,6 @@ var ReactNativeMount = {
ReactInstrumentation.debugTool.onMountRootComponent(
instance._renderedComponent._debugID
);
ReactInstrumentation.debugTool.onEndFlush();
}
var component = instance.getPublicInstance();
if (callback) {
Expand Down
8 changes: 8 additions & 0 deletions src/renderers/native/ReactNativeReconcileTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
var CallbackQueue = require('CallbackQueue');
var PooledClass = require('PooledClass');
var Transaction = require('Transaction');
var ReactInstrumentation = require('ReactInstrumentation');
var ReactUpdateQueue = require('ReactUpdateQueue');

/**
Expand Down Expand Up @@ -43,6 +44,13 @@ var ON_DOM_READY_QUEUEING = {
*/
var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING];

if (__DEV__) {
TRANSACTION_WRAPPERS.push({
initialize: ReactInstrumentation.debugTool.onBeginFlush,
close: ReactInstrumentation.debugTool.onEndFlush,
});
}

/**
* Currently:
* - The order that these are listed in the transaction is critical:
Expand Down
27 changes: 18 additions & 9 deletions src/renderers/shared/ReactDebugTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function resetMeasurements() {
var previousMeasurements = currentFlushMeasurements || [];
var previousOperations = ReactHostOperationHistoryDevtool.getHistory();

if (!isProfiling || currentFlushNesting === 0) {
if (currentFlushNesting === 0) {
currentFlushStartTime = null;
currentFlushMeasurements = null;
clearHistory();
Expand All @@ -105,7 +105,7 @@ function checkDebugID(debugID) {
}

function beginLifeCycleTimer(debugID, timerType) {
if (!isProfiling || currentFlushNesting === 0) {
if (currentFlushNesting === 0) {
return;
}
warning(
Expand All @@ -124,7 +124,7 @@ function beginLifeCycleTimer(debugID, timerType) {
}

function endLifeCycleTimer(debugID, timerType) {
if (!isProfiling || currentFlushNesting === 0) {
if (currentFlushNesting === 0) {
return;
}
warning(
Expand All @@ -136,11 +136,13 @@ function endLifeCycleTimer(debugID, timerType) {
currentTimerType || 'no',
(debugID === currentTimerDebugID) ? 'the same' : 'another'
);
currentFlushMeasurements.push({
timerType,
instanceID: debugID,
duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration,
});
if (isProfiling) {
currentFlushMeasurements.push({
timerType,
instanceID: debugID,
duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration,
});
}
currentTimerStartTime = null;
currentTimerNestedFlushDuration = null;
currentTimerDebugID = null;
Expand Down Expand Up @@ -193,6 +195,7 @@ var ReactDebugTool = {
isProfiling = true;
flushHistory.length = 0;
resetMeasurements();
ReactDebugTool.addDevtool(ReactHostOperationHistoryDevtool);
},
endProfiling() {
if (!isProfiling) {
Expand All @@ -201,6 +204,7 @@ var ReactDebugTool = {

isProfiling = false;
resetMeasurements();
ReactDebugTool.removeDevtool(ReactHostOperationHistoryDevtool);
},
getFlushHistory() {
return flushHistory;
Expand Down Expand Up @@ -235,6 +239,12 @@ var ReactDebugTool = {
checkDebugID(debugID);
emitEvent('onEndReconcilerTimer', debugID, timerType);
},
onError(debugID) {
if (currentTimerDebugID != null) {
endLifeCycleTimer(currentTimerDebugID, currentTimerType);
}
emitEvent('onError', debugID);
},
onBeginProcessingChildContext() {
emitEvent('onBeginProcessingChildContext');
},
Expand Down Expand Up @@ -300,7 +310,6 @@ var ReactDebugTool = {

ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool);
ReactDebugTool.addDevtool(ReactComponentTreeDevtool);
ReactDebugTool.addDevtool(ReactHostOperationHistoryDevtool);
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
if ((/[?&]react_perf\b/).test(url)) {
ReactDebugTool.beginProfiling();
Expand Down
46 changes: 45 additions & 1 deletion src/renderers/shared/__tests__/ReactPerf-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,5 +467,49 @@ describe('ReactPerf', function() {
expect(console.error.calls.count()).toBe(1);

__DEV__ = true;
})
});

it('should work when measurement starts during reconciliation', () => {
// https://github.com/facebook/react/issues/6949#issuecomment-230371009
var Measurer = React.createClass({
componentWillMount() {
ReactPerf.start();
},
componentDidMount() {
ReactPerf.stop();
},
componentWillUpdate() {
ReactPerf.start();
},
componentDidUpdate() {
ReactPerf.stop();
},
render() {
// Force reconciliation despite constant element
return React.cloneElement(this.props.children);
},
});

var container = document.createElement('div');
ReactDOM.render(<Measurer><App /></Measurer>, container);
expect(ReactPerf.getWasted()).toEqual([]);

ReactDOM.render(<Measurer><App /></Measurer>, container);
expect(ReactPerf.getWasted()).toEqual([{
key: 'Measurer',
instanceCount: 1,
inclusiveRenderDuration: 4,
renderCount: 1,
}, {
key: 'App',
instanceCount: 1,
inclusiveRenderDuration: 3,
renderCount: 1,
}, {
key: 'App > Box',
instanceCount: 2,
inclusiveRenderDuration: 2,
renderCount: 2,
}]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

describe('ReactHostOperationHistoryDevtool', () => {
var React;
var ReactPerf;
var ReactDOM;
var ReactDOMComponentTree;
var ReactDOMFeatureFlags;
Expand All @@ -22,12 +23,19 @@ describe('ReactHostOperationHistoryDevtool', () => {
jest.resetModuleRegistry();

React = require('React');
ReactPerf = require('ReactPerf');
ReactDOM = require('ReactDOM');
ReactDOMComponentTree = require('ReactDOMComponentTree');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
ReactHostOperationHistoryDevtool = require('ReactHostOperationHistoryDevtool');

ReactPerf.start();
});

afterEach(() => {
ReactPerf.stop();
})

function assertHistoryMatches(expectedHistory) {
var actualHistory = ReactHostOperationHistoryDevtool.getHistory();
expect(actualHistory).toEqual(expectedHistory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,11 @@ var ReactCompositeComponentMixin = {
try {
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
} catch (e) {
if (__DEV__) {
if (this._debugID !== 0) {
ReactInstrumentation.debugTool.onError();
}
}
// Roll back to checkpoint, handle error (which may add items to the transaction), and take a new checkpoint
transaction.rollback(checkpoint);
this._instance.unstable_handleError(e);
Expand Down
9 changes: 0 additions & 9 deletions src/renderers/shared/stack/reconciler/ReactUpdates.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
var CallbackQueue = require('CallbackQueue');
var PooledClass = require('PooledClass');
var ReactFeatureFlags = require('ReactFeatureFlags');
var ReactInstrumentation = require('ReactInstrumentation');
var ReactReconciler = require('ReactReconciler');
var Transaction = require('Transaction');

Expand Down Expand Up @@ -193,10 +192,6 @@ function runBatchedUpdates(transaction) {
}

var flushBatchedUpdates = function() {
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}

// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
// array and perform any updates enqueued by mount-ready handlers (i.e.,
// componentDidUpdate) but we need to check here too in order to catch
Expand All @@ -216,10 +211,6 @@ var flushBatchedUpdates = function() {
CallbackQueue.release(queue);
}
}

if (__DEV__) {
ReactInstrumentation.debugTool.onEndFlush();
}
};

/**
Expand Down
5 changes: 0 additions & 5 deletions src/renderers/testing/ReactTestMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,6 @@ var ReactHostMount = {

var instance = instantiateReactComponent(nextWrappedElement, false);

if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}

// The initial render is synchronous but any updates that happen during
// rendering, in componentWillMount or componentDidMount, will be batched
// according to the current batching strategy.
Expand All @@ -139,7 +135,6 @@ var ReactHostMount = {
ReactInstrumentation.debugTool.onMountRootComponent(
instance._renderedComponent._debugID
);
ReactInstrumentation.debugTool.onEndFlush();
}
return new ReactTestInstance(instance);
},
Expand Down

0 comments on commit 9fb8989

Please sign in to comment.