xref: /aosp_15_r20/external/cronet/base/task/sequence_manager/work_queue.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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