@samitouri / QOS-React-2 / commits / d6f6b951e1

Support disabling interaction tracing for suspense promises (#16776)

* Support disabling interaction tracing for suspense promises If a thrown Promise has the __reactDoNotTraceInteractions attribute, React will not wrapped its callbacks to continue tracing any current interaction(s). * Added optional '__reactDoNotTraceInteractions' attribute to Flow Thenable type

Brian Vaughn committed Sep 13, 2019 at 08:59 UTC d6f6b951e12e25b67f681094714a337356d2a310
4 files changed +94 -2
packages/react-reconciler/src/ReactFiberCommitWork.js
+3 -1
@@ -1512,7 +1512,9 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
1512 let retry = resolveRetryThenable.bind(null, finishedWork, thenable);
1513 if (!retryCache.has(thenable)) {
1514 if (enableSchedulerTracing) {
1515 - retry = Schedule_tracing_wrap(retry);
1515 + if (thenable.__reactDoNotTraceInteractions !== true) {
1516 + retry = Schedule_tracing_wrap(retry);
1517 + }
1518 }
1519 retryCache.add(thenable);
1520 thenable.then(retry, retry);
packages/react-reconciler/src/ReactFiberThrow.js
+3 -1
@@ -174,7 +174,9 @@ function attachPingListener(
174 renderExpirationTime,
175 );
176 if (enableSchedulerTracing) {
177 - ping = Schedule_tracing_wrap(ping);
177 + if (thenable.__reactDoNotTraceInteractions !== true) {
178 + ping = Schedule_tracing_wrap(ping);
179 + }
180 }
181 thenable.then(ping, ping);
182 }
packages/react-reconciler/src/ReactFiberWorkLoop.js
+3
@@ -204,6 +204,9 @@ const RootCompleted = 4;
204
205 export type Thenable = {
206 then(resolve: () => mixed, reject?: () => mixed): Thenable | void,
207 +
208 + // Special flag to opt out of tracing interactions across a Suspense boundary.
209 + __reactDoNotTraceInteractions?: boolean,
210 };
211
212 // Describes where we are in the React execution stack
packages/react/src/__tests__/ReactProfiler-test.internal.js
+85
@@ -2729,6 +2729,91 @@ describe('Profiler', () => {
2729 onInteractionScheduledWorkCompleted.mock.calls[1][0],
2730 ).toMatchInteraction(highPriUpdateInteraction);
2731 });
2732 +
2733 + it('does not trace Promises flagged with __reactDoNotTraceInteractions', async () => {
2734 + loadModulesForTracing({useNoopRenderer: true});
2735 +
2736 + const interaction = {
2737 + id: 0,
2738 + name: 'initial render',
2739 + timestamp: Scheduler.unstable_now(),
2740 + };
2741 +
2742 + AsyncText = ({ms, text}) => {
2743 + try {
2744 + TextResource.read([text, ms]);
2745 + Scheduler.unstable_yieldValue(`AsyncText [${text}]`);
2746 + return text;
2747 + } catch (promise) {
2748 + promise.__reactDoNotTraceInteractions = true;
2749 +
2750 + if (typeof promise.then === 'function') {
2751 + Scheduler.unstable_yieldValue(`Suspend [${text}]`);
2752 + } else {
2753 + Scheduler.unstable_yieldValue(`Error [${text}]`);
2754 + }
2755 + throw promise;
2756 + }
2757 + };
2758 +
2759 + const onRender = jest.fn();
2760 + SchedulerTracing.unstable_trace(
2761 + interaction.name,
2762 + Scheduler.unstable_now(),
2763 + () => {
2764 + ReactNoop.render(
2765 + <React.Profiler id="test-profiler" onRender={onRender}>
2766 + <React.Suspense fallback={<Text text="Loading..." />}>
2767 + <AsyncText text="Async" ms={20000} />
2768 + </React.Suspense>
2769 + <Text text="Sync" />
2770 + </React.Profiler>,
2771 + );
2772 + },
2773 + );
2774 +
2775 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
2776 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2777 + interaction,
2778 + );
2779 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2780 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2781 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2782 +
2783 + expect(Scheduler).toFlushAndYield([
2784 + 'Suspend [Async]',
2785 + 'Text [Loading...]',
2786 + 'Text [Sync]',
2787 + ]);
2788 + // Should have committed the placeholder.
2789 + expect(ReactNoop.getChildrenAsJSX()).toEqual('Loading...Sync');
2790 + expect(onRender).toHaveBeenCalledTimes(1);
2791 +
2792 + let call = onRender.mock.calls[0];
2793 + expect(call[0]).toEqual('test-profiler');
2794 + expect(call[6]).toMatchInteractions(
2795 + ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
2796 + );
2797 +
2798 + // The interaction is now complete.
2799 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
2800 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
2801 + expect(
2802 + onInteractionScheduledWorkCompleted,
2803 + ).toHaveBeenLastNotifiedOfInteraction(interaction);
2804 +
2805 + // Once the promise resolves, we render the suspended view
2806 + await awaitableAdvanceTimers(20000);
2807 + expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
2808 + expect(Scheduler).toFlushAndYield(['AsyncText [Async]']);
2809 + expect(ReactNoop.getChildrenAsJSX()).toEqual('AsyncSync');
2810 + expect(onRender).toHaveBeenCalledTimes(2);
2811 +
2812 + // No interactions should be associated with this update.
2813 + call = onRender.mock.calls[1];
2814 + expect(call[0]).toEqual('test-profiler');
2815 + expect(call[6]).toMatchInteractions([]);
2816 + });
2817 });
2818 });
2819 });