1 of 34

API for

Main Thread Scheduling

2 of 34

Example app: Search-as-you-type

Lot of deadlines to juggle:

  • being responsive to user input: typing in search-box
  • consistently rendering smooth animations on the page
  • fetching, preparing and updating search results must also progress quickly

3 of 34

Main Thread Contention

Script is the most frequent source of user responsiveness issues.

There is a LOT of script in modern web apps.

It is difficult for web developers to build applications that are (and remain) responsive.

4 of 34

5 of 34

Long script makes it easy to miss deadlines

Guidelines for smooth interactions:

Typing is input and should occur within 10ms

Results are interaction response and should take < 100ms

Rendering work < 16ms (~8ms)]

Need Priorities.

6 of 34

A strategy for Guarantees of Responsiveness

Do work in chunks to avoid blocking.

Give chunks different priorities.

We need something that knows about our tasks, and executes them at the best time -- a Scheduler.

7 of 34

Prior Art

Userspace Schedulers

8 of 34

Scheduling has proven to result in:

better guarantees of responsiveness.

9 of 34

Maps Scheduler

Maps manages multiple types of interactions and events which can happen concurrently

Loading results shouldn’t impact the performance of panning the map

Loading map tiles while panning shouldn’t jank the pan gesture

They do this by scheduling all work and giving higher priority to input response tasks (user-interactive tasks)

10 of 34

Maps Scheduler

  • uses rAF to run the scheduler
  • High priority task types are executed synchronously within rAF
  • “Other” task type
    • executed via “nextTick”
    • 6 priority levels
  • “nextTick”: Yields to browser using messagechannel to message itself in rAF

11 of 34

React Scheduler (source)

  • 4 task priority levels:
    • ImmediatePriority, UserBlockingPriority, NormalPriority, IdlePriority
  • expiration times mapping to priority
  • enables dynamic adjustment: something that starts as low priority gets higher as it approaches the deadline. Expired tasks are the most important.

Uses rAF to run the scheduler and yield (with postmessage). Heading towards not yielding (and not using rAF) when sufficient signals are available..

12 of 34

React Scheduler: “idletick”

  • rAF posts postMessage, within postMessage handler:
    • do as much work as possible until “time + frame rate”
    • execute expired tasks without yielding
    • keep executing callbacks until out of time
    • before exiting, flush all the “immediate priority” callbacks

13 of 34

Ember Framework

created "microtask priority queue" with 3 buckets:

  • 1. data flow
    • eg. modified text can affect rendering - so ordered before; data is “input into rendering”
  • 2. rendering
  • 3. destructors
    • eg. components that were removed in rendering, want to later run their dtors

14 of 34

Ember Framework

Not yet yielding to the browser.

Concerned about other un-prioritized work eg. xhr, browser initiated callbacks, GC etc.

15 of 34

What do these Schedulers have in common?

What are the building blocks?

16 of 34

What it takes to build a Scheduler?

  • 1. Set of tasks with priority
  • 2. “virtual” task-queues for managing groups of tasks
  • 3. API for posting tasks
  • 4. run-loop
    • mechanism to execute tasks at an appropriate time, relative to the current state of the user and the browser
    • (React & Maps use rAF for this currently)

17 of 34

What does the run-loop need?

run-loop requires knowledge of:

  • rendering
    • timing of next frame
    • time budget within current frame
  • input
    • is input pending
    • how long do we have before input should be serviced
  • loading, navigation (including SPA nav)

18 of 34

What does the run-loop need?

Ideally run-loop can effectively coordinate with other work on the main thread:

  • fetches and network responses
  • browser initiated callbacks: eg. onreadystatechange in xhr, post-message from worker etc
  • browser’s internal work: eg. GC
  • rendering: tasks may be reprioritized dependent on renderer state
  • other developer scheduled callbacks: eg. settimeout

19 of 34

Coordination with Rendering pipeline

Next Frame

rAFs

Document Lifecycle

(Style, Layout, Paint)

idle scripting

(best effort)

Available today:

input handler, rAF, rIC

settimeout, postmessage etc.

what happens here is undefined

20 of 34

Fear of Yielding

Since random things can run between frames, JS schedulers are motivated to not yield.

Maybe this is okay if they have enough signals to make a good decision?

At the least need: isInputPending, isRenderPending

21 of 34

Funny Thing: Platform’s event-loop is essentially a run-loop and can schedule JS :)

Maybe just use the platform directly without JS scheduler?

22 of 34

What’s needed

input

handlers

Next Frame

rAFs

Document Lifecycle

(Style, Layout, Paint)

idle scripting

User-blocking

work

(must fit current frame)

Default

tasks

(prepare for next frame)

23 of 34

“user blocking” priority

  • important work -- must fit within current frame i.e. must be small
  • work within input handlers
    • provide the user immediate acknowledgement of their interation, eg. toggling the like button, starting some work when clicking on a comment list etc.
  • work within rAF
  • spec aligning on vsync-aligned-input helps predictability
    • input event handlers run immediately before rAF handlers, without letting other JS run in between.

24 of 34

“default” priority

  • lower priority than user-blocking
  • work for preparing for next frame or future rendering
    • eg. fetching maps tiles
  • guaranteed to run before next frame
  • need clarity on priority relative to
    • other browser initiated callbacks
    • fetch responses, other browser work

25 of 34

Alternatives for “default” priority work

  • rAF
    • is for rendering related work for “current frame”
    • rAF is too late and too little time - to prepare for next frame
    • rAF invokes full rendering machinery
      • expensive side-effects (vsync, thread hops, etc.) that should be avoided unless you’re actually touching the DOM
  • rIC
    • rIC is too unreliable for preparing for next frame
    • prone to starvation
    • maybe enhance rIC?
  • settimeout(0)
    • prone to clamping

26 of 34

Option A:

Built-in platform scheduler

27 of 34

Potential API (link to sketch)

Standardize on input handlers timing.

Expose a unified API.

Expose semantic priority for tasks:

  • (microtask)
  • user-blocking
  • default
  • idle

28 of 34

Default task queues

function mytask() {

...

}

myQueue = TaskQueue.default("user-blocking");

taskId = myQueue.postTask(myTask, <list of args>);

29 of 34

User defined task queues

myQueue = new TaskQueue(‘myCustomQueue’, "default");

myQueue.postTask(task, <list of args for task>);

User-defined task queues enables the app to manage a group of tasks:

  • updating priority: myQueue.updatePriority(<new priority>);
  • canceling
  • flushing the queue when the page is hidden: myQueue.flush();

30 of 34

Option B:

“standardized” JS scheduler

31 of 34

JS Scheduler

  • Address the “fear of yielding” with a good story
  • Expose isInputPending, isRenderPending
  • Plug the gaps:
    • var handle = window.requestDefaultCallback(callback[, options])

32 of 34

Important Issues

to be tackled

33 of 34

Top Issues cited by schedulers

  • A way for promises to yield: i.e. post as macro-task at given priority
  • Priorities for network fetches and for handling network responses
  • API for frame rate throttling: i.e. 30 vs. 60 fps

34 of 34

Thoughts? Ideas?