Skip to content

Commit

Permalink
Move sync task queue to its own module (#21109)
Browse files Browse the repository at this point in the history
The sync task queue is React-specific and doesn't really have anything
to do with Scheduler. We'd keep using it even once `postTask` exists.

By separating that part out, `SchedulerWithReactIntegration` is now
just a module that re-exports the Scheduler API. So I unforked it.
When we switch to ES Modules, we can remove this re-exporting module.
  • Loading branch information
acdlite committed Mar 28, 2021
1 parent 3ba5c87 commit dbe98a5
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 112 deletions.
16 changes: 4 additions & 12 deletions packages/react-dom/src/events/ReactDOMEventListener.js
Expand Up @@ -34,25 +34,21 @@ import {
import getEventTarget from './getEventTarget';
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';

import {
enableLegacyFBSupport,
enableNewReconciler,
} from 'shared/ReactFeatureFlags';
import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags';
import {dispatchEventForPluginEventSystem} from './DOMPluginEventSystem';
import {
flushDiscreteUpdatesIfNeeded,
discreteUpdates,
} from './ReactDOMUpdateBatching';

import {getCurrentPriorityLevel as getCurrentPriorityLevel_old} from 'react-reconciler/src/SchedulerWithReactIntegration.old';
import {
getCurrentPriorityLevel as getCurrentPriorityLevel_new,
getCurrentPriorityLevel as getCurrentSchedulerPriorityLevel,
IdlePriority as IdleSchedulerPriority,
ImmediatePriority as ImmediateSchedulerPriority,
LowPriority as LowSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
} from 'react-reconciler/src/SchedulerWithReactIntegration.new';
} from 'react-reconciler/src/Scheduler';
import {
DiscreteEventPriority,
ContinuousEventPriority,
Expand All @@ -62,10 +58,6 @@ import {
setCurrentUpdatePriority,
} from 'react-reconciler/src/ReactEventPriorities';

const getCurrentPriorityLevel = enableNewReconciler
? getCurrentPriorityLevel_new
: getCurrentPriorityLevel_old;

// TODO: can we stop exporting these?
export let _enabled = true;

Expand Down Expand Up @@ -392,7 +384,7 @@ export function getEventPriority(domEventName: DOMEventName): * {
// We might be in the Scheduler callback.
// Eventually this mechanism will be replaced by a check
// of the current priority on the native scheduler.
const schedulerPriority = getCurrentPriorityLevel();
const schedulerPriority = getCurrentSchedulerPriorityLevel();
switch (schedulerPriority) {
case ImmediateSchedulerPriority:
return DiscreteEventPriority;
Expand Down
Expand Up @@ -28,7 +28,7 @@ import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new';

import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';

import {now} from './SchedulerWithReactIntegration.new';
import {now} from './Scheduler';

import {
IndeterminateComponent,
Expand Down
Expand Up @@ -28,7 +28,7 @@ import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old';

import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.old';

import {now} from './SchedulerWithReactIntegration.old';
import {now} from './Scheduler';

import {
IndeterminateComponent,
Expand Down
Expand Up @@ -25,7 +25,7 @@ import {
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
} from './SchedulerWithReactIntegration.new';
} from './Scheduler';

declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;

Expand Down
Expand Up @@ -25,7 +25,7 @@ import {
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
} from './SchedulerWithReactIntegration.old';
} from './Scheduler';

declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;

Expand Down
67 changes: 67 additions & 0 deletions packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js
@@ -0,0 +1,67 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {SchedulerCallback} from './Scheduler';

import {
DiscreteEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
} from './ReactEventPriorities.new';
import {ImmediatePriority, scheduleCallback} from './Scheduler';

let syncQueue: Array<SchedulerCallback> | null = null;
let isFlushingSyncQueue: boolean = false;

export function scheduleSyncCallback(callback: SchedulerCallback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
if (syncQueue === null) {
syncQueue = [callback];
} else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
syncQueue.push(callback);
}
}

export function flushSyncCallbackQueue() {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrancy.
isFlushingSyncQueue = true;
let i = 0;
const previousUpdatePriority = getCurrentUpdatePriority();
try {
const isSync = true;
const queue = syncQueue;
// TODO: Is this necessary anymore? The only user code that runs in this
// queue is in the render or commit phases.
setCurrentUpdatePriority(DiscreteEventPriority);
for (; i < queue.length; i++) {
let callback = queue[i];
do {
callback = callback(isSync);
} while (callback !== null);
}
syncQueue = null;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
}
// Resume flushing in the next tick
scheduleCallback(ImmediatePriority, flushSyncCallbackQueue);
throw error;
} finally {
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;
}
}
return null;
}
Expand Up @@ -7,50 +7,15 @@
* @flow
*/

// This module only exists as an ESM wrapper around the external CommonJS
// Scheduler dependency. Notice that we're intentionally not using named imports
// because Rollup would use dynamic dispatch for CommonJS interop named imports.
// When we switch to ESM, we can delete this module.
import * as Scheduler from 'scheduler';
import {__interactionsRef} from 'scheduler/tracing';
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
import type {SchedulerCallback} from './Scheduler';

import {
DiscreteEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
} from './ReactEventPriorities.old';
import {ImmediatePriority, scheduleCallback} from './Scheduler';

export const scheduleCallback = Scheduler.unstable_scheduleCallback;
export const cancelCallback = Scheduler.unstable_cancelCallback;
export const shouldYield = Scheduler.unstable_shouldYield;
export const requestPaint = Scheduler.unstable_requestPaint;
export const now = Scheduler.unstable_now;
export const getCurrentPriorityLevel =
Scheduler.unstable_getCurrentPriorityLevel;
export const ImmediatePriority = Scheduler.unstable_ImmediatePriority;
export const UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
export const NormalPriority = Scheduler.unstable_NormalPriority;
export const LowPriority = Scheduler.unstable_LowPriority;
export const IdlePriority = Scheduler.unstable_IdlePriority;

if (enableSchedulerTracing) {
// Provide explicit error message when production+profiling bundle of e.g.
// react-dom is used with production (non-profiling) bundle of
// scheduler/tracing
invariant(
__interactionsRef != null && __interactionsRef.current != null,
'It is not supported to run the profiling version of a renderer (for ' +
'example, `react-dom/profiling`) without also replacing the ' +
'`scheduler/tracing` module with `scheduler/tracing-profiling`. Your ' +
'bundler might have a setting for aliasing both modules. Learn more at ' +
'https://reactjs.org/link/profiling',
);
}

export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null;

// TODO: Move sync task queue to its own module.
let syncQueue: Array<SchedulerCallback> | null = null;
let isFlushingSyncQueue: boolean = false;

Expand Down
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/ReactFiberWorkLoop.new.js
Expand Up @@ -46,9 +46,11 @@ import {
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
} from './Scheduler';
import {
flushSyncCallbackQueue,
scheduleSyncCallback,
} from './SchedulerWithReactIntegration.new';
} from './ReactFiberSyncTaskQueue.new';
import {
NoFlags as NoHookEffect,
Passive as HookPassive,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Expand Up @@ -46,9 +46,11 @@ import {
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
} from './Scheduler';
import {
flushSyncCallbackQueue,
scheduleSyncCallback,
} from './SchedulerWithReactIntegration.old';
} from './ReactFiberSyncTaskQueue.old';
import {
NoFlags as NoHookEffect,
Passive as HookPassive,
Expand Down
Expand Up @@ -15,11 +15,6 @@ import * as Scheduler from 'scheduler';
import {__interactionsRef} from 'scheduler/tracing';
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
import {
DiscreteEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
} from './ReactEventPriorities.new';

export const scheduleCallback = Scheduler.unstable_scheduleCallback;
export const cancelCallback = Scheduler.unstable_cancelCallback;
Expand Down Expand Up @@ -49,54 +44,3 @@ if (enableSchedulerTracing) {
}

export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null;

// TODO: Move sync task queue to its own module.
let syncQueue: Array<SchedulerCallback> | null = null;
let isFlushingSyncQueue: boolean = false;

export function scheduleSyncCallback(callback: SchedulerCallback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
if (syncQueue === null) {
syncQueue = [callback];
} else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
syncQueue.push(callback);
}
}

export function flushSyncCallbackQueue() {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrancy.
isFlushingSyncQueue = true;
let i = 0;
const previousUpdatePriority = getCurrentUpdatePriority();
try {
const isSync = true;
const queue = syncQueue;
// TODO: Is this necessary anymore? The only user code that runs in this
// queue is in the render or commit phases.
setCurrentUpdatePriority(DiscreteEventPriority);
for (; i < queue.length; i++) {
let callback = queue[i];
do {
callback = callback(isSync);
} while (callback !== null);
}
syncQueue = null;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
}
// Resume flushing in the next tick
scheduleCallback(ImmediatePriority, flushSyncCallbackQueue);
throw error;
} finally {
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;
}
}
return null;
}

0 comments on commit dbe98a5

Please sign in to comment.