blob: 7ad8cd2bcbe96307e7eef2e7aa88d2e632def835 [file] [log] [blame]
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:051// 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-doray3c4a5fb12020-06-29 22:25:098#include <limits>
9
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0510#include "base/base_export.h"
Etienne Pierre-doray9e8b40f2019-10-09 16:00:2411#include "base/callback.h"
Hans Wennborg7b533712020-06-22 20:52:2712#include "base/check_op.h"
Etienne Pierre-doray9e8b40f2019-10-09 16:00:2413#include "base/location.h"
Keishi Hattori0e45c022021-11-27 09:25:5214#include "base/memory/raw_ptr.h"
Etienne Pierre-doraye62a1382019-10-03 17:08:5215#include "base/memory/ref_counted.h"
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0516
17namespace base {
18namespace internal {
19class JobTaskSource;
Etienne Pierre-dorayac3680742019-08-29 17:43:1720class PooledTaskRunnerDelegate;
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0521}
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0522
Lei Zhang63b9a4e2021-06-07 20:32:4523class TaskTraits;
24enum class TaskPriority : uint8_t;
25
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0526// Delegate that's passed to Job's worker task, providing an entry point to
Etienne Pierre-doray71878832020-04-20 19:49:0127// communicate with the scheduler. To prevent deadlocks, JobDelegate methods
28// should never be called while holding a user lock.
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0529class BASE_EXPORT JobDelegate {
30 public:
Etienne Pierre-dorayac3680742019-08-29 17:43:1731 // 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-doraye62a1382019-10-03 17:08:5233 // 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-dorayac3680742019-08-29 17:43:1736 JobDelegate(internal::JobTaskSource* task_source,
37 internal::PooledTaskRunnerDelegate* pooled_task_runner_delegate);
Peter Boström7319bbd2021-09-15 22:59:3838
39 JobDelegate(const JobDelegate&) = delete;
40 JobDelegate& operator=(const JobDelegate&) = delete;
41
Etienne Pierre-dorayac3680742019-08-29 17:43:1742 ~JobDelegate();
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0543
Etienne Pierre-doraya5a81e12020-10-15 00:05:2544 // Returns true if this thread *must* return from the worker task on the
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0545 // 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-doray9e8b40f2019-10-09 16:00:2457 // of worker should be adjusted accordingly. See PostJob() for more details.
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0558 void NotifyConcurrencyIncrease();
59
Etienne Pierre-doray3c4a5fb12020-06-29 22:25:0960 // 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-doraybcff179ae2020-09-22 14:53:5365 // 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-dorayc6b18eb2019-07-29 19:25:0571 private:
Etienne Pierre-doray3c4a5fb12020-06-29 22:25:0972 static constexpr uint8_t kInvalidTaskId = std::numeric_limits<uint8_t>::max();
73
Keishi Hattori0e45c022021-11-27 09:25:5274 const raw_ptr<internal::JobTaskSource> task_source_;
75 const raw_ptr<internal::PooledTaskRunnerDelegate>
76 pooled_task_runner_delegate_;
Etienne Pierre-doray3c4a5fb12020-06-29 22:25:0977 uint8_t task_id_ = kInvalidTaskId;
Etienne Pierre-dorayac3680742019-08-29 17:43:1778
79#if DCHECK_IS_ON()
Etienne Pierre-dorayac3680742019-08-29 17:43:1780 // Value returned by the last call to ShouldYield().
81 bool last_should_yield_ = false;
82#endif
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:0583};
84
Etienne Pierre-doraye62a1382019-10-03 17:08:5285// Handle returned when posting a Job. Provides methods to control execution of
Etienne Pierre-doray71878832020-04-20 19:49:0186// the posted Job. To prevent deadlocks, JobHandle methods should never be
87// called while holding a user lock.
Etienne Pierre-doraye62a1382019-10-03 17:08:5288class BASE_EXPORT JobHandle {
89 public:
Etienne Pierre-doray9e8b40f2019-10-09 16:00:2490 JobHandle();
Peter Boström7319bbd2021-09-15 22:59:3891
92 JobHandle(const JobHandle&) = delete;
93 JobHandle& operator=(const JobHandle&) = delete;
94
Etienne Pierre-doraye62a1382019-10-03 17:08:5295 // 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-doray421d2b72019-11-08 19:02:42102 // Returns true if associated with a Job.
103 explicit operator bool() const { return task_source_ != nullptr; }
104
Etienne Pierre-doray4fea66bd2020-11-30 23:20:12105 // Returns true if there's any work pending or any worker running.
106 bool IsActive() const;
Etienne Pierre-doray2f52b3512020-08-12 19:04:29107
Etienne Pierre-doraye62a1382019-10-03 17:08:52108 // 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-doray9e8b40f2019-10-09 16:00:24112 // of workers should be adjusted accordingly. See PostJob() for more details.
Etienne Pierre-doraye62a1382019-10-03 17:08:52113 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-doraye62a1382019-10-03 17:08:52133 friend class internal::JobTaskSource;
134
135 explicit JobHandle(scoped_refptr<internal::JobTaskSource> task_source);
136
137 scoped_refptr<internal::JobTaskSource> task_source_;
Etienne Pierre-doraye62a1382019-10-03 17:08:52138};
139
Etienne Pierre-Dorayf91d7a02020-09-11 15:53:27140// 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.
146using MaxConcurrencyCallback =
147 RepeatingCallback<size_t(size_t /*worker_count*/)>;
148
Gabriel Charette7d5eac02020-01-28 13:37:13149// Posts a repeating |worker_task| with specific |traits| to run in parallel on
150// base::ThreadPool.
Etienne Pierre-doray9e8b40f2019-10-09 16:00:24151// Returns a JobHandle associated with the Job, which can be joined, canceled or
152// detached.
Etienne Pierre-doray71878832020-04-20 19:49:01153// 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-doray9e8b40f2019-10-09 16:00:24162// 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-Dorayf91d7a02020-09-11 15:53:27178// |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-doray9e8b40f2019-10-09 16:00:24180// |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-doray9e8b40f2019-10-09 16:00:24192// - base::ThreadPolicy must be specified if the priority of the task runner
193// will ever be increased from BEST_EFFORT.
Etienne Pierre-Dorayf91d7a02020-09-11 15:53:27194JobHandle BASE_EXPORT PostJob(const Location& from_here,
195 const TaskTraits& traits,
196 RepeatingCallback<void(JobDelegate*)> worker_task,
197 MaxConcurrencyCallback max_concurrency_callback);
Etienne Pierre-doray9e8b40f2019-10-09 16:00:24198
Etienne Pierre-dorayc6b18eb2019-07-29 19:25:05199} // namespace base
200
Gabriel Charette7d5eac02020-01-28 13:37:13201#endif // BASE_TASK_POST_JOB_H_