diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 539ef3ae0c2df6..5796ad6943db78 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -28,6 +28,7 @@ import { enableSchedulerTracing, enableProfilerTimer, enableSuspenseServerRenderer, + enableUpdateSchedulerTracking, enableEventAPI, } from 'shared/ReactFeatureFlags'; import { @@ -1338,7 +1339,8 @@ function commitSuspenseComponent( if (!retryCache.has(thenable)) { if (enableSchedulerTracing) { retry = Schedule_tracing_wrap(retry); - + } + if (enableUpdateSchedulerTracking) { // If we have pending work still, restore the original schedulers restorePendingSchedulers(finishedRoot, committedExpirationTime); } diff --git a/packages/react-reconciler/src/ReactFiberRoot.js b/packages/react-reconciler/src/ReactFiberRoot.js index 25f2f0c20dee46..d557a764840be8 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.js +++ b/packages/react-reconciler/src/ReactFiberRoot.js @@ -84,9 +84,11 @@ type ProfilingOnlyFiberRootProperties = {| interactionThreadID: number, memoizedInteractions: Set, pendingInteractionMap: PendingInteractionMap, +|}; - // Used to enable DevTools Profiler UI to show which Fibers scheduled a given commit. - // May also be useful in the future to expose via the Profiler API somehow? +// The following attributes are only used by DevTools and are only present in DEV builds. +// They enable DevTools Profiler UI to show which Fiber(s) scheduled a given commit. +type SchedulerTrackingOnlyFiberRootProperties = {| memoizedSchedulers: Set, pendingSchedulersMap: PendingSchedulersMap, |}; @@ -99,6 +101,7 @@ type ProfilingOnlyFiberRootProperties = {| export type FiberRoot = { ...BaseFiberRootProperties, ...ProfilingOnlyFiberRootProperties, + ...SchedulerTrackingOnlyFiberRootProperties, }; function FiberRootNode(containerInfo, tag, hydrate) { diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 815b85b7578a67..377a05f6137c1d 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -19,6 +19,7 @@ import type {Interaction} from 'scheduler/src/Tracing'; import { warnAboutDeprecatedLifecycles, enableUserTimingAPI, + enableUpdateSchedulerTracking, enableSuspenseServerRenderer, replayFailedUnitOfWorkWithInvokeGuardedCallback, enableProfilerTimer, @@ -336,7 +337,7 @@ export function scheduleUpdateOnFiber( return; } - if (enableSchedulerTracing) { + if (enableUpdateSchedulerTracking) { const pendingSchedulersMap = root.pendingSchedulersMap; let schedulers = pendingSchedulersMap.get(expirationTime); if (schedulers == null) { @@ -2179,6 +2180,10 @@ export function restorePendingSchedulers( root: FiberRoot, expirationTime: ExpirationTime, ): void { + if (!enableUpdateSchedulerTracking) { + return; + } + const pendingSchedulersMap = root.pendingSchedulersMap; let schedulers = pendingSchedulersMap.get(expirationTime); if (schedulers == null) { @@ -2306,52 +2311,54 @@ function schedulePendingInteraction(root, expirationTime) { function startWorkOnPendingInteraction(root, expirationTime) { // This is called when new work is started on a root. - if (!enableSchedulerTracing) { - return; - } - // Determine which interactions this batch of work currently includes, So that - // we can accurately attribute time spent working on it, And so that cascading - // work triggered during the render phase will be associated with it. - const interactions: Set = new Set(); - root.pendingInteractionMap.forEach( - (scheduledInteractions, scheduledExpirationTime) => { + if (enableUpdateSchedulerTracking) { + const memoizedSchedulers: Set = new Set(); + const pendingSchedulersMap = root.pendingSchedulersMap; + pendingSchedulersMap.forEach((schedulers, scheduledExpirationTime) => { if (scheduledExpirationTime >= expirationTime) { - scheduledInteractions.forEach(interaction => - interactions.add(interaction), - ); + pendingSchedulersMap.delete(scheduledExpirationTime); + schedulers.forEach(fiber => memoizedSchedulers.add(fiber)); } - }, - ); + }); - const memoizedSchedulers: Set = new Set(); - const pendingSchedulersMap = root.pendingSchedulersMap; - pendingSchedulersMap.forEach((schedulers, scheduledExpirationTime) => { - if (scheduledExpirationTime >= expirationTime) { - pendingSchedulersMap.delete(scheduledExpirationTime); - schedulers.forEach(fiber => memoizedSchedulers.add(fiber)); - } - }); + root.memoizedSchedulers = memoizedSchedulers; + } - // Store the current set of interactions on the FiberRoot for a few reasons: - // We can re-use it in hot functions like renderRoot() without having to - // recalculate it. We will also use it in commitWork() to pass to any Profiler - // onRender() hooks. This also provides DevTools with a way to access it when - // the onCommitRoot() hook is called. - root.memoizedInteractions = interactions; - root.memoizedSchedulers = memoizedSchedulers; + if (enableSchedulerTracing) { + // Determine which interactions this batch of work currently includes, So that + // we can accurately attribute time spent working on it, And so that cascading + // work triggered during the render phase will be associated with it. + const interactions: Set = new Set(); + root.pendingInteractionMap.forEach( + (scheduledInteractions, scheduledExpirationTime) => { + if (scheduledExpirationTime >= expirationTime) { + scheduledInteractions.forEach(interaction => + interactions.add(interaction), + ); + } + }, + ); - if (interactions.size > 0) { - const subscriber = __subscriberRef.current; - if (subscriber !== null) { - const threadID = computeThreadID(root, expirationTime); - try { - subscriber.onWorkStarted(interactions, threadID); - } catch (error) { - // If the subscriber throws, rethrow it in a separate task - scheduleCallback(ImmediatePriority, () => { - throw error; - }); + // Store the current set of interactions on the FiberRoot for a few reasons: + // We can re-use it in hot functions like renderRoot() without having to + // recalculate it. We will also use it in commitWork() to pass to any Profiler + // onRender() hooks. This also provides DevTools with a way to access it when + // the onCommitRoot() hook is called. + root.memoizedInteractions = interactions; + + if (interactions.size > 0) { + const subscriber = __subscriberRef.current; + if (subscriber !== null) { + const threadID = computeThreadID(root, expirationTime); + try { + subscriber.onWorkStarted(interactions, threadID); + } catch (error) { + // If the subscriber throws, rethrow it in a separate task + scheduleCallback(ImmediatePriority, () => { + throw error; + }); + } } } } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 50a2841a009697..4cd8a5185b6673 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -40,6 +40,7 @@ import { import { enableSchedulerTracing, enableSuspenseServerRenderer, + enableUpdateSchedulerTracking, enableEventAPI, } from 'shared/ReactFeatureFlags'; import {NoMode, BatchedMode} from './ReactTypeOfMode'; @@ -187,7 +188,8 @@ function attachPingListener( ); if (enableSchedulerTracing) { ping = Schedule_tracing_wrap(ping); - + } + if (enableUpdateSchedulerTracking) { // If we have pending work still, restore the original schedulers restorePendingSchedulers(root, renderExpirationTime); } @@ -322,7 +324,8 @@ function throwException( let retry = resolveRetryThenable.bind(null, workInProgress, thenable); if (enableSchedulerTracing) { retry = Schedule_tracing_wrap(retry); - + } + if (enableUpdateSchedulerTracking) { // If we have pending work still, restore the original schedulers restorePendingSchedulers(root, renderExpirationTime); } diff --git a/packages/react-reconciler/src/__tests__/ReactSchedulers-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSchedulers-test.internal.js index 0d470eca1c8ab0..645d799591b97a 100644 --- a/packages/react-reconciler/src/__tests__/ReactSchedulers-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSchedulers-test.internal.js @@ -16,10 +16,7 @@ let Scheduler; let TestUtils; let mockDevToolsHook; -function loadModules({ - enableProfilerTimer = true, - enableSchedulerTracing = true, -} = {}) { +function loadModules(enableUpdateSchedulerTracking = true) { jest.resetModules(); mockDevToolsHook = { @@ -35,8 +32,7 @@ function loadModules({ ); ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer; - ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing; + ReactFeatureFlags.enableUpdateSchedulerTracking = enableUpdateSchedulerTracking; React = require('react'); ReactDOM = require('react-dom'); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 398aa209707d43..a4492d9b0943bc 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -31,6 +31,9 @@ export const enableProfilerTimer = __PROFILE__; // Trace which interactions trigger each commit. export const enableSchedulerTracing = __PROFILE__; +// Track which Fiber(s) schedule render work. +export const enableUpdateSchedulerTracking = __PROFILE__; + // Only used in www builds. export const enableSuspenseServerRenderer = false; // TODO: __DEV__? Here it might just be false. diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 285ea06bbc64f4..b11ace5f41caf3 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -19,6 +19,7 @@ export const {debugRenderPhaseSideEffects} = require('ReactFeatureFlags'); export const enableUserTimingAPI = __DEV__; export const enableProfilerTimer = __PROFILE__; export const enableSchedulerTracing = __PROFILE__; +export const enableUpdateSchedulerTracking = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableStableConcurrentModeAPIs = false; export const warnAboutShorthandPropertyCollision = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 60f29acdc77977..487cb083c15b65 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -19,6 +19,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; export const warnAboutDeprecatedLifecycles = true; export const enableProfilerTimer = __PROFILE__; export const enableSchedulerTracing = __PROFILE__; +export const enableUpdateSchedulerTracking = __PROFILE__; export const enableSuspenseServerRenderer = false; export const disableJavaScriptURLs = false; export const disableYielding = false; diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js index 14b8716b963420..8d6c91e08394cf 100644 --- a/packages/shared/forks/ReactFeatureFlags.persistent.js +++ b/packages/shared/forks/ReactFeatureFlags.persistent.js @@ -19,6 +19,7 @@ export const warnAboutDeprecatedLifecycles = true; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; export const enableProfilerTimer = __PROFILE__; export const enableSchedulerTracing = __PROFILE__; +export const enableUpdateSchedulerTracking = __PROFILE__; export const enableSuspenseServerRenderer = false; export const disableJavaScriptURLs = false; export const disableYielding = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 40c982f3e7cc37..06a1f04d05e7f5 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -19,6 +19,7 @@ export const warnAboutDeprecatedLifecycles = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = false; export const enableSchedulerTracing = false; +export const enableUpdateSchedulerTracking = false; export const enableSuspenseServerRenderer = false; export const disableJavaScriptURLs = false; export const disableYielding = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index f6f80c89853508..f708fb31217b4a 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -19,6 +19,7 @@ export const warnAboutDeprecatedLifecycles = true; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = false; export const enableSchedulerTracing = false; +export const enableUpdateSchedulerTracking = false; export const enableSuspenseServerRenderer = false; export const enableStableConcurrentModeAPIs = false; export const enableSchedulerDebugging = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 0be35ad2d9f4a3..deb5f2fd82db59 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -32,6 +32,7 @@ export let enableUserTimingAPI = __DEV__; export const enableProfilerTimer = __PROFILE__; export const enableSchedulerTracing = __PROFILE__; +export const enableUpdateSchedulerTracking = __PROFILE__; export const enableSchedulerDebugging = true; export const enableStableConcurrentModeAPIs = false;