Skip to content

Commit 0f6e3cd

Browse files
gaearonsebmarkbage
authored andcommitted
[Scheduler] Profiler Features (second try) (#16542)
* Revert "Revert "[Scheduler] Profiling features (#16145)" (#16392)" This reverts commit 4ba1412. * Fix copy paste mistake * Remove init path dependency on ArrayBuffer * Add a regression test for cancelling multiple tasks * Prevent deopt from adding isQueued later * Remove pop() calls that were added for profiling * Verify that Suspend/Unsuspend events match up in tests This currently breaks tests. * Treat Suspend and Resume as exiting and entering work loop Their definitions used to be more fuzzy. For example, Suspend didn't always fire on exit, and sometimes fired when we did _not_ exit (such as at task enqueue). I chatted to Boone, and he's saying treating Suspend and Resume as strictly exiting and entering the loop is fine for their use case. * Revert "Prevent deopt from adding isQueued later" This reverts commit 9c30b0b. Unnecessary because GCC * Start counter with 1 * Group exports into unstable_Profiling namespace * No catch in PROD codepath * No label TODO * No null checks
1 parent 474b650 commit 0f6e3cd

18 files changed

+919
-66
lines changed

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ module.exports = {
142142
],
143143

144144
globals: {
145+
SharedArrayBuffer: true,
146+
145147
spyOnDev: true,
146148
spyOnDevAndProd: true,
147149
spyOnProd: true,

packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ describe('ReactDebugFiberPerf', () => {
136136
require('shared/ReactFeatureFlags').enableProfilerTimer = false;
137137
require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
138138
require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
139+
require('scheduler/src/SchedulerFeatureFlags').enableProfiling = false;
139140

140141
// Import after the polyfill is set up:
141142
React = require('react');

packages/scheduler/npm/umd/scheduler.development.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,5 +144,9 @@
144144
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
145145
.Scheduler.unstable_UserBlockingPriority;
146146
},
147+
get unstable_Profiling() {
148+
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
149+
.Scheduler.unstable_Profiling;
150+
},
147151
});
148152
});

packages/scheduler/npm/umd/scheduler.production.min.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,9 @@
138138
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
139139
.Scheduler.unstable_UserBlockingPriority;
140140
},
141+
get unstable_Profiling() {
142+
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
143+
.Scheduler.unstable_Profiling;
144+
},
141145
});
142146
});

packages/scheduler/npm/umd/scheduler.profiling.min.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,9 @@
138138
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
139139
.Scheduler.unstable_UserBlockingPriority;
140140
},
141+
get unstable_Profiling() {
142+
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
143+
.Scheduler.unstable_Profiling;
144+
},
141145
});
142146
});

packages/scheduler/src/Scheduler.js

Lines changed: 127 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
/* eslint-disable no-var */
1010

