diff --git a/packages/react-reconciler/src/ReactFiberRootScheduler.js b/packages/react-reconciler/src/ReactFiberRootScheduler.js index 8e0c65b855..835e9f9a70 100644 --- a/packages/react-reconciler/src/ReactFiberRootScheduler.js +++ b/packages/react-reconciler/src/ReactFiberRootScheduler.js @@ -151,29 +151,17 @@ export function flushSyncWorkOnLegacyRootsOnly() { flushSyncWorkAcrossRoots_impl(true); } -function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) { - if (isFlushingWork) { - // Prevent reentrancy. - // TODO: Is this overly defensive? The callers must check the execution - // context first regardless. - return; - } - - if (!mightHavePendingSyncWork) { - // Fast path. There's no sync work to do. - return; - } - - const workInProgressRoot = getWorkInProgressRoot(); - const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes(); - - // There may or may not be synchronous work scheduled. Let's check. - let didPerformSomeWork; +export function _doFlushWork( + firstRoot, + workInProgressRoot, + workInProgressRootRenderLanes, + onlyLegacy, +) { + let didPerformSomeWork = false; let errors: Array | null = null; - isFlushingWork = true; do { didPerformSomeWork = false; - let root = firstScheduledRoot; + let root = firstRoot; while (root !== null) { if (onlyLegacy && root.tag !== LegacyRoot) { // Skip non-legacy roots. @@ -202,6 +190,33 @@ function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) { root = root.next; } } while (didPerformSomeWork); + + return errors; +} +function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) { + if (isFlushingWork) { + // Prevent reentrancy. + // TODO: Is this overly defensive? The callers must check the execution + // context first regardless. + return; + } + + if (!mightHavePendingSyncWork) { + // Fast path. There's no sync work to do. + return; + } + + const workInProgressRoot = getWorkInProgressRoot(); + const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes(); + + // There may or may not be synchronous work scheduled. Let's check. + isFlushingWork = true; + const errors = _doFlushWork( + firstScheduledRoot, + workInProgressRoot, + workInProgressRootRenderLanes, + onlyLegacy, + ); isFlushingWork = false; // If any errors were thrown, rethrow them right before exiting. diff --git a/packages/react-reconciler/src/__tests__/ReactFiberRootScheduler-test.js b/packages/react-reconciler/src/__tests__/ReactFiberRootScheduler-test.js new file mode 100644 index 0000000000..1e6bf432e1 --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ReactFiberRootScheduler-test.js @@ -0,0 +1,63 @@ +let _doFlushWork; +const shimHostConfigPath = 'react-reconciler/src/ReactFiberConfig'; + +jest.mock(shimHostConfigPath, () => { + return jest.requireActual( + 'react-dom-bindings/src/client/ReactFiberConfigDOM.js', + ); +}); +beforeAll(() => { + _doFlushWork = require('../ReactFiberRootScheduler')._doFlushWork; +}); + +test('does not hang', () => { + const root = { + tag: 1, + pendingChildren: null, + pingCache: {}, + finishedWork: null, + timeoutHandle: -1, + cancelPendingCommit: null, + context: {}, + pendingContext: null, + next: null, + callbackNode: null, + callbackPriority: 0, + expirationTimes: [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 278303.90000000596, + 278417.1999999881, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, + ], + pendingLanes: 6176, + suspendedLanes: 0, + pingedLanes: 0, + expiredLanes: 0, + mutableReadLanes: 0, + finishedLanes: 0, + errorRecoveryDisabledLanes: 0, + entangledLanes: 6144, + entanglements: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6144, 6144, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ], + hiddenUpdates: [], + identifierPrefix: '', + pooledCache: null, + pooledCacheLanes: 0, + mutableSourceEagerHydrationData: null, + hydrationCallbacks: { + unstable_concurrentUpdatesByDefault: true, + unstable_strictMode: true, + }, + incompleteTransitions: {}, + effectDuration: 0, + passiveEffectDuration: 0, + memoizedUpdaters: {}, + pendingUpdatersLaneMap: [], + _debugRootType: 'hydrateRoot()', + }; + + expect(() => { + _doFlushWork(root, root, 2, false); + }).not.toThrow(); +});