Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Scheduler] Profiling features #16145

Merged
merged 4 commits into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ module.exports = {
],

globals: {
SharedArrayBuffer: true,

spyOnDev: true,
spyOnDevAndProd: true,
spyOnProd: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ describe('ReactDebugFiberPerf', () => {
require('shared/ReactFeatureFlags').enableProfilerTimer = false;
require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
require('scheduler/src/SchedulerFeatureFlags').enableProfiling = false;

// Import after the polyfill is set up:
React = require('react');
Expand Down
4 changes: 4 additions & 0 deletions packages/scheduler/npm/umd/scheduler.development.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,9 @@
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_UserBlockingPriority;
},
get unstable_sharedProfilingBuffer() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_getFirstCallbackNode;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a copy paste mistake?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :)

},
});
});
4 changes: 4 additions & 0 deletions packages/scheduler/npm/umd/scheduler.production.min.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,9 @@
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_UserBlockingPriority;
},
get unstable_sharedProfilingBuffer() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_getFirstCallbackNode;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question

},
});
});
4 changes: 4 additions & 0 deletions packages/scheduler/npm/umd/scheduler.profiling.min.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,9 @@
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_UserBlockingPriority;
},
get unstable_sharedProfilingBuffer() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_getFirstCallbackNode;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question

},
});
});
124 changes: 109 additions & 15 deletions packages/scheduler/src/Scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@

/* eslint-disable no-var */

