Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 1 | # Threading and Tasks in Chrome - FAQ |
| 2 | |
| 3 | [TOC] |
| 4 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 5 | Note: Make sure to read the main [Threading and Tasks](threading_and_tasks.md) |
| 6 | docs first. |
| 7 | |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 8 | ## General |
| 9 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 10 | ### On which thread will a task run? |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 11 | |
Gabriel Charette | 9b6c0407 | 2022-04-01 23:22:46 | [diff] [blame] | 12 | A task is posted through the `base/task/thread_pool.h` API with `TaskTraits`. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 13 | |
| 14 | * If `TaskTraits` contain `BrowserThread::UI`: |
| 15 | * The task runs on the main thread. |
| 16 | |
| 17 | * If `TaskTraits` contain `BrowserThread::IO`: |
| 18 | * The task runs on the IO thread. |
| 19 | |
| 20 | * If `TaskTraits` don't contain `BrowserThread::UI/IO`: |
| 21 | * If the task is posted through a `SingleThreadTaskRunner` obtained from |
Sami Kyostila | 831c60b | 2019-07-31 13:31:23 | [diff] [blame] | 22 | `CreateSingleThreadTaskRunner(..., mode)`: |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 23 | * Where `mode` is `SingleThreadTaskRunnerThreadMode::DEDICATED`: |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 24 | * The task runs on a thread that only runs tasks from that |
| 25 | SingleThreadTaskRunner. This is not the main thread nor the IO |
| 26 | thread. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 27 | |
| 28 | * Where `mode` is `SingleThreadTaskRunnerThreadMode::SHARED`: |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 29 | * The task runs on a thread that runs tasks from one or many |
| 30 | unrelated SingleThreadTaskRunners. This is not the main thread nor |
| 31 | the IO thread. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 32 | |
| 33 | * Otherwise: |
| 34 | * The task runs in a thread pool. |
| 35 | |
| 36 | As explained in [Prefer Sequences to Threads](threading_and_tasks.md#Prefer-Sequences-to-Threads), |
| 37 | tasks should generally run on a sequence in a thread pool rather than on a |
| 38 | dedicated thread. |
| 39 | |
Francois Doray | 571f85a9 | 2019-02-01 17:02:16 | [diff] [blame] | 40 | ### Does release of a TaskRunner block on posted tasks? |
| 41 | |
| 42 | Releasing a TaskRunner reference does not wait for tasks previously posted to |
| 43 | the TaskRunner to complete their execution. Tasks can run normally after the |
| 44 | last client reference to the TaskRunner to which they were posted has been |
| 45 | released and it can even be kept alive indefinitely through |
| 46 | `SequencedTaskRunnerHandle::Get()` or `ThreadTaskRunnerHandle::Get()`. |
| 47 | |
| 48 | If you want some state to be deleted only after all tasks currently posted to a |
| 49 | SequencedTaskRunner have run, store that state in a helper object and schedule |
| 50 | deletion of that helper object on the SequencedTaskRunner using |
| 51 | `base::OnTaskRunnerDeleter` after posting the last task. See |
| 52 | [example CL](https://crrev.com/c/1416271/15/chrome/browser/performance_monitor/system_monitor.h). |
| 53 | But be aware that any task posting back to its "current" sequence can enqueue |
| 54 | itself after that "last" task. |
| 55 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 56 | ## Making blocking calls (which do not use the CPU) |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 57 | |
Etienne Pierre-doray | d710e15 | 2018-10-26 16:22:23 | [diff] [blame] | 58 | ### How to make a blocking call without preventing other tasks from being scheduled? |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 59 | |
Etienne Pierre-doray | d710e15 | 2018-10-26 16:22:23 | [diff] [blame] | 60 | The steps depend on where the task runs (see [Where will a task run?](#On-what-thread-will-a-task-run_)). |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 61 | |
| 62 | If the task runs in a thread pool: |
| 63 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 64 | * Annotate the scope that may block with |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 65 | `ScopedBlockingCall(BlockingType::MAY_BLOCK/WILL_BLOCK)`. A few milliseconds |
| 66 | after the annotated scope is entered, the capacity of the thread pool is |
| 67 | incremented. This ensures that your task doesn't reduce the number of tasks |
| 68 | that can run concurrently on the CPU. If the scope exits, the thread pool |
Etienne Pierre-doray | d710e15 | 2018-10-26 16:22:23 | [diff] [blame] | 69 | capacity goes back to normal. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 70 | |
| 71 | If the task runs on the main thread, the IO thread or a `SHARED |
| 72 | SingleThreadTaskRunner`: |
| 73 | |
| 74 | * Blocking on one of these threads will cause breakages. Move your task to a |
| 75 | thread pool (or to a `DEDICATED SingleThreadTaskRunner` if necessary - see |
| 76 | [Prefer Sequences to Threads](threading_and_tasks.md#Prefer-Sequences-to-Threads)). |
| 77 | |
| 78 | If the task runs on a `DEDICATED SingleThreadTaskRunner`: |
| 79 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 80 | * Annotate the scope that may block with |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 81 | `ScopedBlockingCall(BlockingType::MAY_BLOCK/WILL_BLOCK)`. The annotation is a |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 82 | no-op that documents the blocking behavior (and makes it pass assertions). |
| 83 | Tasks posted to the same `DEDICATED SingleThreadTaskRunner` won't run until |
| 84 | your blocking task returns (they will never run if the blocking task never |
| 85 | returns). |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 86 | |
Etienne Pierre-doray | 338d61c | 2018-10-16 12:35:32 | [diff] [blame] | 87 | [base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h) |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 88 | explains the difference between `MAY_BLOCK` and `WILL_BLOCK` and gives |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 89 | examples of blocking operations. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 90 | |
Etienne Pierre-doray | d710e15 | 2018-10-26 16:22:23 | [diff] [blame] | 91 | ### How to make a blocking call that may never return without preventing other tasks from being scheduled? |
| 92 | |
| 93 | If you can't avoid making a call to a third-party library that may block off- |
| 94 | CPU, follow recommendations in [How to make a blocking call without affecting |
| 95 | other tasks?](#How-to-make-a-blocking-call-without-affecting-other-tasks_). |
| 96 | This ensures that a current task doesn't prevent other tasks from running even |
| 97 | if it never returns. |
| 98 | |
| 99 | Since tasks posted to the same sequence can't run concurrently, it is advisable |
| 100 | to run tasks that may block indefinitely in |
| 101 | [parallel](threading_and_tasks.md#posting-a-parallel-task) rather than in |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 102 | [sequence](threading_and_tasks.md#posting-a-sequenced-task) (unless posting many |
| 103 | such tasks at which point sequencing can be a useful tool to prevent flooding). |
Etienne Pierre-doray | d710e15 | 2018-10-26 16:22:23 | [diff] [blame] | 104 | |
Etienne Pierre-doray | 338d61c | 2018-10-16 12:35:32 | [diff] [blame] | 105 | ### Do calls to blocking //base APIs need to be annotated with ScopedBlockingCall? |
| 106 | |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 107 | No. All blocking //base APIs (e.g. `base::ReadFileToString`, `base::File::Read`, |
| 108 | `base::SysInfo::AmountOfFreeDiskSpace`, `base::WaitableEvent::Wait`, etc.) have |
| 109 | their own internal annotations. See |
Etienne Pierre-doray | 338d61c | 2018-10-16 12:35:32 | [diff] [blame] | 110 | [base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h). |
| 111 | |
| 112 | ### Can multiple ScopedBlockingCall be nested for the purpose of documentation? |
| 113 | |
| 114 | Nested `ScopedBlockingCall` are supported. Most of the time, the inner |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 115 | ScopedBlockingCalls will no-op (the exception is `WILL_BLOCK` nested in `MAY_BLOCK`). |
Etienne Pierre-doray | 338d61c | 2018-10-16 12:35:32 | [diff] [blame] | 116 | As such, it is permitted to add a ScopedBlockingCall in the scope where a function |
| 117 | that is already annotated is called for documentation purposes.: |
| 118 | |
| 119 | ```cpp |
| 120 | Data GetDataFromNetwork() { |
| 121 | ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK); |
| 122 | // Fetch data from network. |
| 123 | ... |
| 124 | return data; |
| 125 | } |
| 126 | |
| 127 | void ProcessDataFromNetwork() { |
| 128 | Data data; |
| 129 | { |
| 130 | // Document the blocking behavior with a ScopedBlockingCall. |
| 131 | // Permitted, but not required since GetDataFromNetwork() is itself annotated. |
| 132 | ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK); |
| 133 | data = GetDataFromNetwork(); |
| 134 | } |
| 135 | CPUIntensiveProcessing(data); |
| 136 | } |
| 137 | ``` |
| 138 | |
| 139 | However, CPU usage should always be minimal within the scope of |
| 140 | `ScopedBlockingCall`. See |
| 141 | [base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h). |
| 142 | |
| 143 | |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 144 | ## Sequences |
| 145 | |
| 146 | ### How to migrate from SingleThreadTaskRunner to SequencedTaskRunner? |
| 147 | |
| 148 | The following mappings can be useful when migrating code from a |
| 149 | `SingleThreadTaskRunner` to a `SequencedTaskRunner`: |
| 150 | |
| 151 | * base::SingleThreadTaskRunner -> base::SequencedTaskRunner |
| 152 | * SingleThreadTaskRunner::BelongsToCurrentThread() -> SequencedTaskRunner::RunsTasksInCurrentSequence() |
| 153 | * base::ThreadTaskRunnerHandle -> base::SequencedTaskRunnerHandle |
| 154 | * THREAD_CHECKER -> SEQUENCE_CHECKER |
| 155 | * base::ThreadLocalStorage::Slot -> base::SequenceLocalStorageSlot |
| 156 | * BrowserThread::DeleteOnThread -> base::OnTaskRunnerDeleter / base::RefCountedDeleteOnSequence |
| 157 | * BrowserMessageFilter::OverrideThreadForMessage() -> BrowserMessageFilter::OverrideTaskRunnerForMessage() |
Sami Kyostila | 831c60b | 2019-07-31 13:31:23 | [diff] [blame] | 158 | * CreateSingleThreadTaskRunner() -> CreateSequencedTaskRunner() |
| 159 | * Every CreateSingleThreadTaskRunner() usage, outside of |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 160 | BrowserThread::UI/IO, should be accompanied with a comment and ideally a |
| 161 | bug to make it sequence when the sequence-unfriendly dependency is |
| 162 | addressed. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 163 | |
| 164 | ### How to ensure mutual exclusion between tasks posted by a component? |
| 165 | |
Sami Kyostila | 831c60b | 2019-07-31 13:31:23 | [diff] [blame] | 166 | Create a `SequencedTaskRunner` using `CreateSequencedTaskRunner()` and |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 167 | store it on an object that can be accessed from all the PostTask() call sites |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 168 | that require mutual exclusion. If there isn't a shared object that can own a |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 169 | common `SequencedTaskRunner`, use |
| 170 | `Lazy(Sequenced|SingleThread|COMSTA)TaskRunner` in an anonymous namespace. |
| 171 | |
| 172 | ## Tests |
| 173 | |
| 174 | ### How to test code that posts tasks? |
| 175 | |
| 176 | If the test uses `BrowserThread::UI/IO`, instantiate a |
Gabriel Charette | 798fde7 | 2019-08-20 22:24:04 | [diff] [blame] | 177 | `content::BrowserTaskEnvironment` for the scope of the test. Call |
| 178 | `BrowserTaskEnvironment::RunUntilIdle()` to wait until all tasks have run. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 179 | |
| 180 | If the test doesn't use `BrowserThread::UI/IO`, instantiate a |
Gabriel Charette | 694c3c33 | 2019-08-19 14:53:05 | [diff] [blame] | 181 | `base::test::TaskEnvironment` for the scope of the test. Call |
| 182 | `base::test::TaskEnvironment::RunUntilIdle()` to wait until all tasks have |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 183 | run. |
| 184 | |
| 185 | In both cases, you can run tasks until a condition is met. A test that waits for |
| 186 | a condition to be met is easier to understand and debug than a test that waits |
| 187 | for all tasks to run. |
| 188 | |
| 189 | ```cpp |
| 190 | int g_condition = false; |
| 191 | |
| 192 | base::RunLoop run_loop; |
Gabriel Charette | 1f57c9b8 | 2022-03-16 22:54:34 | [diff] [blame] | 193 | base::ThreadPool::PostTask(FROM_HERE, {}, base::BindOnce( |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 194 | [] (base::OnceClosure closure) { |
| 195 | g_condition = true; |
| 196 | std::move(quit_closure).Run(); |
| 197 | }, run_loop.QuitClosure())); |
| 198 | |
| 199 | // Runs tasks until the quit closure is invoked. |
| 200 | run_loop.Run(); |
| 201 | |
| 202 | EXPECT_TRUE(g_condition); |
| 203 | ``` |
| 204 | |
| 205 | ## Your question hasn't been answered? |
| 206 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 207 | 1. Check the main [Threading and Tasks](threading_and_tasks.md) docs. |
| 208 | 2. Ping |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 209 | [scheduler-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/scheduler-dev). |