Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 1 | // Copyright 2019 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #ifndef BASE_TASK_POST_JOB_H_ |
| 6 | #define BASE_TASK_POST_JOB_H_ |
| 7 | |
Etienne Pierre-doray | 3c4a5fb1 | 2020-06-29 22:25:09 | [diff] [blame] | 8 | #include <limits> |
| 9 | |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 10 | #include "base/base_export.h" |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 11 | #include "base/callback.h" |
Hans Wennborg | 7b53371 | 2020-06-22 20:52:27 | [diff] [blame] | 12 | #include "base/check_op.h" |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 13 | #include "base/location.h" |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 14 | #include "base/memory/raw_ptr.h" |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 15 | #include "base/memory/ref_counted.h" |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 16 | |
| 17 | namespace base { |
| 18 | namespace internal { |
| 19 | class JobTaskSource; |
Etienne Pierre-doray | ac368074 | 2019-08-29 17:43:17 | [diff] [blame] | 20 | class PooledTaskRunnerDelegate; |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 21 | } |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 22 | |
Lei Zhang | 63b9a4e | 2021-06-07 20:32:45 | [diff] [blame] | 23 | class TaskTraits; |
| 24 | enum class TaskPriority : uint8_t; |
| 25 | |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 26 | // Delegate that's passed to Job's worker task, providing an entry point to |
Etienne Pierre-doray | 7187883 | 2020-04-20 19:49:01 | [diff] [blame] | 27 | // communicate with the scheduler. To prevent deadlocks, JobDelegate methods |
| 28 | // should never be called while holding a user lock. |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 29 | class BASE_EXPORT JobDelegate { |
| 30 | public: |
Etienne Pierre-doray | ac368074 | 2019-08-29 17:43:17 | [diff] [blame] | 31 | // A JobDelegate is instantiated for each worker task that is run. |
| 32 | // |task_source| is the task source whose worker task is running with this |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 33 | // delegate and |pooled_task_runner_delegate| is used by ShouldYield() to |
| 34 | // check whether the pool wants this worker task to yield (null if this worker |
| 35 | // should never yield -- e.g. when the main thread is a worker). |
Etienne Pierre-doray | ac368074 | 2019-08-29 17:43:17 | [diff] [blame] | 36 | JobDelegate(internal::JobTaskSource* task_source, |
| 37 | internal::PooledTaskRunnerDelegate* pooled_task_runner_delegate); |
Peter Boström | 7319bbd | 2021-09-15 22:59:38 | [diff] [blame] | 38 | |
| 39 | JobDelegate(const JobDelegate&) = delete; |
| 40 | JobDelegate& operator=(const JobDelegate&) = delete; |
| 41 | |
Etienne Pierre-doray | ac368074 | 2019-08-29 17:43:17 | [diff] [blame] | 42 | ~JobDelegate(); |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 43 | |
Etienne Pierre-doray | a5a81e1 | 2020-10-15 00:05:25 | [diff] [blame] | 44 | // Returns true if this thread *must* return from the worker task on the |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 45 | // current thread ASAP. Workers should periodically invoke ShouldYield (or |
| 46 | // YieldIfNeeded()) as often as is reasonable. |
| 47 | bool ShouldYield(); |
| 48 | |
| 49 | // If ShouldYield(), this will pause the current thread (allowing it to be |
| 50 | // replaced in the pool); no-ops otherwise. If it pauses, it will resume and |
| 51 | // return from this call whenever higher priority work completes. |
| 52 | // Prefer ShouldYield() over this (only use YieldIfNeeded() when unwinding |
| 53 | // the stack is not possible). |
| 54 | void YieldIfNeeded(); |
| 55 | |
| 56 | // Notifies the scheduler that max concurrency was increased, and the number |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 57 | // of worker should be adjusted accordingly. See PostJob() for more details. |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 58 | void NotifyConcurrencyIncrease(); |
| 59 | |
Etienne Pierre-doray | 3c4a5fb1 | 2020-06-29 22:25:09 | [diff] [blame] | 60 | // Returns a task_id unique among threads currently running this job, such |
| 61 | // that GetTaskId() < worker count. To achieve this, the same task_id may be |
| 62 | // reused by a different thread after a worker_task returns. |
| 63 | uint8_t GetTaskId(); |
| 64 | |
Etienne Pierre-doray | bcff179ae | 2020-09-22 14:53:53 | [diff] [blame] | 65 | // Returns true if the current task is called from the thread currently |
| 66 | // running JobHandle::Join(). |
| 67 | bool IsJoiningThread() const { |
| 68 | return pooled_task_runner_delegate_ == nullptr; |
| 69 | } |
| 70 | |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 71 | private: |
Etienne Pierre-doray | 3c4a5fb1 | 2020-06-29 22:25:09 | [diff] [blame] | 72 | static constexpr uint8_t kInvalidTaskId = std::numeric_limits<uint8_t>::max(); |
| 73 | |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 74 | const raw_ptr<internal::JobTaskSource> task_source_; |
| 75 | const raw_ptr<internal::PooledTaskRunnerDelegate> |
| 76 | pooled_task_runner_delegate_; |
Etienne Pierre-doray | 3c4a5fb1 | 2020-06-29 22:25:09 | [diff] [blame] | 77 | uint8_t task_id_ = kInvalidTaskId; |
Etienne Pierre-doray | ac368074 | 2019-08-29 17:43:17 | [diff] [blame] | 78 | |
| 79 | #if DCHECK_IS_ON() |
Etienne Pierre-doray | ac368074 | 2019-08-29 17:43:17 | [diff] [blame] | 80 | // Value returned by the last call to ShouldYield(). |
| 81 | bool last_should_yield_ = false; |
| 82 | #endif |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 83 | }; |
| 84 | |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 85 | // Handle returned when posting a Job. Provides methods to control execution of |
Etienne Pierre-doray | 7187883 | 2020-04-20 19:49:01 | [diff] [blame] | 86 | // the posted Job. To prevent deadlocks, JobHandle methods should never be |
| 87 | // called while holding a user lock. |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 88 | class BASE_EXPORT JobHandle { |
| 89 | public: |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 90 | JobHandle(); |
Peter Boström | 7319bbd | 2021-09-15 22:59:38 | [diff] [blame] | 91 | |
| 92 | JobHandle(const JobHandle&) = delete; |
| 93 | JobHandle& operator=(const JobHandle&) = delete; |
| 94 | |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 95 | // A job must either be joined, canceled or detached before the JobHandle is |
| 96 | // destroyed. |
| 97 | ~JobHandle(); |
| 98 | |
| 99 | JobHandle(JobHandle&&); |
| 100 | JobHandle& operator=(JobHandle&&); |
| 101 | |
Etienne Pierre-doray | 421d2b7 | 2019-11-08 19:02:42 | [diff] [blame] | 102 | // Returns true if associated with a Job. |
| 103 | explicit operator bool() const { return task_source_ != nullptr; } |
| 104 | |
Etienne Pierre-doray | 4fea66bd | 2020-11-30 23:20:12 | [diff] [blame] | 105 | // Returns true if there's any work pending or any worker running. |
| 106 | bool IsActive() const; |
Etienne Pierre-doray | 2f52b351 | 2020-08-12 19:04:29 | [diff] [blame] | 107 | |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 108 | // Update this Job's priority. |
| 109 | void UpdatePriority(TaskPriority new_priority); |
| 110 | |
| 111 | // Notifies the scheduler that max concurrency was increased, and the number |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 112 | // of workers should be adjusted accordingly. See PostJob() for more details. |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 113 | void NotifyConcurrencyIncrease(); |
| 114 | |
| 115 | // Contributes to the job on this thread. Doesn't return until all tasks have |
| 116 | // completed and max concurrency becomes 0. This also promotes this Job's |
| 117 | // priority to be at least as high as the calling thread's priority. |
| 118 | void Join(); |
| 119 | |
| 120 | // Forces all existing workers to yield ASAP. Waits until they have all |
| 121 | // returned from the Job's callback before returning. |
| 122 | void Cancel(); |
| 123 | |
| 124 | // Forces all existing workers to yield ASAP but doesn’t wait for them. |
| 125 | // Warning, this is dangerous if the Job's callback is bound to or has access |
| 126 | // to state which may be deleted after this call. |
| 127 | void CancelAndDetach(); |
| 128 | |
| 129 | // Can be invoked before ~JobHandle() to avoid waiting on the job completing. |
| 130 | void Detach(); |
| 131 | |
| 132 | private: |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 133 | friend class internal::JobTaskSource; |
| 134 | |
| 135 | explicit JobHandle(scoped_refptr<internal::JobTaskSource> task_source); |
| 136 | |
| 137 | scoped_refptr<internal::JobTaskSource> task_source_; |
Etienne Pierre-doray | e62a138 | 2019-10-03 17:08:52 | [diff] [blame] | 138 | }; |
| 139 | |
Etienne Pierre-Doray | f91d7a0 | 2020-09-11 15:53:27 | [diff] [blame] | 140 | // Callback used in PostJob() to control the maximum number of threads calling |
| 141 | // the worker task concurrently. |
| 142 | |
| 143 | // Returns the maximum number of threads which may call a job's worker task |
| 144 | // concurrently. |worker_count| is the number of threads currently assigned to |
| 145 | // this job which some callers may need to determine their return value. |
| 146 | using MaxConcurrencyCallback = |
| 147 | RepeatingCallback<size_t(size_t /*worker_count*/)>; |
| 148 | |
Gabriel Charette | 7d5eac0 | 2020-01-28 13:37:13 | [diff] [blame] | 149 | // Posts a repeating |worker_task| with specific |traits| to run in parallel on |
| 150 | // base::ThreadPool. |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 151 | // Returns a JobHandle associated with the Job, which can be joined, canceled or |
| 152 | // detached. |
Etienne Pierre-doray | 7187883 | 2020-04-20 19:49:01 | [diff] [blame] | 153 | // ThreadPool APIs, including PostJob() and methods of the returned JobHandle, |
| 154 | // must never be called while holding a lock that could be acquired by |
| 155 | // |worker_task| or |max_concurrency_callback| -- that could result in a |
| 156 | // deadlock. This is because [1] |max_concurrency_callback| may be invoked while |
| 157 | // holding internal ThreadPool lock (A), hence |max_concurrency_callback| can |
| 158 | // only use a lock (B) if that lock is *never* held while calling back into a |
| 159 | // ThreadPool entry point from any thread (A=>B/B=>A deadlock) and [2] |
| 160 | // |worker_task| or |max_concurrency_callback| is invoked synchronously from |
| 161 | // JobHandle::Join() (A=>JobHandle::Join()=>A deadlock). |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 162 | // To avoid scheduling overhead, |worker_task| should do as much work as |
| 163 | // possible in a loop when invoked, and JobDelegate::ShouldYield() should be |
| 164 | // periodically invoked to conditionally exit and let the scheduler prioritize |
| 165 | // work. |
| 166 | // |
| 167 | // A canonical implementation of |worker_task| looks like: |
| 168 | // void WorkerTask(JobDelegate* job_delegate) { |
| 169 | // while (!job_delegate->ShouldYield()) { |
| 170 | // auto work_item = worker_queue.TakeWorkItem(); // Smallest unit of work. |
| 171 | // if (!work_item) |
| 172 | // return: |
| 173 | // ProcessWork(work_item); |
| 174 | // } |
| 175 | // } |
| 176 | // |
| 177 | // |max_concurrency_callback| controls the maximum number of threads calling |
Etienne Pierre-Doray | f91d7a0 | 2020-09-11 15:53:27 | [diff] [blame] | 178 | // |worker_task| concurrently. |worker_task| is only invoked if the number of |
| 179 | // threads previously running |worker_task| was less than the value returned by |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 180 | // |max_concurrency_callback|. In general, |max_concurrency_callback| should |
| 181 | // return the latest number of incomplete work items (smallest unit of work) |
| 182 | // left to processed. JobHandle/JobDelegate::NotifyConcurrencyIncrease() *must* |
| 183 | // be invoked shortly after |max_concurrency_callback| starts returning a value |
| 184 | // larger than previously returned values. This usually happens when new work |
| 185 | // items are added and the API user wants additional threads to invoke |
| 186 | // |worker_task| concurrently. The callbacks may be called concurrently on any |
| 187 | // thread until the job is complete. If the job handle is detached, the |
| 188 | // callbacks may still be called, so they must not access global state that |
| 189 | // could be destroyed. |
| 190 | // |
| 191 | // |traits| requirements: |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 192 | // - base::ThreadPolicy must be specified if the priority of the task runner |
| 193 | // will ever be increased from BEST_EFFORT. |
Etienne Pierre-Doray | f91d7a0 | 2020-09-11 15:53:27 | [diff] [blame] | 194 | JobHandle BASE_EXPORT PostJob(const Location& from_here, |
| 195 | const TaskTraits& traits, |
| 196 | RepeatingCallback<void(JobDelegate*)> worker_task, |
| 197 | MaxConcurrencyCallback max_concurrency_callback); |
Etienne Pierre-doray | 9e8b40f | 2019-10-09 16:00:24 | [diff] [blame] | 198 | |
Etienne Pierre-doray | c6b18eb | 2019-07-29 19:25:05 | [diff] [blame] | 199 | } // namespace base |
| 200 | |
Gabriel Charette | 7d5eac0 | 2020-01-28 13:37:13 | [diff] [blame] | 201 | #endif // BASE_TASK_POST_JOB_H_ |