import {enableSchedulerDebugging} from './SchedulerFeatureFlags';
import {
requestHostCallback,
enableSchedulerDebugging,
enableSharedProfilingBuffer,
enableProfiling,
} from './SchedulerFeatureFlags';
import {
requestHostCallback as requestHostCallbackWithoutProfiling,
requestHostTimeout,
cancelHostCallback,
cancelHostTimeout,
shouldYieldToHost,
getCurrentTime,
Expand All @@ -21,11 +26,24 @@ import {
import {push, pop, peek} from './SchedulerMinHeap';

// TODO: Use symbols?
var ImmediatePriority = 1;
var UserBlockingPriority = 2;
var NormalPriority = 3;
var LowPriority = 4;
var IdlePriority = 5;
import {
ImmediatePriority,
UserBlockingPriority,
NormalPriority,
LowPriority,
IdlePriority,
} from './SchedulerPriorities';
import {
sharedProfilingBuffer,
markTaskRun,
markTaskYield,
markTaskCompleted,
markTaskCanceled,
markTaskErrored,
markSchedulerSuspended,
markSchedulerUnsuspended,
markTaskStart,
} from './SchedulerProfiling';

// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// Math.pow(2, 30) - 1
Expand Down Expand Up @@ -60,13 +78,36 @@ var isPerformingWork = false;
var isHostCallbackScheduled = false;
var isHostTimeoutScheduled = false;

function requestHostCallbackWithProfiling(cb) {
if (enableProfiling) {
markSchedulerSuspended();
requestHostCallbackWithoutProfiling(cb);
}
}

// Expose a shared array buffer that contains profiling information.
export const unstable_sharedProfilingBuffer =
enableProfiling && enableSharedProfilingBuffer ? sharedProfilingBuffer : null;

const requestHostCallback = enableProfiling
? requestHostCallbackWithProfiling
: requestHostCallbackWithoutProfiling;

function flushTask(task, callback, currentTime) {
currentPriorityLevel = task.priorityLevel;
var didUserCallbackTimeout = task.expirationTime <= currentTime;
markTaskRun(task);
var continuationCallback = callback(didUserCallbackTimeout);
return typeof continuationCallback === 'function'
? continuationCallback
: null;
if (typeof continuationCallback === 'function') {
markTaskYield(task);
return continuationCallback;
} else {
if (enableProfiling) {
markTaskCompleted(task);
task.isQueued = false;
}
return null;
}
}

function advanceTimers(currentTime) {
Expand All @@ -81,6 +122,10 @@ function advanceTimers(currentTime) {
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
if (enableProfiling) {
markTaskStart(timer);
timer.isQueued = true;
}
} else {
// Remaining timers are pending.
return;
Expand All @@ -107,6 +152,10 @@ function handleTimeout(currentTime) {
}

function flushWork(hasTimeRemaining, initialTime) {
if (isHostCallbackScheduled) {
markSchedulerUnsuspended();
}

// We'll need a host callback the next time work is scheduled.
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
Expand Down Expand Up @@ -152,6 +201,8 @@ function flushWork(hasTimeRemaining, initialTime) {
}
// Return whether there's additional work
if (currentTask !== null) {
markSchedulerSuspended();
isHostCallbackScheduled = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found this factoring very counterintuitive.

It's like Scheduler knows too much about the host config. It was surprising to see isHostCallbackScheduled = true here without any requests, and only later realize it "knows" host callback will be scheduled because it returned true, and it trusts host config to respect that.

There's similar implicitness in the error code path.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error code path seems inconsistent with what this code seems to imply.

The host config does schedule a callback in the error case (if this throws).
But the catch below doesn't have the corresponding lines:

            markSchedulerSuspended(currentTime);
            isHostCallbackScheduled = true;

Should it?

return true;
} else {
let firstTimer = peek(timerQueue);
Expand All @@ -160,6 +211,17 @@ function flushWork(hasTimeRemaining, initialTime) {
}
return false;
}
} catch (error) {
if (currentTask !== null) {
if (enableProfiling) {
markTaskErrored(currentTask);
currentTask.isQueued = false;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
throw error;
} finally {
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
Expand Down Expand Up @@ -250,6 +312,7 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {

var startTime;
var timeout;
var label;
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
Expand All @@ -261,6 +324,12 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
typeof options.timeout === 'number'
? options.timeout
: timeoutForPriorityLevel(priorityLevel);
if (enableProfiling) {
var _label = options.label;
if (typeof _label === 'string') {
label = _label;
}
}
} else {
timeout = timeoutForPriorityLevel(priorityLevel);
startTime = currentTime;
Expand All @@ -269,14 +338,21 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
var expirationTime = startTime + timeout;

var newTask = {
id: taskIdCounter++,
id: ++taskIdCounter,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};

if (enableProfiling) {
newTask.isQueued = false;
if (typeof options === 'object' && options !== null) {
newTask.label = label;
}
}

if (startTime > currentTime) {
// This is a delayed task.
newTask.sortIndex = startTime;
Expand All @@ -295,6 +371,10 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
} else {
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (enableProfiling) {
markTaskStart(newTask);
newTask.isQueued = true;
}
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
if (!isHostCallbackScheduled && !isPerformingWork) {
Expand Down Expand Up @@ -323,10 +403,24 @@ function unstable_getFirstCallbackNode() {
}

function unstable_cancelCallback(task) {
// Null out the callback to indicate the task has been canceled. (Can't remove
// from the queue because you can't remove arbitrary nodes from an array based
// heap, only the first one.)
task.callback = null;
if (enableProfiling && task.isQueued) {
markTaskCanceled(task);
task.isQueued = false;
}
if (task !== null && task === peek(taskQueue)) {
pop(taskQueue);
if (enableProfiling && !isPerformingWork && taskQueue.length === 0) {
// The queue is now empty.
markSchedulerUnsuspended();
isHostCallbackScheduled = false;
cancelHostCallback();
}
} else {
// Null out the callback to indicate the task has been canceled. (Can't
// remove from the queue because you can't remove arbitrary nodes from an
// array based heap, only the first one.)
task.callback = null;
}
}

function unstable_getCurrentPriorityLevel() {
Expand Down
3 changes: 3 additions & 0 deletions packages/scheduler/src/SchedulerFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ export const enableIsInputPending = false;
export const requestIdleCallbackBeforeFirstFrame = false;
export const requestTimerEventBeforeFirstFrame = false;
export const enableMessageLoopImplementation = false;
export const enableProfiling = __PROFILE__;
export const enableUserTimingAPI = false;
export const enableSharedProfilingBuffer = false;
18 changes: 18 additions & 0 deletions packages/scheduler/src/SchedulerPriorities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* 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
*/

export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;

// TODO: Use symbols?
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;
Loading