1 // Copyright 2022 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_HIERARCHICAL_TIMING_WHEEL_H_ 6 #define BASE_TASK_SEQUENCE_MANAGER_HIERARCHICAL_TIMING_WHEEL_H_ 7 8 #include <algorithm> 9 #include <array> 10 #include <numeric> 11 #include <vector> 12 13 #include "base/containers/intrusive_heap.h" 14 #include "base/task/sequence_manager/timing_wheel.h" 15 #include "base/time/time.h" 16 #include "third_party/abseil-cpp/absl/types/optional.h" 17 18 namespace base::sequence_manager { 19 20 // A union of |TimingWheelHandle| and |HeapHandle|. At any given 21 // time it holds a value of one of its alternative types. It can only 22 // have either. This class is maintained by the hierarchical timing 23 // wheel as the object moves around within it. It can be used to subsequently 24 // remove the element. 25 class BASE_EXPORT HierarchicalTimingWheelHandle { 26 public: 27 enum : size_t { kInvalidIndex = std::numeric_limits<size_t>::max() }; 28 29 HierarchicalTimingWheelHandle(); 30 31 HierarchicalTimingWheelHandle(const HierarchicalTimingWheelHandle& other) = 32 default; 33 HierarchicalTimingWheelHandle(HierarchicalTimingWheelHandle&& other) noexcept; 34 35 HierarchicalTimingWheelHandle& operator=( 36 const HierarchicalTimingWheelHandle& other) = default; 37 HierarchicalTimingWheelHandle& operator=( 38 HierarchicalTimingWheelHandle&& other) noexcept; 39 40 ~HierarchicalTimingWheelHandle(); 41 42 // TimingWheel contract 43 internal::TimingWheelHandle GetTimingWheelHandle() const; 44 void SetTimingWheelHandle(internal::TimingWheelHandle timing_wheel_handle); 45 void ClearTimingWheelHandle(); 46 47 // IntrusiveHeap contract 48 HeapHandle GetHeapHandle(); 49 void SetHeapHandle(HeapHandle handle); 50 void ClearHeapHandle(); 51 52 size_t GetHierarchyIndex() const; 53 void SetHierarchyIndex(size_t hierarchy_index); 54 void ClearHierarchyIndex(); 55 56 // Gets a default constructed HierarchicalTimingWheelHandle. 57 static HierarchicalTimingWheelHandle Invalid(); 58 59 bool IsValid() const; 60 61 private: 62 // The handle of the timing wheel in the hierarchical timing wheel where the 63 // element is in. 64 internal::TimingWheelHandle timing_wheel_handle_; 65 66 // The handle of the heap in the hierarchical timing wheel where the element 67 // is in. 68 HeapHandle heap_handle_; 69 70 // The index in the hierarchy of timing wheels and heaps, this handle belongs 71 // to. 72 size_t hierarchy_index_ = kInvalidIndex; 73 }; 74 75 // The default HierarchicalTimingWheelHandleAccessor, which simply forwards 76 // calls to the underlying type. It assumes |T| provides 77 // HierarchicalTimingWheelHandle storage and will simply forward calls to 78 // equivalent member function. 79 template <typename T> 80 struct DefaultHierarchicalTimingWheelHandleAccessor { SetTimingWheelHandleDefaultHierarchicalTimingWheelHandleAccessor81 void SetTimingWheelHandle(T* element, 82 internal::TimingWheelHandle handle) const { 83 HierarchicalTimingWheelHandle* htw_handle = element->handle(); 84 htw_handle->SetTimingWheelHandle(handle); 85 } 86 ClearTimingWheelHandleDefaultHierarchicalTimingWheelHandleAccessor87 void ClearTimingWheelHandle(T* element) const { 88 HierarchicalTimingWheelHandle* htw_handle = element->handle(); 89 htw_handle->ClearTimingWheelHandle(); 90 } 91 GetHeapHandleDefaultHierarchicalTimingWheelHandleAccessor92 HeapHandle GetHeapHandle(const T* element) const { 93 HierarchicalTimingWheelHandle* htw_handle = element->handle(); 94 return htw_handle->GetHeapHandle(); 95 } 96 SetHeapHandleDefaultHierarchicalTimingWheelHandleAccessor97 void SetHeapHandle(T* element, HeapHandle handle) const { 98 HierarchicalTimingWheelHandle* htw_handle = element->handle(); 99 htw_handle->SetHeapHandle(handle); 100 } 101 ClearHeapHandleDefaultHierarchicalTimingWheelHandleAccessor102 void ClearHeapHandle(T* element) const { 103 HierarchicalTimingWheelHandle* htw_handle = element->handle(); 104 htw_handle->ClearHeapHandle(); 105 } 106 SetHierarchyIndexDefaultHierarchicalTimingWheelHandleAccessor107 void SetHierarchyIndex(T* element, size_t hierarchy_index) const { 108 HierarchicalTimingWheelHandle* htw_handle = element->handle(); 109 htw_handle->SetHierarchyIndex(hierarchy_index); 110 } 111 ClearHierarchyIndexDefaultHierarchicalTimingWheelHandleAccessor112 void ClearHierarchyIndex(T* element) const { 113 HierarchicalTimingWheelHandle* htw_handle = element->handle(); 114 htw_handle->ClearHierarchyIndex(); 115 } 116 }; 117 118 // Gets the delayed run time of the |element|. Assumes the |element| has a 119 // public |delayed_run_time| member variable. 120 template <typename T> 121 struct GetDelayedRunTime { operatorGetDelayedRunTime122 TimeTicks operator()(const T& element) { return element.delayed_run_time; } 123 }; 124 125 // Used for ordering elements in the IntrusiveHeap in the hierarchy. 126 template <typename T> 127 struct Compare { operatorCompare128 bool operator()(const T& lhs, const T& rhs) const { 129 return lhs.delayed_run_time > rhs.delayed_run_time; 130 } 131 }; 132 133 // This class is made to optimize the data structure IntrusiveHeap. Timers are 134 // implemented by scheduling the user task using TaskRunner::PostDelayedTask(). 135 // The elements are then inserted in an InstrusiveHeap. It suffers from its time 136 // complexity of O(LgN) removal and insertion. 137 // 138 // This class is an implementation of timing wheel technique. It contains a 139 // hierarchy which is a sequence of timing wheels and heaps with different 140 // granularities used to span a greater range of intervals. There are two heaps 141 // in the hierarchy, each placed on the two ends of the sequence of timing 142 // wheels. 143 // 144 // |T| is a typename for the intervals that are inserted in this class. 145 // |TotalWheels| is the number of timing wheels to be constructed in the 146 // hierarchy. |WheelSize| is the number of buckets in each of the timing wheel. 147 // |SmallestBucketDeltaInMicroseconds| corresponds to the time delta per 148 // bucket for the smallest timing wheel in the hierarchy. The time delta per 149 // bucket for the following timing wheels are WheelSize * 150 // |time_delta_per_bucket| of previous timing wheel. 151 // |HierarchicalTimingWheelHandleAccessor| is the type of the object which under 152 // the hood manages the HierarchicalTimingWheelHandle. |GetDelayedRunTime| is a 153 // function which returns the time when the element is due at. 154 // 155 // Example: 156 // Note: The number enclosing in the curly brackets "{}" are the data 157 // structure's hierarchy number. It exists to understand their order in the 158 // hierarchy. 159 // 160 // TotalWheels = 4 161 // WheelSize = 100 162 // SmallestBucketDeltaInMicroseconds = 500 microseconds 163 // 164 // Heap{0} - all elements with delays below 500 microseconds 165 // 166 // Wheel{1} - each bucket of 500microseconds = 0.5ms. 167 // bucket0 contains 0 <= delta < 0.5ms 168 // bucket1 contains 0.5 <= delta < 1ms 169 // Wheel1 contains 0.5 <= delta < 50ms 170 // 171 // Wheel{2} - each bucket of 50ms. 172 // bucket0 contains 0 <= delta < 50ms 173 // bucket1 contains 50ms <= delta < 100ms 174 // Wheel1 contains 50ms <= delta < 5s 175 // 176 // Wheel{3} - each bucket of 5s. 177 // bucket0 contains 0 <= delta < 5s 178 // bucket1 contains 5s <= delta < 10s 179 // Wheel1 contains 5s <= delta < 500s 180 // 181 // Wheel{4} - each bucket of 500s. 182 // bucket0 contains 0 <= delta < 500s 183 // bucket1 contains 500s <= delta < 1000s 184 // Wheel1 contains 500s <= delta < 50000s 185 // 186 // Heap{5} - all elements with delay above or equals to 500microseconds * 187 // (100^4) 188 // 189 // This class takes O(1) time to insert and cancel timers. However, if a element 190 // has a very small or big timer interval, then it's placed in a heap. This 191 // means, the removal and insertion won't be as efficient. However, the 192 // expectation is that such elements with very small or very big intervals would 193 // be very few. 194 195 template <typename T, 196 size_t TotalWheels, 197 size_t WheelSize, 198 size_t SmallestBucketDeltaInMicroseconds, 199 typename HierarchicalTimingWheelHandleAccessor = 200 DefaultHierarchicalTimingWheelHandleAccessor<T>, 201 typename GetDelayedRunTime = GetDelayedRunTime<T>, 202 typename Compare = Compare<T>> 203 class HierarchicalTimingWheel { 204 public: 205 // Construct a HierarchicalTimingWheel instance where |last_wakeup| 206 // corresponds to the last time it was updated. 207 explicit HierarchicalTimingWheel( 208 TimeTicks last_wakeup, 209 const HierarchicalTimingWheelHandleAccessor& 210 hierarchical_timing_wheel_handle_accessor = 211 HierarchicalTimingWheelHandleAccessor(), 212 const GetDelayedRunTime& get_delayed_run_time = GetDelayedRunTime(), 213 const Compare compare = Compare()) small_delay_heap_(compare,hierarchical_timing_wheel_handle_accessor)214 : small_delay_heap_(compare, hierarchical_timing_wheel_handle_accessor), 215 large_delay_heap_(compare, hierarchical_timing_wheel_handle_accessor), 216 last_wakeup_(last_wakeup), 217 hierarchical_timing_wheel_handle_accessor_( 218 hierarchical_timing_wheel_handle_accessor), 219 get_delayed_run_time_(get_delayed_run_time) {} 220 221 HierarchicalTimingWheel(HierarchicalTimingWheel&&) = delete; 222 HierarchicalTimingWheel& operator=(HierarchicalTimingWheel&&) = delete; 223 224 HierarchicalTimingWheel(const HierarchicalTimingWheel&) = delete; 225 HierarchicalTimingWheel& operator=(const HierarchicalTimingWheel&) = delete; 226 227 ~HierarchicalTimingWheel() = default; 228 Size()229 size_t Size() { 230 return small_delay_heap_.size() + large_delay_heap_.size() + 231 std::accumulate(std::begin(wheels_), std::end(wheels_), 0, 232 [](size_t i, auto& wheel) { 233 return wheel.total_elements() + i; 234 }); 235 } 236 237 // Inserts the |element| based on its delayed run time into one of the 238 // |wheels_|. Insert(T element)239 typename std::vector<T>::const_iterator Insert(T element) { 240 DCHECK(get_delayed_run_time_(element) > last_wakeup_); 241 242 const TimeDelta delay = get_delayed_run_time_(element) - last_wakeup_; 243 const size_t hierarchy_index = FindHierarchyIndex(delay); 244 245 if (IsHeap(hierarchy_index)) { 246 auto& heap = GetHeapForHierarchyIndex(hierarchy_index); 247 hierarchical_timing_wheel_handle_accessor_.SetHierarchyIndex( 248 &element, hierarchy_index); 249 auto it = heap.insert(std::move(element)); 250 return it; 251 } else { 252 auto& wheel = GetTimingWheelForHierarchyIndex(hierarchy_index); 253 hierarchical_timing_wheel_handle_accessor_.SetHierarchyIndex( 254 &element, hierarchy_index); 255 auto it = wheel.Insert(std::move(element), delay); 256 return it; 257 } 258 } 259 260 // Updates the hierarchy and reassigns the elements that need to be 261 // placed in a different timing wheel or heap to reflect their respective 262 // delay. It returns the elements that are expired. Update(TimeTicks now)263 std::vector<T> Update(TimeTicks now) { 264 DCHECK(now >= last_wakeup_); 265 std::vector<T> expired_elements; 266 267 // Check for expired elements in the small delay heap. 268 while (!small_delay_heap_.empty() && 269 get_delayed_run_time_(small_delay_heap_.top()) <= now) { 270 T element = small_delay_heap_.take_top(); 271 272 // Clear the hierarchy index since the |element| will be returned. 273 hierarchical_timing_wheel_handle_accessor_.ClearHierarchyIndex(&element); 274 275 expired_elements.push_back(std::move(element)); 276 } 277 278 // Look into the timing wheels for elements which have either expired or 279 // need to be moved down the hierarchy. 280 std::vector<T> elements; 281 const TimeDelta time_delta = now - last_wakeup_; 282 const size_t timing_wheels_delay_upperbound = 283 SmallestBucketDeltaInMicroseconds * Pow(WheelSize, TotalWheels); 284 const TimeTicks timing_wheels_maximum_delayed_run_time = 285 now + Milliseconds(timing_wheels_delay_upperbound); 286 last_wakeup_ = now; 287 288 for (size_t wheel_index = 0; wheel_index < TotalWheels; wheel_index++) { 289 wheels_[wheel_index].AdvanceTimeAndRemoveExpiredElements(time_delta, 290 elements); 291 } 292 293 // Keep on removing the top elements from the |large_delay_heap_| which 294 // could be either moved down the hierarchy or are expired. 295 while (!large_delay_heap_.empty() && 296 get_delayed_run_time_(large_delay_heap_.top()) < 297 timing_wheels_maximum_delayed_run_time) { 298 elements.push_back(std::move(large_delay_heap_.take_top())); 299 } 300 301 // Re-insert elements which haven't expired yet. 302 for (auto& element : elements) { 303 if (now >= get_delayed_run_time_(element)) { 304 hierarchical_timing_wheel_handle_accessor_.ClearHierarchyIndex( 305 &element); 306 expired_elements.emplace_back(std::move(element)); 307 } else { 308 // Doesn't clear hierarchy index since the element will have their 309 // hierarchy index overwritten when re-inserted. 310 Insert(std::move(element)); 311 } 312 } 313 314 return expired_elements; 315 } 316 317 // Removes the |element|. This is considered as the element getting cancelled 318 // and will never be run. Remove(HierarchicalTimingWheelHandle & handle)319 void Remove(HierarchicalTimingWheelHandle& handle) { 320 DCHECK(handle.IsValid()); 321 if (handle.GetTimingWheelHandle().IsValid()) { 322 auto& wheel = GetTimingWheelForHierarchyIndex(handle.GetHierarchyIndex()); 323 wheel.Remove(handle.GetTimingWheelHandle()); 324 } else { 325 auto& heap = GetHeapForHierarchyIndex(handle.GetHierarchyIndex()); 326 heap.erase(handle.GetHeapHandle()); 327 } 328 } 329 330 // Returns the earliest due element in all of the hierarchy. This method 331 // should only called when the HierarchicalTimingWheel is not empty. Top()332 typename std::vector<T>::const_reference Top() { 333 DCHECK_NE(Size(), 0u); 334 335 // Check for smallest elements heap first. 336 if (!small_delay_heap_.empty()) { 337 return small_delay_heap_.top(); 338 } 339 340 // Iterate from smallest to biggest element wheel. 341 for (size_t i = 0; i < TotalWheels; i++) { 342 if (wheels_[i].total_elements() != 0) { 343 return wheels_[i].Top(); 344 } 345 } 346 347 // The result must be in the biggest elements heap. 348 return large_delay_heap_.top(); 349 } 350 351 private: IsHeap(size_t hierarchy_index)352 bool IsHeap(size_t hierarchy_index) { 353 return hierarchy_index == 0 or hierarchy_index == TotalWheels + 1; 354 } 355 GetHeapForHierarchyIndex(size_t hierarchy_index)356 auto& GetHeapForHierarchyIndex(size_t hierarchy_index) { 357 DCHECK(hierarchy_index == 0 || hierarchy_index == TotalWheels + 1); 358 return hierarchy_index == 0 ? small_delay_heap_ : large_delay_heap_; 359 } 360 GetTimingWheelForHierarchyIndex(size_t hierarchy_index)361 auto& GetTimingWheelForHierarchyIndex(size_t hierarchy_index) { 362 DCHECK(hierarchy_index > 0); 363 DCHECK(hierarchy_index < TotalWheels + 1); 364 return wheels_[hierarchy_index - 1]; 365 } 366 367 // Calculates the hierarchy index at which a element with |delay| should be 368 // appended in. FindHierarchyIndex(TimeDelta delay)369 size_t FindHierarchyIndex(TimeDelta delay) { 370 DCHECK(!delay.is_zero()); 371 372 if (delay < Microseconds(SmallestBucketDeltaInMicroseconds)) 373 return 0; 374 375 for (size_t i = 0; i < TotalWheels; i++) { 376 if (delay < (wheels_[i].time_delta_per_bucket() * WheelSize)) { 377 return i + 1; 378 } 379 } 380 381 // Return the index of the heap placed at the end of the hierarchy. 382 return TotalWheels + 1; 383 } 384 385 // Computes |a| to the power of |b| at compile time. This is used to compute 386 // the parameter for |TimingWheel| when generating |wheels_| at compile 387 // time. Pow(size_t a,size_t b)388 constexpr static std::size_t Pow(size_t a, size_t b) { 389 size_t res = 1; 390 for (size_t i = 0; i < b; i++) { 391 res *= a; 392 } 393 return res; 394 } 395 396 using Wheel = 397 typename internal::TimingWheel<T, 398 WheelSize, 399 HierarchicalTimingWheelHandleAccessor, 400 GetDelayedRunTime>; 401 402 // Generates |wheels_| at compile time. 403 template <size_t... I> MakeWheels(std::index_sequence<I...>)404 static std::array<Wheel, TotalWheels> MakeWheels(std::index_sequence<I...>) { 405 return {(Wheel(Microseconds(SmallestBucketDeltaInMicroseconds * 406 Pow(WheelSize, I))))...}; 407 } 408 409 // The timing wheels where the elements are added according to their delay. 410 std::array<Wheel, TotalWheels> wheels_ = 411 MakeWheels(std::make_index_sequence<TotalWheels>{}); 412 413 // There are two heaps enclosing the sequence of timing wheels. The first one 414 // contains elements whose delay is too small to enter a timing wheel. The 415 // second one contains elements whose delay is too big to enter a timing 416 // wheel. 417 IntrusiveHeap<T, Compare, HierarchicalTimingWheelHandleAccessor> 418 small_delay_heap_; 419 IntrusiveHeap<T, Compare, HierarchicalTimingWheelHandleAccessor> 420 large_delay_heap_; 421 422 // The last time when the timing wheels were updated. 423 TimeTicks last_wakeup_; 424 425 HierarchicalTimingWheelHandleAccessor 426 hierarchical_timing_wheel_handle_accessor_; 427 428 GetDelayedRunTime get_delayed_run_time_; 429 }; 430 431 } // namespace base::sequence_manager 432 433 #endif 434