[Scheduler] Support inferring priority from stack (#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.
Andrew Clark committed
Jul 10, 2019 at 18:59 UTC
d2d9b1f70112b0ce02b0db6a27861186c16116f4
2 files changed
+121
-1
packages/scheduler/src/Scheduler.js
+50
-1
@@ -56,6 +56,22 @@ var isPerformingWork = false;
56
var isHostCallbackScheduled = false;
57
var isHostTimeoutScheduled = false;
58
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
+
75
function flushTask(task, currentTime) {
76
// Remove the task from the list before calling the callback. That way the
77
// list is in a consistent state even if the callback throws.
@@ -83,7 +99,40 @@ function flushTask(task, currentTime) {
99
var continuationCallback;
100
try {
101
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
+ }
136
} catch (error) {
137
throw error;
138
} finally {
packages/scheduler/src/__tests__/Scheduler-test.js
+71
@@ -14,6 +14,8 @@ let runWithPriority;
14
let ImmediatePriority;
15
let UserBlockingPriority;
16
let NormalPriority;
17
+let LowPriority;
18
+let IdlePriority;
19
let scheduleCallback;
20
let cancelCallback;
21
let wrapCallback;
@@ -31,6 +33,8 @@ describe('Scheduler', () => {
33
ImmediatePriority = Scheduler.unstable_ImmediatePriority;
34
UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
35
NormalPriority = Scheduler.unstable_NormalPriority;
36
+ LowPriority = Scheduler.unstable_LowPriority;
37
+ IdlePriority = Scheduler.unstable_IdlePriority;
38
scheduleCallback = Scheduler.unstable_scheduleCallback;
39
cancelCallback = Scheduler.unstable_cancelCallback;
40
wrapCallback = Scheduler.unstable_wrapCallback;
@@ -414,6 +418,73 @@ describe('Scheduler', () => {
418
]);
419
});
420
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
+
488
describe('delayed tasks', () => {
489
it('schedules a delayed task', () => {
490
scheduleCallback(