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