1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/task/sequence_manager/work_queue.h"
6
7 #include <optional>
8
9 #include "base/debug/alias.h"
10 #include "base/task/sequence_manager/fence.h"
11 #include "base/task/sequence_manager/sequence_manager_impl.h"
12 #include "base/task/sequence_manager/task_order.h"
13 #include "base/task/sequence_manager/work_queue_sets.h"
14 #include "build/build_config.h"
15 #include "third_party/abseil-cpp/absl/container/inlined_vector.h"
16
17 namespace base {
18 namespace sequence_manager {
19 namespace internal {
20
WorkQueue(TaskQueueImpl * task_queue,const char * name,QueueType queue_type)21 WorkQueue::WorkQueue(TaskQueueImpl* task_queue,
22 const char* name,
23 QueueType queue_type)
24 : task_queue_(task_queue), name_(name), queue_type_(queue_type) {}
25
AsValue(TimeTicks now) const26 Value::List WorkQueue::AsValue(TimeTicks now) const {
27 Value::List state;
28 for (const Task& task : tasks_)
29 state.Append(TaskQueueImpl::TaskAsValue(task, now));
30 return state;
31 }
32
~WorkQueue()33 WorkQueue::~WorkQueue() {
34 DCHECK(!work_queue_sets_) << task_queue_->GetName() << " : "
35 << work_queue_sets_->GetName() << " : " << name_;
36 }
37
GetFrontTask() const38 const Task* WorkQueue::GetFrontTask() const {
39 if (tasks_.empty())
40 return nullptr;
41 return &tasks_.front();
42 }
43
GetBackTask() const44 const Task* WorkQueue::GetBackTask() const {
45 if (tasks_.empty())
46 return nullptr;
47 return &tasks_.back();
48 }
49
BlockedByFence() const50 bool WorkQueue::BlockedByFence() const {
51 if (!fence_)
52 return false;
53
54 // If the queue is empty then any future tasks will have a higher enqueue
55 // order and will be blocked. The queue is also blocked if the head is past
56 // the fence.
57 return tasks_.empty() || tasks_.front().task_order() >= fence_->task_order();
58 }
59
GetFrontTaskOrder() const60 std::optional<TaskOrder> WorkQueue::GetFrontTaskOrder() const {
61 if (tasks_.empty() || BlockedByFence())
62 return std::nullopt;
63 // Quick sanity check.
64 DCHECK(tasks_.front().task_order() <= tasks_.back().task_order())
65 << task_queue_->GetName() << " : " << work_queue_sets_->GetName() << " : "
66 << name_;
67 return tasks_.front().task_order();
68 }
69
Push(Task task)70 void WorkQueue::Push(Task task) {
71 bool was_empty = tasks_.empty();
72 #ifndef NDEBUG
73 DCHECK(task.enqueue_order_set());
74 #endif
75
76 // Make sure the task order is strictly increasing.
77 DCHECK(was_empty || tasks_.back().task_order() < task.task_order());
78 // Make sure enqueue order is strictly increasing for immediate queues and
79 // monotonically increasing for delayed queues.
80 DCHECK(was_empty || tasks_.back().enqueue_order() < task.enqueue_order() ||
81 (queue_type_ == QueueType::kDelayed &&
82 tasks_.back().enqueue_order() == task.enqueue_order()));
83
84 // Amortized O(1).
85 tasks_.push_back(std::move(task));
86
87 if (!was_empty)
88 return;
89
90 // If we hit the fence, pretend to WorkQueueSets that we're empty.
91 if (work_queue_sets_ && !BlockedByFence())
92 work_queue_sets_->OnTaskPushedToEmptyQueue(this);
93 }
94
TaskPusher(WorkQueue * work_queue)95 WorkQueue::TaskPusher::TaskPusher(WorkQueue* work_queue)
96 : work_queue_(work_queue), was_empty_(work_queue->Empty()) {}
97
TaskPusher(TaskPusher && other)98 WorkQueue::TaskPusher::TaskPusher(TaskPusher&& other)
99 : work_queue_(other.work_queue_), was_empty_(other.was_empty_) {
100 other.work_queue_ = nullptr;
101 }
102
Push(Task task)103 void WorkQueue::TaskPusher::Push(Task task) {
104 DCHECK(work_queue_);
105
106 #ifndef NDEBUG
107 DCHECK(task.enqueue_order_set());
108 #endif
109
110 // Make sure the task order is strictly increasing.
111 DCHECK(work_queue_->tasks_.empty() ||
112 work_queue_->tasks_.back().task_order() < task.task_order());
113 // Make sure enqueue order is strictly increasing for immediate queues and
114 // monotonically increasing for delayed queues.
115 DCHECK(work_queue_->tasks_.empty() ||
116 work_queue_->tasks_.back().enqueue_order() < task.enqueue_order() ||
117 (work_queue_->queue_type_ == QueueType::kDelayed &&
118 work_queue_->tasks_.back().enqueue_order() == task.enqueue_order()));
119
120 // Amortized O(1).
121 work_queue_->tasks_.push_back(std::move(task));
122 }
123
~TaskPusher()124 WorkQueue::TaskPusher::~TaskPusher() {
125 // If |work_queue_| became non empty and it isn't blocked by a fence then we
126 // must notify |work_queue_->work_queue_sets_|.
127 if (was_empty_ && work_queue_ && !work_queue_->Empty() &&
128 work_queue_->work_queue_sets_ && !work_queue_->BlockedByFence()) {
129 work_queue_->work_queue_sets_->OnTaskPushedToEmptyQueue(work_queue_);
130 }
131 }
132
CreateTaskPusher()133 WorkQueue::TaskPusher WorkQueue::CreateTaskPusher() {
134 return TaskPusher(this);
135 }
136
PushNonNestableTaskToFront(Task task)137 void WorkQueue::PushNonNestableTaskToFront(Task task) {
138 DCHECK(task.nestable == Nestable::kNonNestable);
139
140 bool was_empty = tasks_.empty();
141 bool was_blocked = BlockedByFence();
142 #ifndef NDEBUG
143 DCHECK(task.enqueue_order_set());
144 #endif
145
146 if (!was_empty) {
147 // Make sure the task order is strictly increasing.
148 DCHECK(task.task_order() < tasks_.front().task_order())
149 << task_queue_->GetName() << " : " << work_queue_sets_->GetName()
150 << " : " << name_;
151 // Make sure the enqueue order is strictly increasing for immediate queues
152 // and monotonically increasing for delayed queues.
153 DCHECK(task.enqueue_order() < tasks_.front().enqueue_order() ||
154 (queue_type_ == QueueType::kDelayed &&
155 task.enqueue_order() == tasks_.front().enqueue_order()))
156 << task_queue_->GetName() << " : " << work_queue_sets_->GetName()
157 << " : " << name_;
158 }
159
160 // Amortized O(1).
161 tasks_.push_front(std::move(task));
162
163 if (!work_queue_sets_)
164 return;
165
166 // Pretend to WorkQueueSets that nothing has changed if we're blocked.
167 if (BlockedByFence())
168 return;
169
170 // Pushing task to front may unblock the fence.
171 if (was_empty || was_blocked) {
172 work_queue_sets_->OnTaskPushedToEmptyQueue(this);
173 } else {
174 work_queue_sets_->OnQueuesFrontTaskChanged(this);
175 }
176 }
177
TakeImmediateIncomingQueueTasks()178 void WorkQueue::TakeImmediateIncomingQueueTasks() {
179 DCHECK(tasks_.empty());
180
181 task_queue_->TakeImmediateIncomingQueueTasks(&tasks_);
182 if (tasks_.empty())
183 return;
184
185 // If we hit the fence, pretend to WorkQueueSets that we're empty.
186 if (work_queue_sets_ && !BlockedByFence())
187 work_queue_sets_->OnTaskPushedToEmptyQueue(this);
188 }
189
TakeTaskFromWorkQueue()190 Task WorkQueue::TakeTaskFromWorkQueue() {
191 DCHECK(work_queue_sets_);
192 DCHECK(!tasks_.empty());
193
194 Task pending_task = std::move(tasks_.front());
195 tasks_.pop_front();
196 // NB immediate tasks have a different pipeline to delayed ones.
197 if (tasks_.empty()) {
198 // NB delayed tasks are inserted via Push, no don't need to reload those.
199 if (queue_type_ == QueueType::kImmediate) {
200 // Short-circuit the queue reload so that OnPopMinQueueInSet does the
201 // right thing.
202 task_queue_->TakeImmediateIncomingQueueTasks(&tasks_);
203 }
204 // Since the queue is empty, now is a good time to consider reducing it's
205 // capacity if we're wasting memory.
206 tasks_.MaybeShrinkQueue();
207 }
208
209 DCHECK(work_queue_sets_);
210 #if DCHECK_IS_ON()
211 // If diagnostics are on it's possible task queues are being selected at
212 // random so we can't use the (slightly) more efficient OnPopMinQueueInSet.
213 work_queue_sets_->OnQueuesFrontTaskChanged(this);
214 #else
215 // OnPopMinQueueInSet calls GetFrontTaskOrder which checks
216 // BlockedByFence() so we don't need to here.
217 work_queue_sets_->OnPopMinQueueInSet(this);
218 #endif
219 task_queue_->TraceQueueSize();
220 return pending_task;
221 }
222
RemoveAllCanceledTasksFromFront()223 bool WorkQueue::RemoveAllCanceledTasksFromFront() {
224 if (!work_queue_sets_) {
225 return false;
226 }
227
228 // Since task destructors could have a side-effect of deleting this task queue
229 // we move cancelled tasks into a temporary container which can be emptied
230 // without accessing |this|.
231 absl::InlinedVector<Task, 8> tasks_to_delete;
232
233 while (!tasks_.empty()) {
234 const auto& pending_task = tasks_.front();
235 if (pending_task.task && !pending_task.IsCanceled())
236 break;
237 tasks_to_delete.push_back(std::move(tasks_.front()));
238 tasks_.pop_front();
239 }
240 if (!tasks_to_delete.empty()) {
241 if (tasks_.empty()) {
242 // NB delayed tasks are inserted via Push, no don't need to reload those.
243 if (queue_type_ == QueueType::kImmediate) {
244 // Short-circuit the queue reload so that OnPopMinQueueInSet does the
245 // right thing.
246 task_queue_->TakeImmediateIncomingQueueTasks(&tasks_);
247 }
248 // Since the queue is empty, now is a good time to consider reducing it's
249 // capacity if we're wasting memory.
250 tasks_.MaybeShrinkQueue();
251 }
252 // If we have a valid |heap_handle_| (i.e. we're not blocked by a fence or
253 // disabled) then |work_queue_sets_| needs to be told.
254 if (heap_handle_.IsValid())
255 work_queue_sets_->OnQueuesFrontTaskChanged(this);
256 task_queue_->TraceQueueSize();
257 }
258 return !tasks_to_delete.empty();
259 }
260
AssignToWorkQueueSets(WorkQueueSets * work_queue_sets)261 void WorkQueue::AssignToWorkQueueSets(WorkQueueSets* work_queue_sets) {
262 work_queue_sets_ = work_queue_sets;
263 }
264
AssignSetIndex(size_t work_queue_set_index)265 void WorkQueue::AssignSetIndex(size_t work_queue_set_index) {
266 work_queue_set_index_ = work_queue_set_index;
267 }
268
InsertFenceImpl(Fence fence)269 bool WorkQueue::InsertFenceImpl(Fence fence) {
270 DCHECK(!fence_ || fence.task_order() >= fence_->task_order() ||
271 fence.IsBlockingFence());
272 bool was_blocked_by_fence = BlockedByFence();
273 fence_ = fence;
274 return was_blocked_by_fence;
275 }
276
InsertFenceSilently(Fence fence)277 void WorkQueue::InsertFenceSilently(Fence fence) {
278 // Ensure that there is no fence present or a new one blocks queue completely.
279 DCHECK(!fence_ || fence_->IsBlockingFence());
280 InsertFenceImpl(fence);
281 }
282
InsertFence(Fence fence)283 bool WorkQueue::InsertFence(Fence fence) {
284 bool was_blocked_by_fence = InsertFenceImpl(fence);
285 if (!work_queue_sets_)
286 return false;
287
288 // Moving the fence forward may unblock some tasks.
289 if (!tasks_.empty() && was_blocked_by_fence && !BlockedByFence()) {
290 work_queue_sets_->OnTaskPushedToEmptyQueue(this);
291 return true;
292 }
293 // Fence insertion may have blocked all tasks in this work queue.
294 if (BlockedByFence())
295 work_queue_sets_->OnQueueBlocked(this);
296 return false;
297 }
298
RemoveFence()299 bool WorkQueue::RemoveFence() {
300 bool was_blocked_by_fence = BlockedByFence();
301 fence_ = std::nullopt;
302 if (work_queue_sets_ && !tasks_.empty() && was_blocked_by_fence) {
303 work_queue_sets_->OnTaskPushedToEmptyQueue(this);
304 return true;
305 }
306 return false;
307 }
308
MaybeShrinkQueue()309 void WorkQueue::MaybeShrinkQueue() {
310 tasks_.MaybeShrinkQueue();
311 }
312
PopTaskForTesting()313 void WorkQueue::PopTaskForTesting() {
314 if (tasks_.empty())
315 return;
316 tasks_.pop_front();
317 }
318
CollectTasksOlderThan(TaskOrder reference,std::vector<const Task * > * result) const319 void WorkQueue::CollectTasksOlderThan(TaskOrder reference,
320 std::vector<const Task*>* result) const {
321 for (const Task& task : tasks_) {
322 if (task.task_order() >= reference)
323 break;
324
325 result->push_back(&task);
326 }
327 }
328
329 } // namespace internal
330 } // namespace sequence_manager
331 } // namespace base
332