Skip to content

Commit

Permalink
Fixed potential interaction tracing leak in Suspense thennable memoiz…
Browse files Browse the repository at this point in the history
…ation (#15531)

Audited the other places we call unstable_wrap() in React DOM and verified that they didn't have this similar problem.
  • Loading branch information
Brian Vaughn committed Apr 29, 2019
1 parent 12e5a13 commit 1b752f1
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 4 deletions.
6 changes: 3 additions & 3 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -1317,10 +1317,10 @@ function commitSuspenseComponent(finishedWork: Fiber) {
thenables.forEach(thenable => {
// Memoize using the boundary fiber to prevent redundant listeners.
let retry = resolveRetryThenable.bind(null, finishedWork, thenable);
if (enableSchedulerTracing) {
retry = Schedule_tracing_wrap(retry);
}
if (!retryCache.has(thenable)) {
if (enableSchedulerTracing) {
retry = Schedule_tracing_wrap(retry);
}
retryCache.add(thenable);
thenable.then(retry, retry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ let React;
let ReactTestRenderer;
let ReactFeatureFlags;
let Scheduler;
let SchedulerTracing;
let ReactCache;
let Suspense;
let act;
Expand All @@ -17,10 +18,12 @@ describe('ReactSuspense', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
ReactFeatureFlags.enableSchedulerTracing = true;
React = require('react');
ReactTestRenderer = require('react-test-renderer');
act = ReactTestRenderer.act;
Scheduler = require('scheduler');
SchedulerTracing = require('scheduler/tracing');
ReactCache = require('react-cache');

Suspense = React.Suspense;
Expand Down Expand Up @@ -914,6 +917,78 @@ describe('ReactSuspense', () => {
]);
});

it('should call onInteractionScheduledWorkCompleted after suspending', done => {
const subscriber = {
onInteractionScheduledWorkCompleted: jest.fn(),
onInteractionTraced: jest.fn(),
onWorkCanceled: jest.fn(),
onWorkScheduled: jest.fn(),
onWorkStarted: jest.fn(),
onWorkStopped: jest.fn(),
};
SchedulerTracing.unstable_subscribe(subscriber);
SchedulerTracing.unstable_trace('test', performance.now(), () => {
function App() {
return (
<React.Suspense fallback={<Text text="Loading..." />}>
<AsyncText text="A" ms={1000} />
<AsyncText text="B" ms={2000} />
<AsyncText text="C" ms={3000} />
</React.Suspense>
);
}

const root = ReactTestRenderer.create(null);
root.update(<App />);

expect(Scheduler).toHaveYielded([
'Suspend! [A]',
'Suspend! [B]',
'Suspend! [C]',
'Loading...',
]);

// Resolve A
jest.advanceTimersByTime(1000);

expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
expect(Scheduler).toFlushExpired([
'A',
// The promises for B and C have now been thrown twice
'Suspend! [B]',
'Suspend! [C]',
]);

// Resolve B
jest.advanceTimersByTime(1000);

expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
expect(Scheduler).toFlushExpired([
// Even though the promise for B was thrown twice, we should only
// re-render once.
'B',
// The promise for C has now been thrown three times
'Suspend! [C]',
]);

// Resolve C
jest.advanceTimersByTime(1000);

expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
expect(Scheduler).toFlushExpired([
// Even though the promise for C was thrown three times, we should only
// re-render once.
'C',
]);

done();
});

expect(
subscriber.onInteractionScheduledWorkCompleted,
).toHaveBeenCalledTimes(1);
});

it('#14162', () => {
const {lazy} = React;

Expand Down
6 changes: 5 additions & 1 deletion packages/react/src/__tests__/ReactProfiler-test.internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2607,7 +2607,11 @@ describe('Profiler', () => {
]);
onRender.mockClear();

expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted.mock.calls[0][0],
).toMatchInteraction(highPriUpdateInteraction);
onInteractionScheduledWorkCompleted.mockClear();

Scheduler.advanceTime(100);
jest.advanceTimersByTime(100);
Expand Down

0 comments on commit 1b752f1

Please sign in to comment.