Skip to content

Commit d2d9b1f

Browse files
authored
[Scheduler] Support inferring priority from stack (facebook#16105)
When executing a task, wraps the callback in an extra function whose name includes the current priority level. Profiling tools with access to function names can use this to determine the priority of the task.
1 parent 48f6594 commit d2d9b1f

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

packages/scheduler/src/Scheduler.js

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ var isPerformingWork = false;
5656
var isHostCallbackScheduled = false;
5757
var isHostTimeoutScheduled = false;
5858

59+
function scheduler_flushTaskAtPriority_Immediate(callback, didTimeout) {
60+
return callback(didTimeout);
61+
}
62+
function scheduler_flushTaskAtPriority_UserBlocking(callback, didTimeout) {
63+
return callback(didTimeout);
64+
}
65+
function scheduler_flushTaskAtPriority_Normal(callback, didTimeout) {
66+
return callback(didTimeout);
67+
}
68+
function scheduler_flushTaskAtPriority_Low(callback, didTimeout) {
69+
return callback(didTimeout);
70+
}
71+
function scheduler_flushTaskAtPriority_Idle(callback, didTimeout) {
72+
return callback(didTimeout);
73+
}
74+
5975
function flushTask(task, currentTime) {
6076
// Remove the task from the list before calling the callback. That way the
6177
// list is in a consistent state even if the callback throws.
@@ -83,7 +99,40 @@ function flushTask(task, currentTime) {
8399
var continuationCallback;
84100
try {
85101
var didUserCallbackTimeout = task.expirationTime <= currentTime;
86-
continuationCallback = callback(didUserCallbackTimeout);
102+
// Add an extra function to the callstack. Profiling tools can use this
103+
// to infer the priority of work that appears higher in the stack.
104+
switch (currentPriorityLevel) {
105+
case ImmediatePriority:
106+
continuationCallback = scheduler_flushTaskAtPriority_Immediate(
107+
callback,
108+
didUserCallbackTimeout,
109+
);
110+
break;
111+
case UserBlockingPriority:
112+
continuationCallback = scheduler_flushTaskAtPriority_UserBlocking(
113+
callback,
114+
didUserCallbackTimeout,
115+
);
116+
break;
117+
case NormalPriority:
118+
continuationCallback = scheduler_flushTaskAtPriority_Normal(
119+
callback,
120+
didUserCallbackTimeout,
121+
);
122+
break;
123+
case LowPriority:
124+
continuationCallback = scheduler_flushTaskAtPriority_Low(
125+
callback,
126+
didUserCallbackTimeout,
127+
);
128+
break;
129+
case IdlePriority:
130+
continuationCallback = scheduler_flushTaskAtPriority_Idle(
131+
callback,
132+
didUserCallbackTimeout,
133+
);
134+
break;
135+
}
87136
} catch (error) {
88137
throw error;
89138
} finally {

packages/scheduler/src/__tests__/Scheduler-test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ let runWithPriority;
1414
let ImmediatePriority;
1515
let UserBlockingPriority;
1616
let NormalPriority;
17+
let LowPriority;
18+
let IdlePriority;
1719
let scheduleCallback;
1820
let cancelCallback;
1921
let wrapCallback;
@@ -31,6 +33,8 @@ describe('Scheduler', () => {
3133
ImmediatePriority = Scheduler.unstable_ImmediatePriority;
3234
UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
3335
NormalPriority = Scheduler.unstable_NormalPriority;
36+
LowPriority = Scheduler.unstable_LowPriority;
37+
IdlePriority = Scheduler.unstable_IdlePriority;
3438
scheduleCallback = Scheduler.unstable_scheduleCallback;
3539
cancelCallback = Scheduler.unstable_cancelCallback;
3640
wrapCallback = Scheduler.unstable_wrapCallback;
@@ -414,6 +418,73 @@ describe('Scheduler', () => {
414418
]);
415419
});
416420

421+
if (__DEV__) {
422+
// Function names are minified in prod, though you could still infer the
423+
// priority if you have sourcemaps.
424+
it('adds extra function to the JS stack whose name includes the priority level', () => {
425+
function inferPriorityFromCallstack() {
426+
try {
427+
throw Error();
428+
} catch (e) {
429+
const stack = e.stack;
430+
const lines = stack.split('\n');
431+
for (let i = lines.length - 1; i >= 0; i--) {
432+
const line = lines[i];
433+
const found = line.match(
434+
/scheduler_flushTaskAtPriority_([A-Za-z]+)/,
435+
);
436+
if (found !== null) {
437+
const priorityStr = found[1];
438+
switch (priorityStr) {
439+
case 'Immediate':
440+
return ImmediatePriority;
441+
case 'UserBlocking':
442+
return UserBlockingPriority;
443+
case 'Normal':
444+
return NormalPriority;
445+
case 'Low':
446+
return LowPriority;
447+
case 'Idle':
448+
return IdlePriority;
449+
}
450+
}
451+
}
452+
return null;
453+
}
454+
}
455+
456+
scheduleCallback(ImmediatePriority, () =>
457+
Scheduler.unstable_yieldValue(
458+
'Immediate: ' + inferPriorityFromCallstack(),
459+
),
460+
);
461+
scheduleCallback(UserBlockingPriority, () =>
462+
Scheduler.unstable_yieldValue(
463+
'UserBlocking: ' + inferPriorityFromCallstack(),
464+
),
465+
);
466+
scheduleCallback(NormalPriority, () =>
467+
Scheduler.unstable_yieldValue(
468+
'Normal: ' + inferPriorityFromCallstack(),
469+
),
470+
);
471+
scheduleCallback(LowPriority, () =>
472+
Scheduler.unstable_yieldValue('Low: ' + inferPriorityFromCallstack()),
473+
);
474+
scheduleCallback(IdlePriority, () =>
475+
Scheduler.unstable_yieldValue('Idle: ' + inferPriorityFromCallstack()),
476+
);
477+
478+
expect(Scheduler).toFlushAndYield([
479+
'Immediate: ' + ImmediatePriority,
480+
'UserBlocking: ' + UserBlockingPriority,
481+
'Normal: ' + NormalPriority,
482+
'Low: ' + LowPriority,
483+
'Idle: ' + IdlePriority,
484+
]);
485+
});
486+
}
487+
417488
describe('delayed tasks', () => {
418489
it('schedules a delayed task', () => {
419490
scheduleCallback(

0 commit comments

Comments
 (0)