1 // Copyright 2012 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 NET_BASE_PRIORITY_QUEUE_H_ 6 #define NET_BASE_PRIORITY_QUEUE_H_ 7 8 #include <stddef.h> 9 #include <stdint.h> 10 11 #include <list> 12 #include <utility> 13 #include <vector> 14 15 #include "base/check_op.h" 16 #include "base/functional/bind.h" 17 #include "base/functional/callback.h" 18 #include "base/not_fatal_until.h" 19 #include "base/threading/thread_checker.h" 20 21 #if !defined(NDEBUG) 22 #include <unordered_set> 23 #endif 24 25 namespace net { 26 27 // A simple priority queue. The order of values is by priority and then FIFO. 28 // Unlike the std::priority_queue, this implementation allows erasing elements 29 // from the queue, and all operations are O(p) time for p priority levels. 30 // The queue is agnostic to priority ordering (whether 0 precedes 1). 31 // If the highest priority is 0, FirstMin() returns the first in order. 32 // 33 // In debug-mode, the internal queues store (id, value) pairs where id is used 34 // to validate Pointers. 35 // 36 template <typename T> 37 class PriorityQueue { 38 private: 39 // This section is up-front for Pointer only. 40 #if !defined(NDEBUG) 41 typedef std::list<std::pair<unsigned, T> > List; 42 #else 43 typedef std::list<T> List; 44 #endif 45 46 public: 47 typedef uint32_t Priority; 48 49 // A pointer to a value stored in the queue. The pointer becomes invalid 50 // when the queue is destroyed or cleared, or the value is erased. 51 class Pointer { 52 public: 53 // Constructs a null pointer. Pointer()54 Pointer() : priority_(kNullPriority) { 55 #if !defined(NDEBUG) 56 id_ = static_cast<unsigned>(-1); 57 #endif 58 // TODO(syzm) 59 // An uninitialized iterator behaves like an uninitialized pointer as per 60 // the STL docs. The fix below is ugly and should possibly be replaced 61 // with a better approach. 62 iterator_ = dummy_empty_list_.end(); 63 } 64 Pointer(const Pointer & p)65 Pointer(const Pointer& p) 66 : priority_(p.priority_), 67 iterator_(p.iterator_) { 68 #if !defined(NDEBUG) 69 id_ = p.id_; 70 #endif 71 } 72 73 Pointer& operator=(const Pointer& p) { 74 // Self-assignment is benign. 75 priority_ = p.priority_; 76 iterator_ = p.iterator_; 77 #if !defined(NDEBUG) 78 id_ = p.id_; 79 #endif 80 return *this; 81 } 82 is_null()83 bool is_null() const { return priority_ == kNullPriority; } 84 priority()85 Priority priority() const { return priority_; } 86 87 #if !defined(NDEBUG) value()88 const T& value() const { return iterator_->second; } 89 #else value()90 const T& value() const { return *iterator_; } 91 #endif 92 93 // Comparing to Pointer from a different PriorityQueue is undefined. Equals(const Pointer & other)94 bool Equals(const Pointer& other) const { 95 return (priority_ == other.priority_) && (iterator_ == other.iterator_); 96 } 97 Reset()98 void Reset() { 99 *this = Pointer(); 100 } 101 102 private: 103 friend class PriorityQueue; 104 105 // Note that we need iterator and not const_iterator to pass to 106 // List::erase. When C++11 is turned on for Chromium, this could 107 // be changed to const_iterator and the const_casts in the rest of 108 // the file can be removed. 109 typedef typename PriorityQueue::List::iterator ListIterator; 110 111 static const Priority kNullPriority = static_cast<Priority>(-1); 112 113 // It is guaranteed that Pointer will treat |iterator| as a 114 // const_iterator. Pointer(Priority priority,const ListIterator & iterator)115 Pointer(Priority priority, const ListIterator& iterator) 116 : priority_(priority), iterator_(iterator) { 117 #if !defined(NDEBUG) 118 id_ = iterator_->first; 119 #endif 120 } 121 122 Priority priority_; 123 ListIterator iterator_; 124 // The STL iterators when uninitialized are like uninitialized pointers 125 // which cause crashes when assigned to other iterators. We need to 126 // initialize a NULL iterator to the end of a valid list. 127 List dummy_empty_list_; 128 129 #if !defined(NDEBUG) 130 // Used by the queue to check if a Pointer is valid. 131 unsigned id_; 132 #endif 133 }; 134 135 // Creates a new queue for |num_priorities|. PriorityQueue(Priority num_priorities)136 explicit PriorityQueue(Priority num_priorities) : lists_(num_priorities) { 137 #if !defined(NDEBUG) 138 next_id_ = 0; 139 #endif 140 } 141 142 PriorityQueue(const PriorityQueue&) = delete; 143 PriorityQueue& operator=(const PriorityQueue&) = delete; ~PriorityQueue()144 ~PriorityQueue() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); } 145 146 // Adds |value| with |priority| to the queue. Returns a pointer to the 147 // created element. Insert(T value,Priority priority)148 Pointer Insert(T value, Priority priority) { 149 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 150 DCHECK_LT(priority, lists_.size()); 151 ++size_; 152 List& list = lists_[priority]; 153 #if !defined(NDEBUG) 154 unsigned id = next_id_; 155 valid_ids_.insert(id); 156 ++next_id_; 157 list.emplace_back(id, std::move(value)); 158 #else 159 list.emplace_back(std::move(value)); 160 #endif 161 return Pointer(priority, std::prev(list.end())); 162 } 163 164 // Adds |value| with |priority| to the queue. Returns a pointer to the 165 // created element. InsertAtFront(T value,Priority priority)166 Pointer InsertAtFront(T value, Priority priority) { 167 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 168 DCHECK_LT(priority, lists_.size()); 169 ++size_; 170 List& list = lists_[priority]; 171 #if !defined(NDEBUG) 172 unsigned id = next_id_; 173 valid_ids_.insert(id); 174 ++next_id_; 175 list.emplace_front(std::pair(id, std::move(value))); 176 #else 177 list.emplace_front(std::move(value)); 178 #endif 179 return Pointer(priority, list.begin()); 180 } 181 182 // Removes the value pointed by |pointer| from the queue. All pointers to this 183 // value including |pointer| become invalid. Returns the erased value. Erase(const Pointer & pointer)184 T Erase(const Pointer& pointer) { 185 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 186 DCHECK_LT(pointer.priority_, lists_.size()); 187 DCHECK_GT(size_, 0u); 188 189 #if !defined(NDEBUG) 190 DCHECK_EQ(1u, valid_ids_.erase(pointer.id_)); 191 DCHECK_EQ(pointer.iterator_->first, pointer.id_); 192 T erased = std::move(pointer.iterator_->second); 193 #else 194 T erased = std::move(*pointer.iterator_); 195 #endif 196 197 --size_; 198 lists_[pointer.priority_].erase(pointer.iterator_); 199 return erased; 200 } 201 202 // Returns a pointer to the first value of minimum priority or a null-pointer 203 // if empty. FirstMin()204 Pointer FirstMin() const { 205 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 206 for (size_t i = 0; i < lists_.size(); ++i) { 207 List* list = const_cast<List*>(&lists_[i]); 208 if (!list->empty()) 209 return Pointer(i, list->begin()); 210 } 211 return Pointer(); 212 } 213 214 // Returns a pointer to the last value of minimum priority or a null-pointer 215 // if empty. LastMin()216 Pointer LastMin() const { 217 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 218 for (size_t i = 0; i < lists_.size(); ++i) { 219 List* list = const_cast<List*>(&lists_[i]); 220 if (!list->empty()) 221 return Pointer(i, --list->end()); 222 } 223 return Pointer(); 224 } 225 226 // Returns a pointer to the first value of maximum priority or a null-pointer 227 // if empty. FirstMax()228 Pointer FirstMax() const { 229 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 230 for (size_t i = lists_.size(); i > 0; --i) { 231 size_t index = i - 1; 232 List* list = const_cast<List*>(&lists_[index]); 233 if (!list->empty()) 234 return Pointer(index, list->begin()); 235 } 236 return Pointer(); 237 } 238 239 // Returns a pointer to the last value of maximum priority or a null-pointer 240 // if empty. LastMax()241 Pointer LastMax() const { 242 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 243 for (size_t i = lists_.size(); i > 0; --i) { 244 size_t index = i - 1; 245 List* list = const_cast<List*>(&lists_[index]); 246 if (!list->empty()) 247 return Pointer(index, --list->end()); 248 } 249 return Pointer(); 250 } 251 252 // Given an ordering of the values in this queue by decreasing priority and 253 // then FIFO, returns a pointer to the value following the value of the given 254 // pointer (which must be non-NULL). I.e., gets the next element in decreasing 255 // priority, then FIFO order. If the given pointer is already pointing at the 256 // last value, returns a null Pointer. 257 // 258 // (One could also implement GetNextTowardsFirstMin() [decreasing priority, 259 // then reverse FIFO] as well as GetNextTowards{First,Last}Max() [increasing 260 // priority, then {,reverse} FIFO].) GetNextTowardsLastMin(const Pointer & pointer)261 Pointer GetNextTowardsLastMin(const Pointer& pointer) const { 262 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 263 DCHECK(!pointer.is_null()); 264 DCHECK_LT(pointer.priority_, lists_.size()); 265 266 typename Pointer::ListIterator it = pointer.iterator_; 267 Priority priority = pointer.priority_; 268 CHECK(it != lists_[priority].end(), base::NotFatalUntil::M130); 269 ++it; 270 while (it == lists_[priority].end()) { 271 if (priority == 0u) { 272 DCHECK(pointer.Equals(LastMin())); 273 return Pointer(); 274 } 275 --priority; 276 it = const_cast<List*>(&lists_[priority])->begin(); 277 } 278 return Pointer(priority, it); 279 } 280 281 // Given an ordering of the values in this queue by decreasing priority and 282 // then FIFO, returns a pointer to the value preceding the value of the given 283 // pointer (which must be non-NULL). I.e., gets the next element in increasing 284 // priority, then reverse FIFO order. If the given pointer is already pointing 285 // at the first value, returns a null Pointer. GetPreviousTowardsFirstMax(const Pointer & pointer)286 Pointer GetPreviousTowardsFirstMax(const Pointer& pointer) const { 287 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 288 DCHECK(!pointer.is_null()); 289 DCHECK_LT(pointer.priority_, lists_.size()); 290 291 typename Pointer::ListIterator it = pointer.iterator_; 292 Priority priority = pointer.priority_; 293 CHECK(it != lists_[priority].end(), base::NotFatalUntil::M130); 294 while (it == lists_[priority].begin()) { 295 if (priority == num_priorities() - 1) { 296 DCHECK(pointer.Equals(FirstMax())); 297 return Pointer(); 298 } 299 ++priority; 300 it = const_cast<List*>(&lists_[priority])->end(); 301 } 302 return Pointer(priority, std::prev(it)); 303 } 304 305 // Checks whether |lhs| is closer in the queue to the first max element than 306 // |rhs|. Assumes that both Pointers refer to elements in this PriorityQueue. IsCloserToFirstMaxThan(const Pointer & lhs,const Pointer & rhs)307 bool IsCloserToFirstMaxThan(const Pointer& lhs, const Pointer& rhs) { 308 if (lhs.Equals(rhs)) 309 return false; 310 if (lhs.priority_ == rhs.priority_) { 311 // Traverse list starting from lhs and see if we find rhs. 312 for (auto it = lhs.iterator_; it != lists_[lhs.priority_].end(); ++it) { 313 if (it == rhs.iterator_) 314 return true; 315 } 316 return false; 317 } 318 return lhs.priority_ > rhs.priority_; 319 } 320 321 // Checks whether |lhs| is closer in the queue to the last min element than 322 // |rhs|. Assumes that both Pointers refer to elements in this PriorityQueue. IsCloserToLastMinThan(const Pointer & lhs,const Pointer & rhs)323 bool IsCloserToLastMinThan(const Pointer& lhs, const Pointer& rhs) { 324 return !lhs.Equals(rhs) && !IsCloserToFirstMaxThan(lhs, rhs); 325 } 326 327 // Finds the first element (with respect to decreasing priority, then FIFO 328 // order) which matches the given predicate. FindIf(const base::RepeatingCallback<bool (T)> & pred)329 Pointer FindIf(const base::RepeatingCallback<bool(T)>& pred) { 330 for (auto pointer = FirstMax(); !pointer.is_null(); 331 pointer = GetNextTowardsLastMin(pointer)) { 332 if (pred.Run(pointer.value())) 333 return pointer; 334 } 335 return Pointer(); 336 } 337 338 // Empties the queue. All pointers become invalid. Clear()339 void Clear() { 340 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 341 for (size_t i = 0; i < lists_.size(); ++i) { 342 lists_[i].clear(); 343 } 344 #if !defined(NDEBUG) 345 valid_ids_.clear(); 346 #endif 347 size_ = 0u; 348 } 349 350 // Returns the number of priorities the queue supports. num_priorities()351 size_t num_priorities() const { 352 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 353 return lists_.size(); 354 } 355 empty()356 bool empty() const { 357 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 358 return size_ == 0; 359 } 360 361 // Returns number of queued values. size()362 size_t size() const { 363 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 364 return size_; 365 } 366 367 private: 368 typedef std::vector<List> ListVector; 369 370 #if !defined(NDEBUG) 371 unsigned next_id_; 372 std::unordered_set<unsigned> valid_ids_; 373 #endif 374 375 ListVector lists_; 376 size_t size_ = 0; 377 378 THREAD_CHECKER(thread_checker_); 379 }; 380 381 } // namespace net 382 383 #endif // NET_BASE_PRIORITY_QUEUE_H_ 384