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

Lines changed: 919 additions & 66 deletions

‎.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
returnglobal.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
145145
.Scheduler.unstable_UserBlockingPriority;
146146
},
147+
getunstable_Profiling(){
148+
returnglobal.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
returnglobal.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
139139
.Scheduler.unstable_UserBlockingPriority;
140140
},
141+
getunstable_Profiling(){
142+
returnglobal.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
returnglobal.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
139139
.Scheduler.unstable_UserBlockingPriority;
140140
},
141+
getunstable_Profiling(){
142+
returnglobal.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-
varImmediatePriority=1;
25-
varUserBlockingPriority=2;
26-
varNormalPriority=3;
27-
varLowPriority=4;
28-
varIdlePriority=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
vartimerQueue=[];
4765

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

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

63-
functionflushTask(task,callback,currentTime){
64-
currentPriorityLevel=task.priorityLevel;
65-
vardidUserCallbackTimeout=task.expirationTime<=currentTime;
66-
varcontinuationCallback=callback(didUserCallbackTimeout);
67-
returntypeofcontinuationCallback==='function'
68-
? continuationCallback
69-
: null;
70-
}
71-
7281
functionadvanceTimers(currentTime){
7382
// Check for tasks that are no longer delayed and add them to the queue.
7483
lettimer=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
functionflushWork(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
constpreviousPriorityLevel=currentPriorityLevel;
120137
try{
121-
letcurrentTime=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-
constcallback=currentTask.callback;
136-
if(callback!==null){
137-
currentTask.callback=null;
138-
constcontinuation=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+
returnworkLoop(hasTimeRemaining,initialTime);
141+
}catch(error){
142+
if(currentTask!==null){
143+
constcurrentTime=getCurrentTime();
144+
markTaskErrored(currentTask,currentTime);
145+
currentTask.isQueued=false;
145146
}
146-
currentTime=getCurrentTime();
147-
advanceTimers(currentTime);
148-
}else{
149-
pop(taskQueue);
147+
throwerror;
150148
}
151-
currentTask=peek(taskQueue);
152-
}
153-
// Return whether there's additional work
154-
if(currentTask!==null){
155-
returntrue;
156149
}else{
157-
letfirstTimer=peek(timerQueue);
158-
if(firstTimer!==null){
159-
requestHostTimeout(handleTimeout,firstTimer.startTime-currentTime);
160-
}
161-
returnfalse;
150+
// No catch in prod codepath.
151+
returnworkLoop(hasTimeRemaining,initialTime);
162152
}
163153
}finally{
164154
currentTask=null;
165155
currentPriorityLevel=previousPriorityLevel;
166156
isPerformingWork=false;
157+
if(enableProfiling){
158+
constcurrentTime=getCurrentTime();
159+
markSchedulerSuspended(currentTime);
160+
}
161+
}
162+
}
163+
164+
functionworkLoop(hasTimeRemaining,initialTime){
165+
letcurrentTime=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+
constcallback=currentTask.callback;
180+
if(callback!==null){
181+
currentTask.callback=null;
182+
currentPriorityLevel=currentTask.priorityLevel;
183+
constdidUserCallbackTimeout=currentTask.expirationTime<=currentTime;
184+
markTaskRun(currentTask,currentTime);
185+
constcontinuationCallback=callback(didUserCallbackTimeout);
186+
currentTime=getCurrentTime();
187+
if(typeofcontinuationCallback==='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+
returntrue;
208+
}else{
209+
letfirstTimer=peek(timerQueue);
210+
if(firstTimer!==null){
211+
requestHostTimeout(handleTimeout,firstTimer.startTime-currentTime);
212+
}
213+
returnfalse;
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
functionunstable_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+
constcurrentTime=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
getCurrentTimeasunstable_now,
371433
forceFrameRateasunstable_forceFrameRate,
372434
};
435+
436+
exportconstunstable_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
exportconstrequestIdleCallbackBeforeFirstFrame=false;
1212
exportconstrequestTimerEventBeforeFirstFrame=false;
1313
exportconstenableMessageLoopImplementation=false;
14+
exportconstenableProfiling=__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+
exporttypePriorityLevel=0|1|2|3|4|5;
11+
12+
// TODO: Use symbols?
13+
exportconstNoPriority=0;
14+
exportconstImmediatePriority=1;
15+
exportconstUserBlockingPriority=2;
16+
exportconstNormalPriority=3;
17+
exportconstLowPriority=4;
18+
exportconstIdlePriority=5;

0 commit comments

Comments
 (0)