11-
import {enableSchedulerDebugging} from './SchedulerFeatureFlags';
11+
import {
12+
enableSchedulerDebugging,
13+
enableProfiling,
14+
} from './SchedulerFeatureFlags';
1215
import {
1316
requestHostCallback,
1417
requestHostTimeout,
@@ -21,11 +24,26 @@ import {
2124
import {push, pop, peek} from './SchedulerMinHeap';
2225

2326
// TODO: Use symbols?
24-
var ImmediatePriority = 1;
25-
var UserBlockingPriority = 2;
26-
var NormalPriority = 3;
27-
var LowPriority = 4;
28-
var IdlePriority = 5;
27+
import {
28+
ImmediatePriority,
29+
UserBlockingPriority,
30+
NormalPriority,
31+
LowPriority,
32+
IdlePriority,
33+
} from './SchedulerPriorities';
34+
import {
35+
sharedProfilingBuffer,
36+
markTaskRun,
37+
markTaskYield,
38+
markTaskCompleted,
39+
markTaskCanceled,
40+
markTaskErrored,
41+
markSchedulerSuspended,
42+
markSchedulerUnsuspended,
43+
markTaskStart,
44+
stopLoggingProfilingEvents,
45+
startLoggingProfilingEvents,
46+
} from './SchedulerProfiling';
2947

3048
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
3149
// Math.pow(2, 30) - 1
@@ -46,7 +64,7 @@ var taskQueue = [];
4664
var timerQueue = [];
4765

4866
// Incrementing id counter. Used to maintain insertion order.
49-
var taskIdCounter = 0;
67+
var taskIdCounter = 1;
5068

5169
// Pausing the scheduler is useful for debugging.
5270
var isSchedulerPaused = false;
@@ -60,15 +78,6 @@ var isPerformingWork = false;
6078
var isHostCallbackScheduled = false;
6179
var isHostTimeoutScheduled = false;
6280

63-
function flushTask(task, callback, currentTime) {
64-
currentPriorityLevel = task.priorityLevel;
65-
var didUserCallbackTimeout = task.expirationTime <= currentTime;
66-
var continuationCallback = callback(didUserCallbackTimeout);
67-
return typeof continuationCallback === 'function'
68-
? continuationCallback
69-
: null;
70-
}
71-
7281
function advanceTimers(currentTime) {
7382
// Check for tasks that are no longer delayed and add them to the queue.
7483
let timer = peek(timerQueue);
@@ -81,6 +90,10 @@ function advanceTimers(currentTime) {
8190
pop(timerQueue);
8291
timer.sortIndex = timer.expirationTime;
8392
push(taskQueue, timer);
93+
if (enableProfiling) {
94+
markTaskStart(timer);
95+
timer.isQueued = true;
96+
}
8497
} else {
8598
// Remaining timers are pending.
8699
return;
@@ -107,6 +120,10 @@ function handleTimeout(currentTime) {
107120
}
108121

109122
function flushWork(hasTimeRemaining, initialTime) {
123+
if (enableProfiling) {
124+
markSchedulerUnsuspended(initialTime);
125+
}
126+
110127
// We'll need a host callback the next time work is scheduled.
111128
isHostCallbackScheduled = false;
112129
if (isHostTimeoutScheduled) {
@@ -118,52 +135,82 @@ function flushWork(hasTimeRemaining, initialTime) {
118135
isPerformingWork = true;
119136
const previousPriorityLevel = currentPriorityLevel;
120137
try {
121-
let currentTime = initialTime;
122-
advanceTimers(currentTime);
123-
currentTask = peek(taskQueue);
124-
while (
125-
currentTask !== null &&
126-
!(enableSchedulerDebugging && isSchedulerPaused)
127-
) {
128-
if (
129-
currentTask.expirationTime > currentTime &&
130-
(!hasTimeRemaining || shouldYieldToHost())
131-
) {
132-
// This currentTask hasn't expired, and we've reached the deadline.
133-
break;
134-
}
135-
const callback = currentTask.callback;
136-
if (callback !== null) {
137-
currentTask.callback = null;
138-
const continuation = flushTask(currentTask, callback, currentTime);
139-
if (continuation !== null) {
140-
currentTask.callback = continuation;
141-
} else {
142-
if (currentTask === peek(taskQueue)) {
143-
pop(taskQueue);
144-
}
138+
if (enableProfiling) {
139+
try {
140+
return workLoop(hasTimeRemaining, initialTime);
141+
} catch (error) {
142+
if (currentTask !== null) {
143+
const currentTime = getCurrentTime();
144+
markTaskErrored(currentTask, currentTime);
145+
currentTask.isQueued = false;
145146
}
146-
currentTime = getCurrentTime();
147-
advanceTimers(currentTime);
148-
} else {
149-
pop(taskQueue);
147+
throw error;
150148
}
151-
currentTask = peek(taskQueue);
152-
}
153-
// Return whether there's additional work
154-
if (currentTask !== null) {
155-
return true;
156149
} else {
157-
let firstTimer = peek(timerQueue);
158-
if (firstTimer !== null) {
159-
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
160-
}
161-
return false;
150+
// No catch in prod codepath.
151+
return workLoop(hasTimeRemaining, initialTime);
162152
}
163153
} finally {
164154
currentTask = null;
165155
currentPriorityLevel = previousPriorityLevel;
166156
isPerformingWork = false;
157+
if (enableProfiling) {
158+
const currentTime = getCurrentTime();
159+
markSchedulerSuspended(currentTime);
160+
}
161+
}
162+
}
163+
164+
function workLoop(hasTimeRemaining, initialTime) {
165+
let currentTime = initialTime;
166+
advanceTimers(currentTime);
167+
currentTask = peek(taskQueue);
168+
while (
169+
currentTask !== null &&
170+
!(enableSchedulerDebugging && isSchedulerPaused)
171+
) {
172+
if (
173+
currentTask.expirationTime > currentTime &&
174+
(!hasTimeRemaining || shouldYieldToHost())
175+
) {
176+
// This currentTask hasn't expired, and we've reached the deadline.
177+
break;
178+
}
179+
const callback = currentTask.callback;
180+
if (callback !== null) {
181+
currentTask.callback = null;
182+
currentPriorityLevel = currentTask.priorityLevel;
183+
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
184+
markTaskRun(currentTask, currentTime);
185+
const continuationCallback = callback(didUserCallbackTimeout);
186+
currentTime = getCurrentTime();
187+
if (typeof continuationCallback === 'function') {
188+
currentTask.callback = continuationCallback;
189+
markTaskYield(currentTask, currentTime);
190+
} else {
191+
if (enableProfiling) {
192+
markTaskCompleted(currentTask, currentTime);
193+
currentTask.isQueued = false;
194+
}
195+
if (currentTask === peek(taskQueue)) {
196+
pop(taskQueue);
197+
}
198+
}
199+
advanceTimers(currentTime);
200+
} else {
201+
pop(taskQueue);
202+
}
203+
currentTask = peek(taskQueue);
204+
}
205+
// Return whether there's additional work
206+
if (currentTask !== null) {
207+
return true;
208+
} else {
209+
let firstTimer = peek(timerQueue);
210+
if (firstTimer !== null) {
211+
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
212+
}
213+
return false;
167214
}
168215
}
169216

@@ -276,6 +323,9 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
276323
expirationTime,
277324
sortIndex: -1,
278325
};
326+
if (enableProfiling) {
327+
newTask.isQueued = false;
328+
}
279329

280330
if (startTime > currentTime) {
281331
// This is a delayed task.
@@ -295,6 +345,10 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
295345
} else {
296346
newTask.sortIndex = expirationTime;
297347
push(taskQueue, newTask);
348+
if (enableProfiling) {
349+
markTaskStart(newTask, currentTime);
350+
newTask.isQueued = true;
351+
}
298352
// Schedule a host callback, if needed. If we're already performing work,
299353
// wait until the next time we yield.
300354
if (!isHostCallbackScheduled && !isPerformingWork) {
@@ -323,9 +377,17 @@ function unstable_getFirstCallbackNode() {
323377
}
324378

325379
function unstable_cancelCallback(task) {
326-
// Null out the callback to indicate the task has been canceled. (Can't remove
327-
// from the queue because you can't remove arbitrary nodes from an array based
328-
// heap, only the first one.)
380+
if (enableProfiling) {
381+
if (task.isQueued) {
382+
const currentTime = getCurrentTime();
383+
markTaskCanceled(task, currentTime);
384+
task.isQueued = false;
385+
}
386+
}
387+
388+
// Null out the callback to indicate the task has been canceled. (Can't
389+
// remove from the queue because you can't remove arbitrary nodes from an
390+
// array based heap, only the first one.)
329391
task.callback = null;
330392
}
331393

@@ -370,3 +432,11 @@ export {
370432
getCurrentTime as unstable_now,
371433
forceFrameRate as unstable_forceFrameRate,
372434
};
435+
436+
export const unstable_Profiling = enableProfiling
437+
? {
438+
startLoggingProfilingEvents,
439+
stopLoggingProfilingEvents,
440+
sharedProfilingBuffer,
441+
}
442+
: null;

packages/scheduler/src/SchedulerFeatureFlags.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export const enableIsInputPending = false;
1111
export const requestIdleCallbackBeforeFirstFrame = false;
1212
export const requestTimerEventBeforeFirstFrame = false;
1313
export const enableMessageLoopImplementation = false;
14+
export const enableProfiling = __PROFILE__;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
11+
12+
// TODO: Use symbols?
13+
export const NoPriority = 0;
14+
export const ImmediatePriority = 1;
15+
export const UserBlockingPriority = 2;
16+
export const NormalPriority = 3;
17+
export const LowPriority = 4;
18+
export const IdlePriority = 5;

0 commit comments

Comments
 (0)