• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_
6 #define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_
7 
8 #include <stddef.h>
9 
10 #include <atomic>
11 #include <optional>
12 #include <vector>
13 
14 #include "base/base_export.h"
15 #include "base/dcheck_is_on.h"
16 #include "base/memory/raw_ptr.h"
17 #include "base/pending_task.h"
18 #include "base/task/sequence_manager/sequence_manager.h"
19 #include "base/task/sequence_manager/sequenced_task_source.h"
20 #include "base/task/sequence_manager/task_order.h"
21 #include "base/task/sequence_manager/work_queue_sets.h"
22 #include "base/values.h"
23 
24 namespace base {
25 namespace sequence_manager {
26 namespace internal {
27 
28 class AssociatedThreadId;
29 
30 // TaskQueueSelector is used by the SchedulerHelper to enable prioritization
31 // of particular task queues.
32 class BASE_EXPORT TaskQueueSelector : public WorkQueueSets::Observer {
33  public:
34   using SelectTaskOption = SequencedTaskSource::SelectTaskOption;
35 
36   TaskQueueSelector(scoped_refptr<const AssociatedThreadId> associated_thread,
37                     const SequenceManager::Settings& settings);
38 
39   TaskQueueSelector(const TaskQueueSelector&) = delete;
40   TaskQueueSelector& operator=(const TaskQueueSelector&) = delete;
41   ~TaskQueueSelector() override;
42 
43   // Called to register a queue that can be selected. This function is called
44   // on the main thread.
45   void AddQueue(internal::TaskQueueImpl* queue,
46                 TaskQueue::QueuePriority priority);
47 
48   // The specified work will no longer be considered for selection. This
49   // function is called on the main thread.
50   void RemoveQueue(internal::TaskQueueImpl* queue);
51 
52   // Make |queue| eligible for selection. This function is called on the main
53   // thread. Must only be called if |queue| is disabled.
54   void EnableQueue(internal::TaskQueueImpl* queue);
55 
56   // Disable selection from |queue|. Must only be called if |queue| is enabled.
57   void DisableQueue(internal::TaskQueueImpl* queue);
58 
59   // Called get or set the priority of |queue|.
60   void SetQueuePriority(internal::TaskQueueImpl* queue,
61                         TaskQueue::QueuePriority priority);
62 
63   // Called to choose the work queue from which the next task should be taken
64   // and run. Return the queue to service if there is one or null otherwise.
65   // This function is called on the main thread.
66   WorkQueue* SelectWorkQueueToService(
67       SelectTaskOption option = SelectTaskOption::kDefault);
68 
69   // Serialize the selector state for tracing/debugging.
70   Value::Dict AsValue() const;
71 
72   class BASE_EXPORT Observer {
73    public:
74     virtual ~Observer() = default;
75 
76     // Called when |queue| transitions from disabled to enabled.
77     virtual void OnTaskQueueEnabled(internal::TaskQueueImpl* queue) = 0;
78 
79     // Called when work becomes available.
80     virtual void OnWorkAvailable() = 0;
81   };
82 
83   // Called once to set the Observer. This function is called
84   // on the main thread. If |observer| is null, then no callbacks will occur.
85   void SetTaskQueueSelectorObserver(Observer* observer);
86 
87   // Returns the priority of the most important pending task if one exists.
88   // O(1).
89   std::optional<TaskQueue::QueuePriority> GetHighestPendingPriority(
90       SelectTaskOption option = SelectTaskOption::kDefault) const;
91 
92   // WorkQueueSets::Observer implementation:
93   void WorkQueueSetBecameEmpty(size_t set_index) override;
94   void WorkQueueSetBecameNonEmpty(size_t set_index) override;
95 
96   // Populates |result| with tasks with lower priority than the first task from
97   // |selected_work_queue| which could otherwise run now.
98   void CollectSkippedOverLowerPriorityTasks(
99       const internal::WorkQueue* selected_work_queue,
100       std::vector<const Task*>* result) const;
101 
102  protected:
delayed_work_queue_sets()103   WorkQueueSets* delayed_work_queue_sets() { return &delayed_work_queue_sets_; }
104 
immediate_work_queue_sets()105   WorkQueueSets* immediate_work_queue_sets() {
106     return &immediate_work_queue_sets_;
107   }
108 
109   // This method will force select an immediate task if those are being
110   // starved by delayed tasks.
111   void SetImmediateStarvationCountForTest(int immediate_starvation_count);
112 
113   // Tracks which priorities are currently active, meaning there are pending
114   // runnable tasks with that priority. Because there are only a handful of
115   // priorities, and because we always run tasks in order from highest to lowest
116   // priority, we can use a single integer to represent enabled priorities,
117   // using a bit per priority.
118   class BASE_EXPORT ActivePriorityTracker {
119    public:
120     ActivePriorityTracker();
121 
HasActivePriority()122     bool HasActivePriority() const { return active_priorities_ != 0; }
123 
IsActive(TaskQueue::QueuePriority priority)124     bool IsActive(TaskQueue::QueuePriority priority) const {
125       return active_priorities_ & (size_t{1} << static_cast<size_t>(priority));
126     }
127 
128     void SetActive(TaskQueue::QueuePriority priority, bool is_active);
129 
130     TaskQueue::QueuePriority HighestActivePriority() const;
131 
132    private:
133     static_assert(SequenceManager::PrioritySettings::kMaxPriorities <
134                       sizeof(size_t) * 8,
135                   "The number of priorities must be strictly less than the "
136                   "number of bits of |active_priorities_|!");
137     size_t active_priorities_ = 0;
138   };
139 
140   /*
141    * SetOperation is used to configure ChooseWithPriority() and must have:
142    *
143    * static std::optional<WorkQueueAndTaskOrder>
144    * GetWithPriority(const WorkQueueSets& sets,
145    *                 TaskQueue::QueuePriority priority);
146    */
147 
148   // The default
149   struct SetOperationOldest {
GetWithPrioritySetOperationOldest150     static std::optional<WorkQueueAndTaskOrder> GetWithPriority(
151         const WorkQueueSets& sets,
152         TaskQueue::QueuePriority priority) {
153       return sets.GetOldestQueueAndTaskOrderInSet(priority);
154     }
155   };
156 
157 #if DCHECK_IS_ON()
158   struct SetOperationRandom {
GetWithPrioritySetOperationRandom159     static std::optional<WorkQueueAndTaskOrder> GetWithPriority(
160         const WorkQueueSets& sets,
161         TaskQueue::QueuePriority priority) {
162       return sets.GetRandomQueueAndTaskOrderInSet(priority);
163     }
164   };
165 #endif  // DCHECK_IS_ON()
166 
167   template <typename SetOperation>
ChooseWithPriority(TaskQueue::QueuePriority priority)168   WorkQueue* ChooseWithPriority(TaskQueue::QueuePriority priority) const {
169     // Maximum number of delayed tasks tasks which can be run while there's a
170     // waiting non-delayed task.
171     static const int kMaxDelayedStarvationTasks = 3;
172 
173     // Select an immediate work queue if we are starving immediate tasks.
174     if (immediate_starvation_count_ >= kMaxDelayedStarvationTasks) {
175       WorkQueue* queue =
176           ChooseImmediateOnlyWithPriority<SetOperation>(priority);
177       if (queue)
178         return queue;
179       return ChooseDelayedOnlyWithPriority<SetOperation>(priority);
180     }
181     return ChooseImmediateOrDelayedTaskWithPriority<SetOperation>(priority);
182   }
183 
184   template <typename SetOperation>
ChooseImmediateOnlyWithPriority(TaskQueue::QueuePriority priority)185   WorkQueue* ChooseImmediateOnlyWithPriority(
186       TaskQueue::QueuePriority priority) const {
187     if (auto queue_and_order = SetOperation::GetWithPriority(
188             immediate_work_queue_sets_, priority)) {
189       return queue_and_order->queue;
190     }
191     return nullptr;
192   }
193 
194   template <typename SetOperation>
ChooseDelayedOnlyWithPriority(TaskQueue::QueuePriority priority)195   WorkQueue* ChooseDelayedOnlyWithPriority(
196       TaskQueue::QueuePriority priority) const {
197     if (auto queue_and_order =
198             SetOperation::GetWithPriority(delayed_work_queue_sets_, priority)) {
199       return queue_and_order->queue;
200     }
201     return nullptr;
202   }
203 
204  private:
priority_count()205   size_t priority_count() const { return non_empty_set_counts_.size(); }
206 
207   void ChangeSetIndex(internal::TaskQueueImpl* queue,
208                       TaskQueue::QueuePriority priority);
209   void AddQueueImpl(internal::TaskQueueImpl* queue,
210                     TaskQueue::QueuePriority priority);
211   void RemoveQueueImpl(internal::TaskQueueImpl* queue);
212 
213 #if DCHECK_IS_ON() || !defined(NDEBUG)
214   bool CheckContainsQueueForTest(const internal::TaskQueueImpl* queue) const;
215 #endif
216 
217   template <typename SetOperation>
ChooseImmediateOrDelayedTaskWithPriority(TaskQueue::QueuePriority priority)218   WorkQueue* ChooseImmediateOrDelayedTaskWithPriority(
219       TaskQueue::QueuePriority priority) const {
220     if (auto immediate_queue_and_order = SetOperation::GetWithPriority(
221             immediate_work_queue_sets_, priority)) {
222       if (auto delayed_queue_and_order = SetOperation::GetWithPriority(
223               delayed_work_queue_sets_, priority)) {
224         return immediate_queue_and_order->order < delayed_queue_and_order->order
225                    ? immediate_queue_and_order->queue
226                    : delayed_queue_and_order->queue;
227       }
228       return immediate_queue_and_order->queue;
229     }
230     return ChooseDelayedOnlyWithPriority<SetOperation>(priority);
231   }
232 
233   // Returns true if there are pending tasks with priority |priority|.
234   bool HasTasksWithPriority(TaskQueue::QueuePriority priority) const;
235 
236   const scoped_refptr<const AssociatedThreadId> associated_thread_;
237 
238 #if DCHECK_IS_ON()
239   const bool random_task_selection_ = false;
240 #endif
241 
242   // Count of the number of sets (delayed or immediate) for each priority.
243   // Should only contain 0, 1 or 2.
244   std::vector<int> non_empty_set_counts_;
245 
246   static constexpr const int kMaxNonEmptySetCount = 2;
247   // An atomic is used here because InitializeFeatures() can race with
248   // SequenceManager reading this.
249   static std::atomic_int g_max_delayed_starvation_tasks;
250 
251   // List of active priorities, which is used to work out which priority to run
252   // next.
253   ActivePriorityTracker active_priority_tracker_;
254 
255   WorkQueueSets delayed_work_queue_sets_;
256   WorkQueueSets immediate_work_queue_sets_;
257   int immediate_starvation_count_ = 0;
258 
259   raw_ptr<Observer> task_queue_selector_observer_ = nullptr;  // Not owned.
260 };
261 
262 }  // namespace internal
263 }  // namespace sequence_manager
264 }  // namespace base
265 
266 #endif  // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_
267