This document provides an overview of task scheduling in Blink and Chrome’s renderer process and outlines best practises for posting tasks.
Most of Blink is essentially single-threaded: most important things happen on the main thread (including JavaScript execution, DOM, CSS, layout calculations), which means that there are many things which want to run on the main thread at the same time. Therefore Blink needs a scheduling policy to prioritise the right thing — for example, to schedule input handling above everything else.
The majority of the scheduling logic deals explicitly with main thread scheduling and this document assumes that we are talking about the main thread unless stated otherwise. If you don’t need DOM access, please refer to the off main thread scheduling section to find out how to schedule this type of work.
The main scheduling unit in Blink is a task. A task is a base::OnceClosure
posted via TaskRunner::PostTask
or TaskRunner::PostDelayedTask
interface. The regular method of creating closures, base::BindOnce/Repeating
, is banned. Blink should use one of the followings:
WTF::Bind
: for tasks which are posted to the same thread.CrossThreadBind
: for tasks which are posted to a different thread.At the moment Blink Scheduler treats tasks as an atomic unit — if a task has started, it can’t be interrupted until it completes. The scheduler can only choose a new task to run from the eligible tasks or can elect not to run any task at all.
In order to post a task, a task runner reference is needed. Almost all main thread tasks should be associated with a frame to allow the scheduler to freeze or prioritise individual frames. This is a hard requirement backed by a DCHECK
that a task running JavaScript should have this association (which is being introduced).
FrameScheduler::GetTaskRunner
(or its aliases LocalFrame::GetTaskRunner
, WebLocalFrame::GetTaskRunner
, RenderFrame::GetTaskRunner
or ExecutionContext::GetTaskRunner
) should be used for that. They return a task runner which continues to run tasks after the frame is detached.
Some tasks can’t be associated with a particular frame. One example is garbage collection which interacts with all JavaScript heaps in the isolate. For these use-cases a named per-thread task runner should be used:
content/
: blink::scheduler::WebThreadScheduler::Current()->SpecificTaskRunner()
blink/
: blink::scheduler::ThreadScheduler::Current()->SpecificTaskRunner()
Per-thread task runners include:
New task runners might be added in the future; contact the team if you think you need a new one.
SingleThreadTaskRunner::GetCurrentDefault
base::SingleThreadTaskRunner::GetCurrentDefault
is a way to get a task runner and it returns a default task runner for a thread. Because tasks posted to it lack any attribution, the scheduler can’t properly schedule and prioritise them.
base::SingleThreadTaskRunner::GetCurrentDefault
usages are banned in blink/
and content/renderer/
directories and strongly discouraged elsewhere in the renderer process.
Please help us to convert them to the appropriate task runner (usually per-frame one). See Task Type Usage Guideline for more details.
In addition to frame association, the scheduler also needs to know the nature of the task to correctly handle it. blink::TaskType
encodes this information. TaskType
is a required parameter of all GetTaskRunner()
methods and FrameScheduler
returns an appropriate task runner based on the TaskType
.
All tasks mentioned in the spec should have task source explicitly defined (e.g. see generic task sources definition in the spec). There are still some places where the task source is not mentioned explicitly — reach out to domenic@ and garykac@ for advice.
For the non-speced tasks (for example, clean up caches or record metrics. Note that these tasks shouldn’t run JavaScript), kInternal*
task types should be used.
If you’re happy with the default scheduling policies, which should happen in the majority of cases, kInternalDefault
task type should be used. Otherwise, reach out to the team to discuss adding new task type for your needs.
The scheduler selects the next task to run based on the priority (modulo some starvation logic). The tasks with the same priority run in order.
There are following rules to assign priorities:
The default priority is normal.
During synchronous dialogs (alert()
or print()
) or inside v8 debugger breakpoints scheduler enters a nested run loop and stops running pauseable tasks. Specifically, no JavaScript can run when a page is paused.
The pausing is triggered by ScopedPagePauser
. Almost all tasks can be paused.
Scheduler may elect not to run some tasks when processing user gestures in order to increase responsiveness.
Scheduler defers tasks for two seconds after a user gesture, as it’s very likely that another gesture will arrive soon. The majority of tasks can be deferred.
Most of the tasks are deferrable.
Scheduler freezes background pages in order to increase responsiveness of the foreground tabs and save power.
On mobile all pages are frozen after five minutes in the background. On desktop only eligible pages are frozen, which is determined by heuristics based on the APIs page is using.
Most of the tasks are freezable.
Scheduler delays tasks in the background pages and offscreen frames in order to improve responsiveness of the foreground pages without breaking useful background page functionality.
At the moment only JavaScript timers (setTimeout
/setInterval
) are throttleable. While there is a general desire to expand this list, this is a low priority effort as we are focused on making freezing better instead.
If your task doesn’t have to run on the main thread, use worker_pool::PostTask
, which uses a thread pool behind the scenes.
Do not create your own dedicated thread if you need ordering for your tasks, use worker_pool::CreateTaskRunner
instead — this creates a sequence (virtual thread which can run tasks in order on any of the threads in the thread pool).
See also Threading and Tasks in Chrome for more details.
Many data structures in Blink are bound to a particular thread (e.g. Strings, garbage-collected classes, etc), so it’s not safe to pass a pointer to them to another thread. To enforce this, base::Bind
is banned in Blink and WTF::Bind
and CrossThreadBind
are provided as alternatives. WTF::Bind
should be used to post tasks to the same thread and closures returned by it DCHECK that they run on the same thread. CrossThreadBind
applies CrossThreadCopier
to its arguments and creates a deep copy, so the resulting closure can run on a different thread.