From 4fdeaa0824d83b3481b1e1c8029aa640846e61ed Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Wed, 27 Jan 2021 12:08:37 -0700 Subject: [PATCH] Use callback priority to determine cancellation --- .../src/ReactFiberWorkLoop.new.js | 64 +++++++++++++++---- .../src/ReactFiberWorkLoop.old.js | 64 +++++++++++++++---- scripts/error-codes/codes.json | 3 +- 3 files changed, 104 insertions(+), 27 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 0281eec1ce991..dfc531c4701fa 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -712,26 +712,59 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { // This returns the priority level computed during the `getNextLanes` call. const newCallbackPriority = returnNextLanesPriority(); - if (nextLanes === NoLanes) { - // Special case: There's nothing to work on. - if (existingCallbackNode !== null) { - cancelCallback(existingCallbackNode); + if (enableDiscreteEventMicroTasks) { + if (nextLanes === NoLanes) { + // Special case: There's nothing to work on. + if (existingCallbackNode !== null) { + cancelCallback(existingCallbackNode); + } root.callbackNode = null; root.callbackPriority = NoLanePriority; + return; } - return; - } - // Check if there's an existing task. We may be able to reuse it. - if (existingCallbackNode !== null) { + // Check if there's an existing task. We may be able to reuse it. const existingCallbackPriority = root.callbackPriority; - if (existingCallbackPriority === newCallbackPriority) { - // The priority hasn't changed. We can reuse the existing task. Exit. + if ( + existingCallbackPriority !== NoLanePriority && + existingCallbackPriority !== InputDiscreteLanePriority + ) { + if (existingCallbackPriority === newCallbackPriority) { + // The priority hasn't changed. We can reuse the existing task. Exit. + return; + } + + invariant( + existingCallbackNode != null, + 'Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.', + ); + + // The priority changed. Cancel the existing callback. We'll schedule a new + // one below. + cancelCallback(existingCallbackNode); + } + } else { + if (nextLanes === NoLanes) { + // Special case: There's nothing to work on. + if (existingCallbackNode !== null) { + cancelCallback(existingCallbackNode); + root.callbackNode = null; + root.callbackPriority = NoLanePriority; + } return; } - // The priority changed. Cancel the existing callback. We'll schedule a new - // one below. - cancelCallback(existingCallbackNode); + + // Check if there's an existing task. We may be able to reuse it. + if (existingCallbackNode !== null) { + const existingCallbackPriority = root.callbackPriority; + if (existingCallbackPriority === newCallbackPriority) { + // The priority hasn't changed. We can reuse the existing task. Exit. + return; + } + // The priority changed. Cancel the existing callback. We'll schedule a new + // one below. + cancelCallback(existingCallbackNode); + } } // Schedule a new callback. @@ -739,6 +772,8 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { if (newCallbackPriority === SyncLanePriority) { // Special case: Sync React callbacks are scheduled on a special // internal queue + + // TODO: After enableDiscreteEventMicroTasks lands, we can remove the fake node. newCallbackNode = scheduleSyncCallback( performSyncWorkOnRoot.bind(null, root), ); @@ -1879,6 +1914,9 @@ function commitRootImpl(root, renderPriorityLevel) { // commitRoot never returns a continuation; it always finishes synchronously. // So we can clear these now to allow a new callback to be scheduled. root.callbackNode = null; + if (enableDiscreteEventMicroTasks) { + root.callbackPriority = NoLanePriority; + } // Update the first and last pending times on this root. The new first // pending time is whatever is left on the root fiber. diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index c4f49c3e8dd2b..a777e9a260f4d 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -694,26 +694,59 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { // This returns the priority level computed during the `getNextLanes` call. const newCallbackPriority = returnNextLanesPriority(); - if (nextLanes === NoLanes) { - // Special case: There's nothing to work on. - if (existingCallbackNode !== null) { - cancelCallback(existingCallbackNode); + if (enableDiscreteEventMicroTasks) { + if (nextLanes === NoLanes) { + // Special case: There's nothing to work on. + if (existingCallbackNode !== null) { + cancelCallback(existingCallbackNode); + } root.callbackNode = null; root.callbackPriority = NoLanePriority; + return; } - return; - } - // Check if there's an existing task. We may be able to reuse it. - if (existingCallbackNode !== null) { + // Check if there's an existing task. We may be able to reuse it. const existingCallbackPriority = root.callbackPriority; - if (existingCallbackPriority === newCallbackPriority) { - // The priority hasn't changed. We can reuse the existing task. Exit. + if ( + existingCallbackPriority !== NoLanePriority && + existingCallbackPriority !== InputDiscreteLanePriority + ) { + if (existingCallbackPriority === newCallbackPriority) { + // The priority hasn't changed. We can reuse the existing task. Exit. + return; + } + + invariant( + existingCallbackNode != null, + 'Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.', + ); + + // The priority changed. Cancel the existing callback. We'll schedule a new + // one below. + cancelCallback(existingCallbackNode); + } + } else { + if (nextLanes === NoLanes) { + // Special case: There's nothing to work on. + if (existingCallbackNode !== null) { + cancelCallback(existingCallbackNode); + root.callbackNode = null; + root.callbackPriority = NoLanePriority; + } return; } - // The priority changed. Cancel the existing callback. We'll schedule a new - // one below. - cancelCallback(existingCallbackNode); + + // Check if there's an existing task. We may be able to reuse it. + if (existingCallbackNode !== null) { + const existingCallbackPriority = root.callbackPriority; + if (existingCallbackPriority === newCallbackPriority) { + // The priority hasn't changed. We can reuse the existing task. Exit. + return; + } + // The priority changed. Cancel the existing callback. We'll schedule a new + // one below. + cancelCallback(existingCallbackNode); + } } // Schedule a new callback. @@ -721,6 +754,8 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { if (newCallbackPriority === SyncLanePriority) { // Special case: Sync React callbacks are scheduled on a special // internal queue + + // TODO: After enableDiscreteEventMicroTasks lands, we can remove the fake node. newCallbackNode = scheduleSyncCallback( performSyncWorkOnRoot.bind(null, root), ); @@ -1859,6 +1894,9 @@ function commitRootImpl(root, renderPriorityLevel) { // commitRoot never returns a continuation; it always finishes synchronously. // So we can clear these now to allow a new callback to be scheduled. root.callbackNode = null; + if (enableDiscreteEventMicroTasks) { + root.callbackPriority = NoLanePriority; + } // Update the first and last pending times on this root. The new first // pending time is whatever is left on the root fiber. diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 12a8db733b0ce..ac30edd08d14d 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -372,5 +372,6 @@ "381": "This feature is not supported by ReactSuspenseTestUtils.", "382": "This query has received more parameters than the last time the same query was used. Always pass the exact number of parameters that the query needs.", "383": "This query has received fewer parameters than the last time the same query was used. Always pass the exact number of parameters that the query needs.", - "384": "Refreshing the cache is not supported in Server Components." + "384": "Refreshing the cache is not supported in Server Components.", + "385": "Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue." }